Merge remote-tracking branch 'origin/release-candidate' into release
							
								
								
									
										17
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -123,18 +123,11 @@ jobs: | ||||
|       - name: 'Upload artifacts to update server' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         run: | | ||||
|           mkdir -p ~/.ssh | ||||
|           ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts | ||||
|           echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; | ||||
|           chmod 600 ./deploy_key; | ||||
|           rsync -avzP --delete --mkpath \ | ||||
|               -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ | ||||
|               artifacts/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${BRANCH_NAME}/"; | ||||
|           rm ./deploy_key; | ||||
| 
 | ||||
|       - name: 'Trigger update server reindex' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         run: curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} | ||||
|           FILES=$(for CUR in $(ls artifacts/); do echo "-F files=@artifacts/$CUR"; done) | ||||
|           curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ | ||||
|               -F "branch=${BRANCH_NAME}" \ | ||||
|               ${FILES[@]} \ | ||||
|               "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles | ||||
| 
 | ||||
|       - name: 'Find Previous Comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} | ||||
|  | ||||
							
								
								
									
										3
									
								
								.github/workflows/reindex.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -11,4 +11,5 @@ jobs: | ||||
|     steps: | ||||
|       - name: Trigger reindex | ||||
|         run: | | ||||
|           curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} | ||||
|           curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ | ||||
|               "${{ secrets.INDEXER_URL }}"/firmware/reindex | ||||
|  | ||||
							
								
								
									
										22
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -30,27 +30,25 @@ bindings/ | ||||
| .mxproject | ||||
| Brewfile.lock.json | ||||
| 
 | ||||
| # Visual Studio Code | ||||
| /.vscode/ | ||||
| 
 | ||||
| # Kate | ||||
| .kateproject | ||||
| .kateconfig | ||||
| 
 | ||||
| # legendary cmake's | ||||
| build | ||||
| CMakeLists.txt | ||||
| 
 | ||||
| # bundle output | ||||
| dist | ||||
| 
 | ||||
| # kde | ||||
| .directory | ||||
| 
 | ||||
| # SCons | ||||
| .sconsign.dblite | ||||
| 
 | ||||
| 
 | ||||
| # Visual Studio Code | ||||
| /.vscode | ||||
| 
 | ||||
| # bundle output | ||||
| /dist | ||||
| 
 | ||||
| # SCons build dir | ||||
| build/ | ||||
| /build | ||||
| 
 | ||||
| # Toolchain | ||||
| /toolchain | ||||
| @ -64,3 +62,5 @@ PVS-Studio.log | ||||
| *.PVS-Studio.* | ||||
| 
 | ||||
| .gdbinit | ||||
| 
 | ||||
| /fbt_options_local.py | ||||
| @ -14,9 +14,7 @@ void lfrfid_debug_scene_tune_on_enter(void* context) { | ||||
|     furi_hal_rfid_comp_set_callback(comparator_trigger_callback, app); | ||||
|     furi_hal_rfid_comp_start(); | ||||
| 
 | ||||
|     furi_hal_rfid_pins_read(); | ||||
|     furi_hal_rfid_tim_read(125000, 0.5); | ||||
|     furi_hal_rfid_tim_read_start(); | ||||
|     furi_hal_rfid_tim_read_start(125000, 0.5); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidDebugViewTune); | ||||
| } | ||||
| @ -43,6 +41,5 @@ void lfrfid_debug_scene_tune_on_exit(void* context) { | ||||
| 
 | ||||
|     furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog); | ||||
|     furi_hal_rfid_tim_read_stop(); | ||||
|     furi_hal_rfid_tim_reset(); | ||||
|     furi_hal_rfid_pins_reset(); | ||||
| } | ||||
|  | ||||
| @ -27,6 +27,12 @@ static const uint32_t nfc_test_file_version = 1; | ||||
| #define NFC_TEST_DATA_MAX_LEN 18 | ||||
| #define NFC_TETS_TIMINGS_MAX_LEN 1350 | ||||
| 
 | ||||
| // Maximum allowed time for buffer preparation to fit 500us nt message timeout
 | ||||
| #define NFC_TEST_4_BYTE_BUILD_BUFFER_TIM_MAX (150) | ||||
| #define NFC_TEST_16_BYTE_BUILD_BUFFER_TIM_MAX (640) | ||||
| #define NFC_TEST_4_BYTE_BUILD_SIGNAL_TIM_MAX (110) | ||||
| #define NFC_TEST_16_BYTE_BUILD_SIGNAL_TIM_MAX (440) | ||||
| 
 | ||||
| typedef struct { | ||||
|     Storage* storage; | ||||
|     NfcaSignal* signal; | ||||
| @ -89,13 +95,13 @@ static bool nfc_test_read_signal_from_file(const char* file_name) { | ||||
| 
 | ||||
| static bool nfc_test_digital_signal_test_encode( | ||||
|     const char* file_name, | ||||
|     uint32_t encode_max_time, | ||||
|     uint32_t build_signal_max_time_us, | ||||
|     uint32_t build_buffer_max_time_us, | ||||
|     uint32_t timing_tolerance, | ||||
|     uint32_t timings_sum_tolerance) { | ||||
|     furi_assert(nfc_test); | ||||
| 
 | ||||
|     bool success = false; | ||||
|     uint32_t time = 0; | ||||
|     uint32_t dut_timings_sum = 0; | ||||
|     uint32_t ref_timings_sum = 0; | ||||
|     uint8_t parity[10] = {}; | ||||
| @ -109,17 +115,37 @@ static bool nfc_test_digital_signal_test_encode( | ||||
| 
 | ||||
|         // Encode signal
 | ||||
|         FURI_CRITICAL_ENTER(); | ||||
|         time = DWT->CYCCNT; | ||||
|         uint32_t time_start = DWT->CYCCNT; | ||||
| 
 | ||||
|         nfca_signal_encode( | ||||
|             nfc_test->signal, nfc_test->test_data, nfc_test->test_data_len * 8, parity); | ||||
| 
 | ||||
|         uint32_t time_signal = | ||||
|             (DWT->CYCCNT - time_start) / furi_hal_cortex_instructions_per_microsecond(); | ||||
| 
 | ||||
|         time_start = DWT->CYCCNT; | ||||
| 
 | ||||
|         digital_signal_prepare_arr(nfc_test->signal->tx_signal); | ||||
|         time = (DWT->CYCCNT - time) / furi_hal_cortex_instructions_per_microsecond(); | ||||
| 
 | ||||
|         uint32_t time_buffer = | ||||
|             (DWT->CYCCNT - time_start) / furi_hal_cortex_instructions_per_microsecond(); | ||||
|         FURI_CRITICAL_EXIT(); | ||||
| 
 | ||||
|         // Check timings
 | ||||
|         if(time > encode_max_time) { | ||||
|         if(time_signal > build_signal_max_time_us) { | ||||
|             FURI_LOG_E( | ||||
|                 TAG, "Encoding time: %ld us while accepted value: %ld us", time, encode_max_time); | ||||
|                 TAG, | ||||
|                 "Build signal time: %ld us while accepted value: %ld us", | ||||
|                 time_signal, | ||||
|                 build_signal_max_time_us); | ||||
|             break; | ||||
|         } | ||||
|         if(time_buffer > build_buffer_max_time_us) { | ||||
|             FURI_LOG_E( | ||||
|                 TAG, | ||||
|                 "Build buffer time: %ld us while accepted value: %ld us", | ||||
|                 time_buffer, | ||||
|                 build_buffer_max_time_us); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
| @ -156,7 +182,16 @@ static bool nfc_test_digital_signal_test_encode( | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         FURI_LOG_I(TAG, "Encoding time: %ld us. Acceptable time: %ld us", time, encode_max_time); | ||||
|         FURI_LOG_I( | ||||
|             TAG, | ||||
|             "Build signal time: %ld us. Acceptable time: %ld us", | ||||
|             time_signal, | ||||
|             build_signal_max_time_us); | ||||
|         FURI_LOG_I( | ||||
|             TAG, | ||||
|             "Build buffer time: %ld us. Acceptable time: %ld us", | ||||
|             time_buffer, | ||||
|             build_buffer_max_time_us); | ||||
|         FURI_LOG_I( | ||||
|             TAG, | ||||
|             "Timings sum difference: %ld [1/64MHZ]. Acceptable difference: %ld [1/64MHz]", | ||||
| @ -171,11 +206,19 @@ static bool nfc_test_digital_signal_test_encode( | ||||
| MU_TEST(nfc_digital_signal_test) { | ||||
|     mu_assert( | ||||
|         nfc_test_digital_signal_test_encode( | ||||
|             NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_SHORT_FILE, 500, 1, 37), | ||||
|             NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_SHORT_FILE, | ||||
|             NFC_TEST_4_BYTE_BUILD_SIGNAL_TIM_MAX, | ||||
|             NFC_TEST_4_BYTE_BUILD_BUFFER_TIM_MAX, | ||||
|             1, | ||||
|             37), | ||||
|         "NFC short digital signal test failed\r\n"); | ||||
|     mu_assert( | ||||
|         nfc_test_digital_signal_test_encode( | ||||
|             NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_LONG_FILE, 2000, 1, 37), | ||||
|             NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_LONG_FILE, | ||||
|             NFC_TEST_16_BYTE_BUILD_SIGNAL_TIM_MAX, | ||||
|             NFC_TEST_16_BYTE_BUILD_BUFFER_TIM_MAX, | ||||
|             1, | ||||
|             37), | ||||
|         "NFC long digital signal test failed\r\n"); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,11 +1,10 @@ | ||||
| #include "magic.h" | ||||
| #include "classic_gen1.h" | ||||
| 
 | ||||
| #include <furi_hal_nfc.h> | ||||
| 
 | ||||
| #define TAG "Magic" | ||||
| 
 | ||||
| #define MAGIC_CMD_WUPA (0x40) | ||||
| #define MAGIC_CMD_WIPE (0x41) | ||||
| #define MAGIC_CMD_ACCESS (0x43) | ||||
| 
 | ||||
| #define MAGIC_MIFARE_READ_CMD (0x30) | ||||
| @ -15,7 +14,7 @@ | ||||
| 
 | ||||
| #define MAGIC_BUFFER_SIZE (32) | ||||
| 
 | ||||
| bool magic_wupa() { | ||||
| bool magic_gen1_wupa() { | ||||
|     bool magic_activated = false; | ||||
|     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
| @ -23,19 +22,6 @@ bool magic_wupa() { | ||||
|     FuriHalNfcReturn ret = 0; | ||||
| 
 | ||||
|     do { | ||||
|         // Setup nfc poller
 | ||||
|         furi_hal_nfc_exit_sleep(); | ||||
|         furi_hal_nfc_ll_txrx_on(); | ||||
|         furi_hal_nfc_ll_poll(); | ||||
|         ret = furi_hal_nfc_ll_set_mode( | ||||
|             FuriHalNfcModePollNfca, FuriHalNfcBitrate106, FuriHalNfcBitrate106); | ||||
|         if(ret != FuriHalNfcReturnOk) break; | ||||
| 
 | ||||
|         furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER); | ||||
|         furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER); | ||||
|         furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); | ||||
|         furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCA); | ||||
| 
 | ||||
|         // Start communication
 | ||||
|         tx_data[0] = MAGIC_CMD_WUPA; | ||||
|         ret = furi_hal_nfc_ll_txrx_bits( | ||||
| @ -53,15 +39,10 @@ bool magic_wupa() { | ||||
|         magic_activated = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     if(!magic_activated) { | ||||
|         furi_hal_nfc_ll_txrx_off(); | ||||
|         furi_hal_nfc_start_sleep(); | ||||
|     } | ||||
| 
 | ||||
|     return magic_activated; | ||||
| } | ||||
| 
 | ||||
| bool magic_data_access_cmd() { | ||||
| bool magic_gen1_data_access_cmd() { | ||||
|     bool write_cmd_success = false; | ||||
|     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
| @ -86,15 +67,10 @@ bool magic_data_access_cmd() { | ||||
|         write_cmd_success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     if(!write_cmd_success) { | ||||
|         furi_hal_nfc_ll_txrx_off(); | ||||
|         furi_hal_nfc_start_sleep(); | ||||
|     } | ||||
| 
 | ||||
|     return write_cmd_success; | ||||
| } | ||||
| 
 | ||||
| bool magic_read_block(uint8_t block_num, MfClassicBlock* data) { | ||||
| bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data) { | ||||
|     furi_assert(data); | ||||
| 
 | ||||
|     bool read_success = false; | ||||
| @ -122,15 +98,10 @@ bool magic_read_block(uint8_t block_num, MfClassicBlock* data) { | ||||
|         read_success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     if(!read_success) { | ||||
|         furi_hal_nfc_ll_txrx_off(); | ||||
|         furi_hal_nfc_start_sleep(); | ||||
|     } | ||||
| 
 | ||||
|     return read_success; | ||||
| } | ||||
| 
 | ||||
| bool magic_write_blk(uint8_t block_num, MfClassicBlock* data) { | ||||
| bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data) { | ||||
|     furi_assert(data); | ||||
| 
 | ||||
|     bool write_success = false; | ||||
| @ -170,44 +141,5 @@ bool magic_write_blk(uint8_t block_num, MfClassicBlock* data) { | ||||
|         write_success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     if(!write_success) { | ||||
|         furi_hal_nfc_ll_txrx_off(); | ||||
|         furi_hal_nfc_start_sleep(); | ||||
|     } | ||||
| 
 | ||||
|     return write_success; | ||||
| } | ||||
| 
 | ||||
| bool magic_wipe() { | ||||
|     bool wipe_success = false; | ||||
|     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint16_t rx_len = 0; | ||||
|     FuriHalNfcReturn ret = 0; | ||||
| 
 | ||||
|     do { | ||||
|         tx_data[0] = MAGIC_CMD_WIPE; | ||||
|         ret = furi_hal_nfc_ll_txrx_bits( | ||||
|             tx_data, | ||||
|             8, | ||||
|             rx_data, | ||||
|             sizeof(rx_data), | ||||
|             &rx_len, | ||||
|             FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | | ||||
|                 FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, | ||||
|             furi_hal_nfc_ll_ms2fc(2000)); | ||||
| 
 | ||||
|         if(ret != FuriHalNfcReturnIncompleteByte) break; | ||||
|         if(rx_len != 4) break; | ||||
|         if(rx_data[0] != MAGIC_ACK) break; | ||||
| 
 | ||||
|         wipe_success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return wipe_success; | ||||
| } | ||||
| 
 | ||||
| void magic_deactivate() { | ||||
|     furi_hal_nfc_ll_txrx_off(); | ||||
|     furi_hal_nfc_sleep(); | ||||
| } | ||||
							
								
								
									
										11
									
								
								applications/external/nfc_magic/lib/magic/classic_gen1.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <lib/nfc/protocols/mifare_classic.h> | ||||
| 
 | ||||
| bool magic_gen1_wupa(); | ||||
| 
 | ||||
| bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data); | ||||
| 
 | ||||
| bool magic_gen1_data_access_cmd(); | ||||
| 
 | ||||
| bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data); | ||||
							
								
								
									
										33
									
								
								applications/external/nfc_magic/lib/magic/common.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,33 @@ | ||||
| #include "common.h" | ||||
| 
 | ||||
| #include <furi_hal_nfc.h> | ||||
| 
 | ||||
| #define REQA (0x26) | ||||
| #define CL1_PREFIX (0x93) | ||||
| #define SELECT (0x70) | ||||
| 
 | ||||
| #define MAGIC_BUFFER_SIZE (32) | ||||
| 
 | ||||
| bool magic_activate() { | ||||
|     FuriHalNfcReturn ret = 0; | ||||
| 
 | ||||
|     // Setup nfc poller
 | ||||
|     furi_hal_nfc_exit_sleep(); | ||||
|     furi_hal_nfc_ll_txrx_on(); | ||||
|     furi_hal_nfc_ll_poll(); | ||||
|     ret = furi_hal_nfc_ll_set_mode( | ||||
|         FuriHalNfcModePollNfca, FuriHalNfcBitrate106, FuriHalNfcBitrate106); | ||||
|     if(ret != FuriHalNfcReturnOk) return false; | ||||
| 
 | ||||
|     furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER); | ||||
|     furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER); | ||||
|     furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); | ||||
|     furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCA); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void magic_deactivate() { | ||||
|     furi_hal_nfc_ll_txrx_off(); | ||||
|     furi_hal_nfc_sleep(); | ||||
| } | ||||
							
								
								
									
										19
									
								
								applications/external/nfc_magic/lib/magic/common.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,19 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     MagicTypeClassicGen1, | ||||
|     MagicTypeClassicDirectWrite, | ||||
|     MagicTypeClassicAPDU, | ||||
|     MagicTypeUltralightGen1, | ||||
|     MagicTypeUltralightDirectWrite, | ||||
|     MagicTypeUltralightC_Gen1, | ||||
|     MagicTypeUltralightC_DirectWrite, | ||||
|     MagicTypeGen4, | ||||
| } MagicType; | ||||
| 
 | ||||
| bool magic_activate(); | ||||
| 
 | ||||
| void magic_deactivate(); | ||||
							
								
								
									
										199
									
								
								applications/external/nfc_magic/lib/magic/gen4.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,199 @@ | ||||
| #include "gen4.h" | ||||
| 
 | ||||
| #include <furi_hal_nfc.h> | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| #define TAG "Magic" | ||||
| 
 | ||||
| #define MAGIC_CMD_PREFIX (0xCF) | ||||
| 
 | ||||
| #define MAGIC_CMD_GET_CFG (0xC6) | ||||
| #define MAGIC_CMD_WRITE (0xCD) | ||||
| #define MAGIC_CMD_READ (0xCE) | ||||
| #define MAGIC_CMD_SET_CFG (0xF0) | ||||
| #define MAGIC_CMD_FUSE_CFG (0xF1) | ||||
| #define MAGIC_CMD_SET_PWD (0xFE) | ||||
| 
 | ||||
| #define MAGIC_BUFFER_SIZE (40) | ||||
| 
 | ||||
| const uint8_t MAGIC_DEFAULT_CONFIG[] = { | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x78, 0x00, 0x91, 0x02, 0xDA, 0xBC, 0x19, 0x10, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x04, 0x00, 0x08, 0x00 | ||||
| }; | ||||
| 
 | ||||
| const uint8_t MAGIC_DEFAULT_BLOCK0[] = { | ||||
|     0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | ||||
| }; | ||||
| 
 | ||||
| const uint8_t MAGIC_EMPTY_BLOCK[16] = { 0 }; | ||||
| 
 | ||||
| const uint8_t MAGIC_DEFAULT_SECTOR_TRAILER[] = { | ||||
|     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF | ||||
| }; | ||||
| 
 | ||||
| static bool magic_gen4_is_block_num_trailer(uint8_t n) { | ||||
|     n++; | ||||
|     if (n < 32 * 4) { | ||||
|         return (n % 4 == 0); | ||||
|     } | ||||
| 
 | ||||
|     return (n % 16 == 0); | ||||
| } | ||||
| 
 | ||||
| bool magic_gen4_get_cfg(uint32_t pwd, uint8_t* config) { | ||||
|     bool is_valid_config_len = false; | ||||
|     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint16_t rx_len = 0; | ||||
|     FuriHalNfcReturn ret = 0; | ||||
| 
 | ||||
|     do { | ||||
|         // Start communication
 | ||||
|         tx_data[0] = MAGIC_CMD_PREFIX; | ||||
|         tx_data[1] = (uint8_t)(pwd >> 24); | ||||
|         tx_data[2] = (uint8_t)(pwd >> 16); | ||||
|         tx_data[3] = (uint8_t)(pwd >> 8); | ||||
|         tx_data[4] = (uint8_t)pwd; | ||||
|         tx_data[5] = MAGIC_CMD_GET_CFG; | ||||
|         ret = furi_hal_nfc_ll_txrx( | ||||
|             tx_data, | ||||
|             6, | ||||
|             rx_data, | ||||
|             sizeof(rx_data), | ||||
|             &rx_len, | ||||
|             FURI_HAL_NFC_TXRX_DEFAULT, | ||||
|             furi_hal_nfc_ll_ms2fc(20)); | ||||
|         if(ret != FuriHalNfcReturnOk) break; | ||||
|         if(rx_len != 30 && rx_len != 32) break; | ||||
|         memcpy(config, rx_data, rx_len); | ||||
|         is_valid_config_len = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return is_valid_config_len; | ||||
| } | ||||
| 
 | ||||
| bool magic_gen4_set_cfg(uint32_t pwd, const uint8_t* config, uint8_t config_length, bool fuse) { | ||||
|     bool write_success = false; | ||||
|     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint16_t rx_len = 0; | ||||
|     FuriHalNfcReturn ret = 0; | ||||
| 
 | ||||
|     do { | ||||
|         // Start communication
 | ||||
|         tx_data[0] = MAGIC_CMD_PREFIX; | ||||
|         tx_data[1] = (uint8_t)(pwd >> 24); | ||||
|         tx_data[2] = (uint8_t)(pwd >> 16); | ||||
|         tx_data[3] = (uint8_t)(pwd >> 8); | ||||
|         tx_data[4] = (uint8_t)pwd; | ||||
|         tx_data[5] = fuse ? MAGIC_CMD_FUSE_CFG : MAGIC_CMD_SET_CFG; | ||||
|         memcpy(tx_data + 6, config, config_length); | ||||
|         ret = furi_hal_nfc_ll_txrx( | ||||
|             tx_data, | ||||
|             6 + config_length, | ||||
|             rx_data, | ||||
|             sizeof(rx_data), | ||||
|             &rx_len, | ||||
|             FURI_HAL_NFC_TXRX_DEFAULT, | ||||
|             furi_hal_nfc_ll_ms2fc(20)); | ||||
|         if(ret != FuriHalNfcReturnOk) break; | ||||
|         if(rx_len != 2) break; | ||||
|         write_success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return write_success; | ||||
| } | ||||
| 
 | ||||
| bool magic_gen4_set_pwd(uint32_t old_pwd, uint32_t new_pwd) { | ||||
|     bool change_success = false; | ||||
|     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint16_t rx_len = 0; | ||||
|     FuriHalNfcReturn ret = 0; | ||||
| 
 | ||||
|     do { | ||||
|         // Start communication
 | ||||
|         tx_data[0] = MAGIC_CMD_PREFIX; | ||||
|         tx_data[1] = (uint8_t)(old_pwd >> 24); | ||||
|         tx_data[2] = (uint8_t)(old_pwd >> 16); | ||||
|         tx_data[3] = (uint8_t)(old_pwd >> 8); | ||||
|         tx_data[4] = (uint8_t)old_pwd; | ||||
|         tx_data[5] = MAGIC_CMD_SET_PWD; | ||||
|         tx_data[6] = (uint8_t)(new_pwd >> 24); | ||||
|         tx_data[7] = (uint8_t)(new_pwd >> 16); | ||||
|         tx_data[8] = (uint8_t)(new_pwd >> 8); | ||||
|         tx_data[9] = (uint8_t)new_pwd; | ||||
|         ret = furi_hal_nfc_ll_txrx( | ||||
|             tx_data, | ||||
|             10, | ||||
|             rx_data, | ||||
|             sizeof(rx_data), | ||||
|             &rx_len, | ||||
|             FURI_HAL_NFC_TXRX_DEFAULT, | ||||
|             furi_hal_nfc_ll_ms2fc(20)); | ||||
|         FURI_LOG_I(TAG, "ret %d, len %d", ret, rx_len); | ||||
|         if(ret != FuriHalNfcReturnOk) break; | ||||
|         if(rx_len != 2) break; | ||||
|         change_success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return change_success; | ||||
| } | ||||
| 
 | ||||
| bool magic_gen4_write_blk(uint32_t pwd, uint8_t block_num, const uint8_t* data) { | ||||
|     bool write_success = false; | ||||
|     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||
|     uint16_t rx_len = 0; | ||||
|     FuriHalNfcReturn ret = 0; | ||||
| 
 | ||||
|     do { | ||||
|         // Start communication
 | ||||
|         tx_data[0] = MAGIC_CMD_PREFIX; | ||||
|         tx_data[1] = (uint8_t)(pwd >> 24); | ||||
|         tx_data[2] = (uint8_t)(pwd >> 16); | ||||
|         tx_data[3] = (uint8_t)(pwd >> 8); | ||||
|         tx_data[4] = (uint8_t)pwd; | ||||
|         tx_data[5] = MAGIC_CMD_WRITE; | ||||
|         tx_data[6] = block_num; | ||||
|         memcpy(tx_data + 7, data, 16); | ||||
|         ret = furi_hal_nfc_ll_txrx( | ||||
|             tx_data, | ||||
|             23, | ||||
|             rx_data, | ||||
|             sizeof(rx_data), | ||||
|             &rx_len, | ||||
|             FURI_HAL_NFC_TXRX_DEFAULT, | ||||
|             furi_hal_nfc_ll_ms2fc(200)); | ||||
|         if(ret != FuriHalNfcReturnOk) break; | ||||
|         if(rx_len != 2) break; | ||||
|         write_success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return write_success; | ||||
| } | ||||
| 
 | ||||
| bool magic_gen4_wipe(uint32_t pwd) { | ||||
|     if(!magic_gen4_set_cfg(pwd, MAGIC_DEFAULT_CONFIG, sizeof(MAGIC_DEFAULT_CONFIG), false)) { | ||||
|         FURI_LOG_E(TAG, "Set config failed"); | ||||
|         return false; | ||||
|     } | ||||
|     if(!magic_gen4_write_blk(pwd, 0, MAGIC_DEFAULT_BLOCK0)) { | ||||
|         FURI_LOG_E(TAG, "Block 0 write failed"); | ||||
|         return false; | ||||
|     } | ||||
|     for(size_t i = 1; i < 64; i++) { | ||||
|         const uint8_t* block = magic_gen4_is_block_num_trailer(i) ? MAGIC_DEFAULT_SECTOR_TRAILER : MAGIC_EMPTY_BLOCK; | ||||
|         if(!magic_gen4_write_blk(pwd, i, block)) { | ||||
|             FURI_LOG_E(TAG, "Block %d write failed", i); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     for(size_t i = 65; i < 256; i++) { | ||||
|         if(!magic_gen4_write_blk(pwd, i, MAGIC_EMPTY_BLOCK)) { | ||||
|             FURI_LOG_E(TAG, "Block %d write failed", i); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
							
								
								
									
										48
									
								
								applications/external/nfc_magic/lib/magic/gen4.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,48 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <lib/nfc/protocols/mifare_classic.h> | ||||
| 
 | ||||
| #define MAGIC_GEN4_DEFAULT_PWD 0x00000000 | ||||
| #define MAGIC_GEN4_CONFIG_LEN 32 | ||||
| 
 | ||||
| #define NFCID1_SINGLE_SIZE 4 | ||||
| #define NFCID1_DOUBLE_SIZE 7 | ||||
| #define NFCID1_TRIPLE_SIZE 10 | ||||
| 
 | ||||
| typedef enum { | ||||
|     MagicGen4UIDLengthSingle = 0x00, | ||||
|     MagicGen4UIDLengthDouble = 0x01, | ||||
|     MagicGen4UIDLengthTriple = 0x02 | ||||
| } MagicGen4UIDLength; | ||||
| 
 | ||||
| typedef enum { | ||||
|     MagicGen4UltralightModeUL_EV1 = 0x00, | ||||
|     MagicGen4UltralightModeNTAG = 0x01, | ||||
|     MagicGen4UltralightModeUL_C = 0x02, | ||||
|     MagicGen4UltralightModeUL = 0x03 | ||||
| } MagicGen4UltralightMode; | ||||
| 
 | ||||
| typedef enum { | ||||
|     // for writing original (shadow) data
 | ||||
|     MagicGen4ShadowModePreWrite = 0x00, | ||||
|     // written data can be read once before restored to original
 | ||||
|     MagicGen4ShadowModeRestore = 0x01, | ||||
|     // written data is discarded
 | ||||
|     MagicGen4ShadowModeIgnore = 0x02, | ||||
|     // apparently for UL?
 | ||||
|     MagicGen4ShadowModeHighSpeedIgnore = 0x03 | ||||
| } MagicGen4ShadowMode; | ||||
| 
 | ||||
| bool magic_gen4_get_cfg(uint32_t pwd, uint8_t* config); | ||||
| 
 | ||||
| bool magic_gen4_set_cfg(uint32_t pwd, const uint8_t* config, uint8_t config_length, bool fuse); | ||||
| 
 | ||||
| bool magic_gen4_set_pwd(uint32_t old_pwd, uint32_t new_pwd); | ||||
| 
 | ||||
| bool magic_gen4_read_blk(uint32_t pwd, uint8_t block_num, uint8_t* data); | ||||
| 
 | ||||
| bool magic_gen4_write_blk(uint32_t pwd, uint8_t block_num, const uint8_t* data); | ||||
| 
 | ||||
| bool magic_gen4_wipe(uint32_t pwd); | ||||
| 
 | ||||
| void magic_gen4_deactivate(); | ||||
| @ -1,15 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <lib/nfc/protocols/mifare_classic.h> | ||||
| 
 | ||||
| bool magic_wupa(); | ||||
| 
 | ||||
| bool magic_read_block(uint8_t block_num, MfClassicBlock* data); | ||||
| 
 | ||||
| bool magic_data_access_cmd(); | ||||
| 
 | ||||
| bool magic_write_blk(uint8_t block_num, MfClassicBlock* data); | ||||
| 
 | ||||
| bool magic_wipe(); | ||||
| 
 | ||||
| void magic_deactivate(); | ||||
							
								
								
									
										23
									
								
								applications/external/nfc_magic/lib/magic/types.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,23 @@ | ||||
| #include "types.h" | ||||
| 
 | ||||
| const char* nfc_magic_type(MagicType type) { | ||||
|     if(type == MagicTypeClassicGen1) { | ||||
|         return "Classic Gen 1A/B"; | ||||
|     } else if(type == MagicTypeClassicDirectWrite) { | ||||
|         return "Classic DirectWrite"; | ||||
|     } else if(type == MagicTypeClassicAPDU) { | ||||
|         return "Classic APDU"; | ||||
|     } else if(type == MagicTypeUltralightGen1) { | ||||
|         return "Ultralight Gen 1"; | ||||
|     } else if(type == MagicTypeUltralightDirectWrite) { | ||||
|         return "Ultralight DirectWrite"; | ||||
|     } else if(type == MagicTypeUltralightC_Gen1) { | ||||
|         return "Ultralight-C Gen 1"; | ||||
|     } else if(type == MagicTypeUltralightC_DirectWrite) { | ||||
|         return "Ultralight-C DirectWrite"; | ||||
|     } else if(type == MagicTypeGen4) { | ||||
|         return "Gen 4 GTU"; | ||||
|     } else { | ||||
|         return "Unknown"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										5
									
								
								applications/external/nfc_magic/lib/magic/types.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "common.h" | ||||
| 
 | ||||
| const char* nfc_magic_type(MagicType type); | ||||
							
								
								
									
										22
									
								
								applications/external/nfc_magic/nfc_magic.c
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -48,8 +48,9 @@ NfcMagic* nfc_magic_alloc() { | ||||
|         nfc_magic->view_dispatcher, nfc_magic_tick_event_callback, 100); | ||||
| 
 | ||||
|     // Nfc device
 | ||||
|     nfc_magic->nfc_dev = nfc_device_alloc(); | ||||
|     furi_string_set(nfc_magic->nfc_dev->folder, NFC_APP_FOLDER); | ||||
|     nfc_magic->dev = malloc(sizeof(NfcMagicDevice)); | ||||
|     nfc_magic->source_dev = nfc_device_alloc(); | ||||
|     furi_string_set(nfc_magic->source_dev->folder, NFC_APP_FOLDER); | ||||
| 
 | ||||
|     // Open GUI record
 | ||||
|     nfc_magic->gui = furi_record_open(RECORD_GUI); | ||||
| @ -81,6 +82,13 @@ NfcMagic* nfc_magic_alloc() { | ||||
|         NfcMagicViewTextInput, | ||||
|         text_input_get_view(nfc_magic->text_input)); | ||||
| 
 | ||||
|     // Byte Input
 | ||||
|     nfc_magic->byte_input = byte_input_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         nfc_magic->view_dispatcher, | ||||
|         NfcMagicViewByteInput, | ||||
|         byte_input_get_view(nfc_magic->byte_input)); | ||||
| 
 | ||||
|     // Custom Widget
 | ||||
|     nfc_magic->widget = widget_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
| @ -93,7 +101,8 @@ void nfc_magic_free(NfcMagic* nfc_magic) { | ||||
|     furi_assert(nfc_magic); | ||||
| 
 | ||||
|     // Nfc device
 | ||||
|     nfc_device_free(nfc_magic->nfc_dev); | ||||
|     free(nfc_magic->dev); | ||||
|     nfc_device_free(nfc_magic->source_dev); | ||||
| 
 | ||||
|     // Submenu
 | ||||
|     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); | ||||
| @ -107,10 +116,14 @@ void nfc_magic_free(NfcMagic* nfc_magic) { | ||||
|     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewLoading); | ||||
|     loading_free(nfc_magic->loading); | ||||
| 
 | ||||
|     // TextInput
 | ||||
|     // Text Input
 | ||||
|     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewTextInput); | ||||
|     text_input_free(nfc_magic->text_input); | ||||
| 
 | ||||
|     // Byte Input
 | ||||
|     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput); | ||||
|     byte_input_free(nfc_magic->byte_input); | ||||
| 
 | ||||
|     // Custom Widget
 | ||||
|     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||
|     widget_free(nfc_magic->widget); | ||||
| @ -164,6 +177,7 @@ int32_t nfc_magic_app(void* p) { | ||||
| 
 | ||||
|     view_dispatcher_run(nfc_magic->view_dispatcher); | ||||
| 
 | ||||
|     magic_deactivate(); | ||||
|     nfc_magic_free(nfc_magic); | ||||
| 
 | ||||
|     return 0; | ||||
|  | ||||
							
								
								
									
										2
									
								
								applications/external/nfc_magic/nfc_magic.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| typedef struct NfcMagicDevice NfcMagicDevice; | ||||
| 
 | ||||
| typedef struct NfcMagic NfcMagic; | ||||
|  | ||||
							
								
								
									
										20
									
								
								applications/external/nfc_magic/nfc_magic_i.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -3,7 +3,10 @@ | ||||
| #include "nfc_magic.h" | ||||
| #include "nfc_magic_worker.h" | ||||
| 
 | ||||
| #include "lib/magic/magic.h" | ||||
| #include "lib/magic/common.h" | ||||
| #include "lib/magic/types.h" | ||||
| #include "lib/magic/classic_gen1.h" | ||||
| #include "lib/magic/gen4.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <gui/gui.h> | ||||
| @ -15,6 +18,7 @@ | ||||
| #include <gui/modules/popup.h> | ||||
| #include <gui/modules/loading.h> | ||||
| #include <gui/modules/text_input.h> | ||||
| #include <gui/modules/byte_input.h> | ||||
| #include <gui/modules/widget.h> | ||||
| 
 | ||||
| #include <input/input.h> | ||||
| @ -39,14 +43,22 @@ enum NfcMagicCustomEvent { | ||||
|     NfcMagicCustomEventTextInputDone, | ||||
| }; | ||||
| 
 | ||||
| struct NfcMagicDevice { | ||||
|     MagicType type; | ||||
|     uint32_t cuid; | ||||
|     uint32_t password; | ||||
| }; | ||||
| 
 | ||||
| struct NfcMagic { | ||||
|     NfcMagicWorker* worker; | ||||
|     ViewDispatcher* view_dispatcher; | ||||
|     Gui* gui; | ||||
|     NotificationApp* notifications; | ||||
|     SceneManager* scene_manager; | ||||
|     // NfcMagicDevice* dev;
 | ||||
|     NfcDevice* nfc_dev; | ||||
|     struct NfcMagicDevice* dev; | ||||
|     NfcDevice* source_dev; | ||||
| 
 | ||||
|     uint32_t new_password; | ||||
| 
 | ||||
|     FuriString* text_box_store; | ||||
| 
 | ||||
| @ -55,6 +67,7 @@ struct NfcMagic { | ||||
|     Popup* popup; | ||||
|     Loading* loading; | ||||
|     TextInput* text_input; | ||||
|     ByteInput* byte_input; | ||||
|     Widget* widget; | ||||
| }; | ||||
| 
 | ||||
| @ -63,6 +76,7 @@ typedef enum { | ||||
|     NfcMagicViewPopup, | ||||
|     NfcMagicViewLoading, | ||||
|     NfcMagicViewTextInput, | ||||
|     NfcMagicViewByteInput, | ||||
|     NfcMagicViewWidget, | ||||
| } NfcMagicView; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										350
									
								
								applications/external/nfc_magic/nfc_magic_worker.c
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,6 +1,9 @@ | ||||
| #include "nfc_magic_worker_i.h" | ||||
| 
 | ||||
| #include "lib/magic/magic.h" | ||||
| #include "nfc_magic_i.h" | ||||
| #include "lib/magic/common.h" | ||||
| #include "lib/magic/classic_gen1.h" | ||||
| #include "lib/magic/gen4.h" | ||||
| 
 | ||||
| #define TAG "NfcMagicWorker" | ||||
| 
 | ||||
| @ -43,15 +46,20 @@ void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker) { | ||||
| void nfc_magic_worker_start( | ||||
|     NfcMagicWorker* nfc_magic_worker, | ||||
|     NfcMagicWorkerState state, | ||||
|     NfcMagicDevice* magic_dev, | ||||
|     NfcDeviceData* dev_data, | ||||
|     uint32_t new_password, | ||||
|     NfcMagicWorkerCallback callback, | ||||
|     void* context) { | ||||
|     furi_assert(nfc_magic_worker); | ||||
|     furi_assert(magic_dev); | ||||
|     furi_assert(dev_data); | ||||
| 
 | ||||
|     nfc_magic_worker->callback = callback; | ||||
|     nfc_magic_worker->context = context; | ||||
|     nfc_magic_worker->magic_dev = magic_dev; | ||||
|     nfc_magic_worker->dev_data = dev_data; | ||||
|     nfc_magic_worker->new_password = new_password; | ||||
|     nfc_magic_worker_change_state(nfc_magic_worker, state); | ||||
|     furi_thread_start(nfc_magic_worker->thread); | ||||
| } | ||||
| @ -63,6 +71,8 @@ int32_t nfc_magic_worker_task(void* context) { | ||||
|         nfc_magic_worker_check(nfc_magic_worker); | ||||
|     } else if(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { | ||||
|         nfc_magic_worker_write(nfc_magic_worker); | ||||
|     } else if(nfc_magic_worker->state == NfcMagicWorkerStateRekey) { | ||||
|         nfc_magic_worker_rekey(nfc_magic_worker); | ||||
|     } else if(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { | ||||
|         nfc_magic_worker_wipe(nfc_magic_worker); | ||||
|     } | ||||
| @ -74,59 +84,244 @@ int32_t nfc_magic_worker_task(void* context) { | ||||
| 
 | ||||
| void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { | ||||
|     bool card_found_notified = false; | ||||
|     bool done = false; | ||||
|     FuriHalNfcDevData nfc_data = {}; | ||||
|     MfClassicData* src_data = &nfc_magic_worker->dev_data->mf_classic_data; | ||||
|     NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; | ||||
|     NfcDeviceData* dev_data = nfc_magic_worker->dev_data; | ||||
|     NfcProtocol dev_protocol = dev_data->protocol; | ||||
| 
 | ||||
|     while(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { | ||||
|         do { | ||||
|             if(magic_dev->type == MagicTypeClassicGen1) { | ||||
|                 if(furi_hal_nfc_detect(&nfc_data, 200)) { | ||||
|             if(!card_found_notified) { | ||||
|                 nfc_magic_worker->callback( | ||||
|                     NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); | ||||
|                 card_found_notified = true; | ||||
|             } | ||||
|             furi_hal_nfc_sleep(); | ||||
|             if(!magic_wupa()) { | ||||
|                     magic_deactivate(); | ||||
|                     magic_activate(); | ||||
|                     if(!magic_gen1_wupa()) { | ||||
|                         FURI_LOG_E(TAG, "No card response to WUPA (not a magic card)"); | ||||
|                         nfc_magic_worker->callback( | ||||
|                             NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); | ||||
|                         done = true; | ||||
|                         break; | ||||
|                     } | ||||
|             furi_hal_nfc_sleep(); | ||||
|                     magic_deactivate(); | ||||
|                 } | ||||
|         if(magic_wupa()) { | ||||
|             if(!magic_data_access_cmd()) { | ||||
|                 FURI_LOG_E(TAG, "No card response to data access command (not a magic card)"); | ||||
|                 magic_activate(); | ||||
|                 if(magic_gen1_wupa()) { | ||||
|                     if(!magic_gen1_data_access_cmd()) { | ||||
|                         FURI_LOG_E( | ||||
|                             TAG, "No card response to data access command (not a magic card)"); | ||||
|                         nfc_magic_worker->callback( | ||||
|                             NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); | ||||
|                         done = true; | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     MfClassicData* mfc_data = &dev_data->mf_classic_data; | ||||
|                     for(size_t i = 0; i < 64; i++) { | ||||
|                         FURI_LOG_D(TAG, "Writing block %d", i); | ||||
|                 if(!magic_write_blk(i, &src_data->block[i])) { | ||||
|                         if(!magic_gen1_write_blk(i, &mfc_data->block[i])) { | ||||
|                             FURI_LOG_E(TAG, "Failed to write %d block", i); | ||||
|                     nfc_magic_worker->callback(NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||
|                             done = true; | ||||
|                             nfc_magic_worker->callback( | ||||
|                                 NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|             nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||
| 
 | ||||
|                     done = true; | ||||
|                     nfc_magic_worker->callback( | ||||
|                         NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||
|                     break; | ||||
|                 } | ||||
|             } else if(magic_dev->type == MagicTypeGen4) { | ||||
|                 if(furi_hal_nfc_detect(&nfc_data, 200)) { | ||||
|                     uint8_t gen4_config[28]; | ||||
|                     uint32_t password = magic_dev->password; | ||||
| 
 | ||||
|                     uint32_t cuid; | ||||
|                     if(dev_protocol == NfcDeviceProtocolMifareClassic) { | ||||
|                         gen4_config[0] = 0x00; | ||||
|                         gen4_config[27] = 0x00; | ||||
|                     } else if(dev_protocol == NfcDeviceProtocolMifareUl) { | ||||
|                         MfUltralightData* mf_ul_data = &dev_data->mf_ul_data; | ||||
|                         gen4_config[0] = 0x01; | ||||
|                         switch(mf_ul_data->type) { | ||||
|                         case MfUltralightTypeUL11: | ||||
|                         case MfUltralightTypeUL21: | ||||
|                         // UL-C?
 | ||||
|                         // UL?
 | ||||
|                         default: | ||||
|                             gen4_config[27] = MagicGen4UltralightModeUL_EV1; | ||||
|                             break; | ||||
|                         case MfUltralightTypeNTAG203: | ||||
|                         case MfUltralightTypeNTAG213: | ||||
|                         case MfUltralightTypeNTAG215: | ||||
|                         case MfUltralightTypeNTAG216: | ||||
|                         case MfUltralightTypeNTAGI2C1K: | ||||
|                         case MfUltralightTypeNTAGI2C2K: | ||||
|                         case MfUltralightTypeNTAGI2CPlus1K: | ||||
|                         case MfUltralightTypeNTAGI2CPlus2K: | ||||
|                             gen4_config[27] = MagicGen4UltralightModeNTAG; | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     if(dev_data->nfc_data.uid_len == 4) { | ||||
|                         gen4_config[1] = MagicGen4UIDLengthSingle; | ||||
|                     } else if(dev_data->nfc_data.uid_len == 7) { | ||||
|                         gen4_config[1] = MagicGen4UIDLengthDouble; | ||||
|                     } else { | ||||
|                         FURI_LOG_E(TAG, "Unexpected UID length %d", dev_data->nfc_data.uid_len); | ||||
|                         nfc_magic_worker->callback( | ||||
|                             NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||
|                         done = true; | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     gen4_config[2] = (uint8_t)(password >> 24); | ||||
|                     gen4_config[3] = (uint8_t)(password >> 16); | ||||
|                     gen4_config[4] = (uint8_t)(password >> 8); | ||||
|                     gen4_config[5] = (uint8_t)password; | ||||
| 
 | ||||
|                     if(dev_protocol == NfcDeviceProtocolMifareUl) { | ||||
|                         gen4_config[6] = MagicGen4ShadowModeHighSpeedIgnore; | ||||
|                     } else { | ||||
|                         gen4_config[6] = MagicGen4ShadowModeIgnore; | ||||
|                     } | ||||
|                     gen4_config[7] = 0x00; | ||||
|                     memset(gen4_config + 8, 0, 16); | ||||
|                     gen4_config[24] = dev_data->nfc_data.atqa[0]; | ||||
|                     gen4_config[25] = dev_data->nfc_data.atqa[1]; | ||||
|                     gen4_config[26] = dev_data->nfc_data.sak; | ||||
| 
 | ||||
|                     furi_hal_nfc_sleep(); | ||||
|                     furi_hal_nfc_activate_nfca(200, &cuid); | ||||
|                     if(!magic_gen4_set_cfg(password, gen4_config, sizeof(gen4_config), false)) { | ||||
|                         nfc_magic_worker->callback( | ||||
|                             NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||
|                         done = true; | ||||
|                         break; | ||||
|                     } | ||||
|                     if(dev_protocol == NfcDeviceProtocolMifareClassic) { | ||||
|                         MfClassicData* mfc_data = &dev_data->mf_classic_data; | ||||
|                         size_t block_count = 64; | ||||
|                         if(mfc_data->type == MfClassicType4k) block_count = 256; | ||||
|                         for(size_t i = 0; i < block_count; i++) { | ||||
|                             FURI_LOG_D(TAG, "Writing block %d", i); | ||||
|                             if(!magic_gen4_write_blk(password, i, mfc_data->block[i].value)) { | ||||
|                                 FURI_LOG_E(TAG, "Failed to write %d block", i); | ||||
|                                 nfc_magic_worker->callback( | ||||
|                                     NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||
|                                 done = true; | ||||
|                                 break; | ||||
|                             } | ||||
|                         } | ||||
|                     } else if(dev_protocol == NfcDeviceProtocolMifareUl) { | ||||
|                         MfUltralightData* mf_ul_data = &dev_data->mf_ul_data; | ||||
|                         for(size_t i = 0; (i * 4) < mf_ul_data->data_read; i++) { | ||||
|                             size_t data_offset = i * 4; | ||||
|                             FURI_LOG_D( | ||||
|                                 TAG, | ||||
|                                 "Writing page %zu (%zu/%u)", | ||||
|                                 i, | ||||
|                                 data_offset, | ||||
|                                 mf_ul_data->data_read); | ||||
|                             uint8_t* block = mf_ul_data->data + data_offset; | ||||
|                             if(!magic_gen4_write_blk(password, i, block)) { | ||||
|                                 FURI_LOG_E(TAG, "Failed to write %zu page", i); | ||||
|                                 nfc_magic_worker->callback( | ||||
|                                     NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||
|                                 done = true; | ||||
|                                 break; | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
|                         uint8_t buffer[16] = {0}; | ||||
| 
 | ||||
|                         for(size_t i = 0; i < 8; i++) { | ||||
|                             memcpy(buffer, &mf_ul_data->signature[i * 4], 4); //-V1086
 | ||||
|                             if(!magic_gen4_write_blk(password, 0xF2 + i, buffer)) { | ||||
|                                 FURI_LOG_E(TAG, "Failed to write signature block %d", i); | ||||
|                                 nfc_magic_worker->callback( | ||||
|                                     NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||
|                                 done = true; | ||||
|                                 break; | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
|                         buffer[0] = mf_ul_data->version.header; | ||||
|                         buffer[1] = mf_ul_data->version.vendor_id; | ||||
|                         buffer[2] = mf_ul_data->version.prod_type; | ||||
|                         buffer[3] = mf_ul_data->version.prod_subtype; | ||||
|                         if(!magic_gen4_write_blk(password, 0xFA, buffer)) { | ||||
|                             FURI_LOG_E(TAG, "Failed to write version block 0"); | ||||
|                             nfc_magic_worker->callback( | ||||
|                                 NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||
|                             done = true; | ||||
|                             break; | ||||
|                         } | ||||
| 
 | ||||
|                         buffer[0] = mf_ul_data->version.prod_ver_major; | ||||
|                         buffer[1] = mf_ul_data->version.prod_ver_minor; | ||||
|                         buffer[2] = mf_ul_data->version.storage_size; | ||||
|                         buffer[3] = mf_ul_data->version.protocol_type; | ||||
|                         if(!magic_gen4_write_blk(password, 0xFB, buffer)) { | ||||
|                             FURI_LOG_E(TAG, "Failed to write version block 1"); | ||||
|                             nfc_magic_worker->callback( | ||||
|                                 NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||
|                             done = true; | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     nfc_magic_worker->callback( | ||||
|                         NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||
|                     done = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } while(false); | ||||
| 
 | ||||
|         if(done) break; | ||||
| 
 | ||||
|         if(card_found_notified) { | ||||
|             nfc_magic_worker->callback( | ||||
|                 NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); | ||||
|             card_found_notified = false; | ||||
|         } | ||||
|         } | ||||
| 
 | ||||
|         furi_delay_ms(300); | ||||
|     } | ||||
|     magic_deactivate(); | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { | ||||
|     NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; | ||||
|     bool card_found_notified = false; | ||||
|     uint8_t gen4_config[MAGIC_GEN4_CONFIG_LEN]; | ||||
| 
 | ||||
|     while(nfc_magic_worker->state == NfcMagicWorkerStateCheck) { | ||||
|         if(magic_wupa()) { | ||||
|         magic_activate(); | ||||
|         if(magic_gen1_wupa()) { | ||||
|             magic_dev->type = MagicTypeClassicGen1; | ||||
|             if(!card_found_notified) { | ||||
|                 nfc_magic_worker->callback( | ||||
|                     NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); | ||||
|                 card_found_notified = true; | ||||
|             } | ||||
| 
 | ||||
|             furi_hal_nfc_activate_nfca(200, &magic_dev->cuid); | ||||
|             nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         magic_deactivate(); | ||||
|         furi_delay_ms(300); | ||||
|         magic_activate(); | ||||
| 
 | ||||
|         furi_hal_nfc_activate_nfca(200, &magic_dev->cuid); | ||||
|         if(magic_gen4_get_cfg(magic_dev->password, gen4_config)) { | ||||
|             magic_dev->type = MagicTypeGen4; | ||||
|             if(!card_found_notified) { | ||||
|                 nfc_magic_worker->callback( | ||||
|                     NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); | ||||
| @ -135,12 +330,56 @@ void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { | ||||
| 
 | ||||
|             nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||
|             break; | ||||
|         } else { | ||||
|         } | ||||
| 
 | ||||
|         if(card_found_notified) { | ||||
|             nfc_magic_worker->callback( | ||||
|                 NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); | ||||
|             card_found_notified = false; | ||||
|         } | ||||
| 
 | ||||
|         magic_deactivate(); | ||||
|         furi_delay_ms(300); | ||||
|     } | ||||
| 
 | ||||
|     magic_deactivate(); | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_worker_rekey(NfcMagicWorker* nfc_magic_worker) { | ||||
|     NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; | ||||
|     bool card_found_notified = false; | ||||
| 
 | ||||
|     if(magic_dev->type != MagicTypeGen4) { | ||||
|         nfc_magic_worker->callback(NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     while(nfc_magic_worker->state == NfcMagicWorkerStateRekey) { | ||||
|         magic_activate(); | ||||
|         uint32_t cuid; | ||||
|         furi_hal_nfc_activate_nfca(200, &cuid); | ||||
|         if(cuid != magic_dev->cuid) { | ||||
|             if(card_found_notified) { | ||||
|                 nfc_magic_worker->callback( | ||||
|                     NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); | ||||
|                 card_found_notified = false; | ||||
|             } | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         nfc_magic_worker->callback(NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); | ||||
|         card_found_notified = true; | ||||
| 
 | ||||
|         if(magic_gen4_set_pwd(magic_dev->password, nfc_magic_worker->new_password)) { | ||||
|             magic_dev->password = nfc_magic_worker->new_password; | ||||
|             nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(card_found_notified) { //-V547
 | ||||
|             nfc_magic_worker->callback( | ||||
|                 NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); | ||||
|             card_found_notified = false; | ||||
|         } | ||||
|         furi_delay_ms(300); | ||||
|     } | ||||
| @ -148,8 +387,17 @@ void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { | ||||
|     NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; | ||||
|     bool card_found_notified = false; | ||||
|     bool card_wiped = false; | ||||
| 
 | ||||
|     MfClassicBlock block; | ||||
|     memset(&block, 0, sizeof(MfClassicBlock)); | ||||
|     MfClassicBlock empty_block; | ||||
|     memset(&empty_block, 0, sizeof(MfClassicBlock)); | ||||
|     MfClassicBlock trailer_block; | ||||
|     memset(&trailer_block, 0xff, sizeof(MfClassicBlock)); | ||||
| 
 | ||||
|     block.value[0] = 0x01; | ||||
|     block.value[1] = 0x02; | ||||
|     block.value[2] = 0x03; | ||||
| @ -158,15 +406,69 @@ void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { | ||||
|     block.value[5] = 0x08; | ||||
|     block.value[6] = 0x04; | ||||
| 
 | ||||
|     trailer_block.value[7] = 0x07; | ||||
|     trailer_block.value[8] = 0x80; | ||||
|     trailer_block.value[9] = 0x69; | ||||
| 
 | ||||
|     while(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { | ||||
|         do { | ||||
|             magic_deactivate(); | ||||
|             furi_delay_ms(300); | ||||
|         if(!magic_wupa()) continue; | ||||
|         if(!magic_wipe()) continue; | ||||
|         if(!magic_data_access_cmd()) continue; | ||||
|         if(!magic_write_blk(0, &block)) continue; | ||||
|         nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||
|             if(!magic_activate()) break; | ||||
|             if(magic_dev->type == MagicTypeClassicGen1) { | ||||
|                 if(!magic_gen1_wupa()) break; | ||||
|                 if(!card_found_notified) { | ||||
|                     nfc_magic_worker->callback( | ||||
|                         NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); | ||||
|                     card_found_notified = true; | ||||
|                 } | ||||
| 
 | ||||
|                 if(!magic_gen1_data_access_cmd()) break; | ||||
|                 if(!magic_gen1_write_blk(0, &block)) break; | ||||
| 
 | ||||
|                 for(size_t i = 1; i < 64; i++) { | ||||
|                     FURI_LOG_D(TAG, "Wiping block %d", i); | ||||
|                     bool success = false; | ||||
|                     if((i | 0x03) == i) { | ||||
|                         success = magic_gen1_write_blk(i, &trailer_block); | ||||
|                     } else { | ||||
|                         success = magic_gen1_write_blk(i, &empty_block); | ||||
|                     } | ||||
| 
 | ||||
|                     if(!success) { | ||||
|                         FURI_LOG_E(TAG, "Failed to write %d block", i); | ||||
|                         nfc_magic_worker->callback( | ||||
|                             NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 card_wiped = true; | ||||
|                 nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||
|             } else if(magic_dev->type == MagicTypeGen4) { | ||||
|                 uint32_t cuid; | ||||
|                 if(!furi_hal_nfc_activate_nfca(200, &cuid)) break; | ||||
|                 if(cuid != magic_dev->cuid) break; | ||||
|                 if(!card_found_notified) { | ||||
|                     nfc_magic_worker->callback( | ||||
|                         NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); | ||||
|                     card_found_notified = true; | ||||
|                 } | ||||
| 
 | ||||
|                 if(!magic_gen4_wipe(magic_dev->password)) break; | ||||
| 
 | ||||
|                 card_wiped = true; | ||||
|                 nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||
|             } | ||||
|         } while(false); | ||||
| 
 | ||||
|         if(card_wiped) break; | ||||
| 
 | ||||
|         if(card_found_notified) { | ||||
|             nfc_magic_worker->callback( | ||||
|                 NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); | ||||
|             card_found_notified = false; | ||||
|         } | ||||
|     } | ||||
|     magic_deactivate(); | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <lib/nfc/nfc_device.h> | ||||
| #include "nfc_magic.h" | ||||
| 
 | ||||
| typedef struct NfcMagicWorker NfcMagicWorker; | ||||
| 
 | ||||
| @ -9,6 +10,7 @@ typedef enum { | ||||
| 
 | ||||
|     NfcMagicWorkerStateCheck, | ||||
|     NfcMagicWorkerStateWrite, | ||||
|     NfcMagicWorkerStateRekey, | ||||
|     NfcMagicWorkerStateWipe, | ||||
| 
 | ||||
|     NfcMagicWorkerStateStop, | ||||
| @ -33,6 +35,8 @@ void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker); | ||||
| void nfc_magic_worker_start( | ||||
|     NfcMagicWorker* nfc_magic_worker, | ||||
|     NfcMagicWorkerState state, | ||||
|     NfcMagicDevice* magic_dev, | ||||
|     NfcDeviceData* dev_data, | ||||
|     uint32_t new_password, | ||||
|     NfcMagicWorkerCallback callback, | ||||
|     void* context); | ||||
|  | ||||
| @ -3,11 +3,14 @@ | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #include "nfc_magic_worker.h" | ||||
| #include "lib/magic/common.h" | ||||
| 
 | ||||
| struct NfcMagicWorker { | ||||
|     FuriThread* thread; | ||||
| 
 | ||||
|     NfcMagicDevice* magic_dev; | ||||
|     NfcDeviceData* dev_data; | ||||
|     uint32_t new_password; | ||||
| 
 | ||||
|     NfcMagicWorkerCallback callback; | ||||
|     void* context; | ||||
| @ -21,4 +24,6 @@ void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker); | ||||
| 
 | ||||
| void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker); | ||||
| 
 | ||||
| void nfc_magic_worker_rekey(NfcMagicWorker* nfc_magic_worker); | ||||
| 
 | ||||
| void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker); | ||||
|  | ||||
							
								
								
									
										50
									
								
								applications/external/nfc_magic/scenes/nfc_magic_scene_actions.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,50 @@ | ||||
| #include "../nfc_magic_i.h" | ||||
| enum SubmenuIndex { | ||||
|     SubmenuIndexWrite, | ||||
|     SubmenuIndexWipe, | ||||
| }; | ||||
| 
 | ||||
| void nfc_magic_scene_actions_submenu_callback(void* context, uint32_t index) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index); | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_actions_on_enter(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     Submenu* submenu = nfc_magic->submenu; | ||||
|     submenu_add_item( | ||||
|         submenu, "Write", SubmenuIndexWrite, nfc_magic_scene_actions_submenu_callback, nfc_magic); | ||||
|     submenu_add_item( | ||||
|         submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_actions_submenu_callback, nfc_magic); | ||||
| 
 | ||||
|     submenu_set_selected_item( | ||||
|         submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneActions)); | ||||
|     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); | ||||
| } | ||||
| 
 | ||||
| bool nfc_magic_scene_actions_on_event(void* context, SceneManagerEvent event) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SubmenuIndexWrite) { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexWipe) { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); | ||||
|             consumed = true; | ||||
|         } | ||||
|         scene_manager_set_scene_state(nfc_magic->scene_manager, NfcMagicSceneActions, event.event); | ||||
|     } else if(event.type == SceneManagerEventTypeBack) { | ||||
|         consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|             nfc_magic->scene_manager, NfcMagicSceneStart); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_actions_on_exit(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     submenu_reset(nfc_magic->submenu); | ||||
| } | ||||
| @ -42,7 +42,9 @@ void nfc_magic_scene_check_on_enter(void* context) { | ||||
|     nfc_magic_worker_start( | ||||
|         nfc_magic->worker, | ||||
|         NfcMagicWorkerStateCheck, | ||||
|         &nfc_magic->nfc_dev->dev_data, | ||||
|         nfc_magic->dev, | ||||
|         &nfc_magic->source_dev->dev_data, | ||||
|         nfc_magic->new_password, | ||||
|         nfc_magic_check_worker_callback, | ||||
|         nfc_magic); | ||||
|     nfc_magic_blink_start(nfc_magic); | ||||
|  | ||||
| @ -1,4 +1,8 @@ | ||||
| ADD_SCENE(nfc_magic, start, Start) | ||||
| ADD_SCENE(nfc_magic, key_input, KeyInput) | ||||
| ADD_SCENE(nfc_magic, actions, Actions) | ||||
| ADD_SCENE(nfc_magic, gen4_actions, Gen4Actions) | ||||
| ADD_SCENE(nfc_magic, new_key_input, NewKeyInput) | ||||
| ADD_SCENE(nfc_magic, file_select, FileSelect) | ||||
| ADD_SCENE(nfc_magic, write_confirm, WriteConfirm) | ||||
| ADD_SCENE(nfc_magic, wrong_card, WrongCard) | ||||
| @ -8,5 +12,7 @@ ADD_SCENE(nfc_magic, success, Success) | ||||
| ADD_SCENE(nfc_magic, check, Check) | ||||
| ADD_SCENE(nfc_magic, not_magic, NotMagic) | ||||
| ADD_SCENE(nfc_magic, magic_info, MagicInfo) | ||||
| ADD_SCENE(nfc_magic, rekey, Rekey) | ||||
| ADD_SCENE(nfc_magic, rekey_fail, RekeyFail) | ||||
| ADD_SCENE(nfc_magic, wipe, Wipe) | ||||
| ADD_SCENE(nfc_magic, wipe_fail, WipeFail) | ||||
|  | ||||
| @ -1,22 +1,60 @@ | ||||
| #include "../nfc_magic_i.h" | ||||
| 
 | ||||
| static bool nfc_magic_scene_file_select_is_file_suitable(NfcDevice* nfc_dev) { | ||||
|     return (nfc_dev->format == NfcDeviceSaveFormatMifareClassic) && | ||||
|            (nfc_dev->dev_data.mf_classic_data.type == MfClassicType1k) && | ||||
|            (nfc_dev->dev_data.nfc_data.uid_len == 4); | ||||
| static bool nfc_magic_scene_file_select_is_file_suitable(NfcMagic* nfc_magic) { | ||||
|     NfcDevice* nfc_dev = nfc_magic->source_dev; | ||||
|     if(nfc_dev->format == NfcDeviceSaveFormatMifareClassic) { | ||||
|         switch(nfc_magic->dev->type) { | ||||
|         case MagicTypeClassicGen1: | ||||
|         case MagicTypeClassicDirectWrite: | ||||
|         case MagicTypeClassicAPDU: | ||||
|             if((nfc_dev->dev_data.mf_classic_data.type != MfClassicType1k) || | ||||
|                (nfc_dev->dev_data.nfc_data.uid_len != 4)) { | ||||
|                 return false; | ||||
|             } | ||||
|             return true; | ||||
| 
 | ||||
|         case MagicTypeGen4: | ||||
|             return true; | ||||
|         default: | ||||
|             return false; | ||||
|         } | ||||
|     } else if( | ||||
|         (nfc_dev->format == NfcDeviceSaveFormatMifareUl) && | ||||
|         (nfc_dev->dev_data.nfc_data.uid_len == 7)) { | ||||
|         switch(nfc_magic->dev->type) { | ||||
|         case MagicTypeUltralightGen1: | ||||
|         case MagicTypeUltralightDirectWrite: | ||||
|         case MagicTypeUltralightC_Gen1: | ||||
|         case MagicTypeUltralightC_DirectWrite: | ||||
|         case MagicTypeGen4: | ||||
|             switch(nfc_dev->dev_data.mf_ul_data.type) { | ||||
|             case MfUltralightTypeNTAGI2C1K: | ||||
|             case MfUltralightTypeNTAGI2C2K: | ||||
|             case MfUltralightTypeNTAGI2CPlus1K: | ||||
|             case MfUltralightTypeNTAGI2CPlus2K: | ||||
|                 return false; | ||||
|             default: | ||||
|                 return true; | ||||
|             } | ||||
|         default: | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_file_select_on_enter(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     // Process file_select return
 | ||||
|     nfc_device_set_loading_callback(nfc_magic->nfc_dev, nfc_magic_show_loading_popup, nfc_magic); | ||||
|     nfc_device_set_loading_callback( | ||||
|         nfc_magic->source_dev, nfc_magic_show_loading_popup, nfc_magic); | ||||
| 
 | ||||
|     if(!furi_string_size(nfc_magic->nfc_dev->load_path)) { | ||||
|         furi_string_set_str(nfc_magic->nfc_dev->load_path, NFC_APP_FOLDER); | ||||
|     if(!furi_string_size(nfc_magic->source_dev->load_path)) { | ||||
|         furi_string_set_str(nfc_magic->source_dev->load_path, NFC_APP_FOLDER); | ||||
|     } | ||||
| 
 | ||||
|     if(nfc_file_select(nfc_magic->nfc_dev)) { | ||||
|         if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic->nfc_dev)) { | ||||
|     if(nfc_file_select(nfc_magic->source_dev)) { | ||||
|         if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic)) { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm); | ||||
|         } else { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrongCard); | ||||
| @ -34,5 +72,5 @@ bool nfc_magic_scene_file_select_on_event(void* context, SceneManagerEvent event | ||||
| 
 | ||||
| void nfc_magic_scene_file_select_on_exit(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     nfc_device_set_loading_callback(nfc_magic->nfc_dev, NULL, nfc_magic); | ||||
|     nfc_device_set_loading_callback(nfc_magic->source_dev, NULL, nfc_magic); | ||||
| } | ||||
|  | ||||
							
								
								
									
										70
									
								
								applications/external/nfc_magic/scenes/nfc_magic_scene_gen4_actions.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,70 @@ | ||||
| #include "../nfc_magic_i.h" | ||||
| enum SubmenuIndex { | ||||
|     SubmenuIndexWrite, | ||||
|     SubmenuIndexChangePassword, | ||||
|     SubmenuIndexWipe, | ||||
| }; | ||||
| 
 | ||||
| void nfc_magic_scene_gen4_actions_submenu_callback(void* context, uint32_t index) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index); | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_gen4_actions_on_enter(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     Submenu* submenu = nfc_magic->submenu; | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Write", | ||||
|         SubmenuIndexWrite, | ||||
|         nfc_magic_scene_gen4_actions_submenu_callback, | ||||
|         nfc_magic); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Change password", | ||||
|         SubmenuIndexChangePassword, | ||||
|         nfc_magic_scene_gen4_actions_submenu_callback, | ||||
|         nfc_magic); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Wipe", | ||||
|         SubmenuIndexWipe, | ||||
|         nfc_magic_scene_gen4_actions_submenu_callback, | ||||
|         nfc_magic); | ||||
| 
 | ||||
|     submenu_set_selected_item( | ||||
|         submenu, | ||||
|         scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneGen4Actions)); | ||||
|     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); | ||||
| } | ||||
| 
 | ||||
| bool nfc_magic_scene_gen4_actions_on_event(void* context, SceneManagerEvent event) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SubmenuIndexWrite) { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexChangePassword) { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNewKeyInput); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexWipe) { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); | ||||
|             consumed = true; | ||||
|         } | ||||
|         scene_manager_set_scene_state( | ||||
|             nfc_magic->scene_manager, NfcMagicSceneGen4Actions, event.event); | ||||
|     } else if(event.type == SceneManagerEventTypeBack) { | ||||
|         consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|             nfc_magic->scene_manager, NfcMagicSceneStart); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_gen4_actions_on_exit(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     submenu_reset(nfc_magic->submenu); | ||||
| } | ||||
							
								
								
									
										45
									
								
								applications/external/nfc_magic/scenes/nfc_magic_scene_key_input.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,45 @@ | ||||
| #include "../nfc_magic_i.h" | ||||
| 
 | ||||
| void nfc_magic_scene_key_input_byte_input_callback(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event( | ||||
|         nfc_magic->view_dispatcher, NfcMagicCustomEventByteInputDone); | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_key_input_on_enter(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     ByteInput* byte_input = nfc_magic->byte_input; | ||||
|     byte_input_set_header_text(byte_input, "Enter the password in hex"); | ||||
|     byte_input_set_result_callback( | ||||
|         byte_input, | ||||
|         nfc_magic_scene_key_input_byte_input_callback, | ||||
|         NULL, | ||||
|         nfc_magic, | ||||
|         (uint8_t*)&nfc_magic->dev->password, | ||||
|         4); | ||||
|     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput); | ||||
| } | ||||
| 
 | ||||
| bool nfc_magic_scene_key_input_on_event(void* context, SceneManagerEvent event) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == NfcMagicCustomEventByteInputDone) { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_key_input_on_exit(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     // Clear view
 | ||||
|     byte_input_set_result_callback(nfc_magic->byte_input, NULL, NULL, NULL, NULL, 0); | ||||
|     byte_input_set_header_text(nfc_magic->byte_input, ""); | ||||
| } | ||||
| @ -1,4 +1,5 @@ | ||||
| #include "../nfc_magic_i.h" | ||||
| #include "../lib/magic/types.h" | ||||
| 
 | ||||
| void nfc_magic_scene_magic_info_widget_callback( | ||||
|     GuiButtonType result, | ||||
| @ -13,14 +14,18 @@ void nfc_magic_scene_magic_info_widget_callback( | ||||
| void nfc_magic_scene_magic_info_on_enter(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     Widget* widget = nfc_magic->widget; | ||||
|     const char* card_type = nfc_magic_type(nfc_magic->dev->type); | ||||
| 
 | ||||
|     notification_message(nfc_magic->notifications, &sequence_success); | ||||
| 
 | ||||
|     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); | ||||
|     widget_add_string_element( | ||||
|         widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected"); | ||||
|     widget_add_string_element(widget, 3, 17, AlignLeft, AlignTop, FontSecondary, card_type); | ||||
|     widget_add_button_element( | ||||
|         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, nfc_magic); | ||||
|     widget_add_button_element( | ||||
|         widget, GuiButtonTypeRight, "More", nfc_magic_scene_magic_info_widget_callback, nfc_magic); | ||||
| 
 | ||||
|     // Setup and start worker
 | ||||
|     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||
| @ -33,6 +38,15 @@ bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event) | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == GuiButtonTypeLeft) { | ||||
|             consumed = scene_manager_previous_scene(nfc_magic->scene_manager); | ||||
|         } else if(event.event == GuiButtonTypeRight) { | ||||
|             MagicType type = nfc_magic->dev->type; | ||||
|             if(type == MagicTypeGen4) { | ||||
|                 scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneGen4Actions); | ||||
|                 consumed = true; | ||||
|             } else { | ||||
|                 scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneActions); | ||||
|                 consumed = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
|  | ||||
							
								
								
									
										45
									
								
								applications/external/nfc_magic/scenes/nfc_magic_scene_new_key_input.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,45 @@ | ||||
| #include "../nfc_magic_i.h" | ||||
| 
 | ||||
| void nfc_magic_scene_new_key_input_byte_input_callback(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event( | ||||
|         nfc_magic->view_dispatcher, NfcMagicCustomEventByteInputDone); | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_new_key_input_on_enter(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     ByteInput* byte_input = nfc_magic->byte_input; | ||||
|     byte_input_set_header_text(byte_input, "Enter the password in hex"); | ||||
|     byte_input_set_result_callback( | ||||
|         byte_input, | ||||
|         nfc_magic_scene_new_key_input_byte_input_callback, | ||||
|         NULL, | ||||
|         nfc_magic, | ||||
|         (uint8_t*)&nfc_magic->new_password, | ||||
|         4); | ||||
|     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput); | ||||
| } | ||||
| 
 | ||||
| bool nfc_magic_scene_new_key_input_on_event(void* context, SceneManagerEvent event) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == NfcMagicCustomEventByteInputDone) { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneRekey); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_new_key_input_on_exit(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     // Clear view
 | ||||
|     byte_input_set_result_callback(nfc_magic->byte_input, NULL, NULL, NULL, NULL, 0); | ||||
|     byte_input_set_header_text(nfc_magic->byte_input, ""); | ||||
| } | ||||
							
								
								
									
										95
									
								
								applications/external/nfc_magic/scenes/nfc_magic_scene_rekey.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,95 @@ | ||||
| #include "../nfc_magic_i.h" | ||||
| 
 | ||||
| enum { | ||||
|     NfcMagicSceneRekeyStateCardSearch, | ||||
|     NfcMagicSceneRekeyStateCardFound, | ||||
| }; | ||||
| 
 | ||||
| bool nfc_magic_rekey_worker_callback(NfcMagicWorkerEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static void nfc_magic_scene_rekey_setup_view(NfcMagic* nfc_magic) { | ||||
|     Popup* popup = nfc_magic->popup; | ||||
|     popup_reset(popup); | ||||
|     uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneRekey); | ||||
| 
 | ||||
|     if(state == NfcMagicSceneRekeyStateCardSearch) { | ||||
|         popup_set_text( | ||||
|             nfc_magic->popup, | ||||
|             "Apply the\nsame card\nto the back", | ||||
|             128, | ||||
|             32, | ||||
|             AlignRight, | ||||
|             AlignCenter); | ||||
|         popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); | ||||
|     } else { | ||||
|         popup_set_icon(popup, 12, 23, &I_Loading_24); | ||||
|         popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_rekey_on_enter(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     scene_manager_set_scene_state( | ||||
|         nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch); | ||||
|     nfc_magic_scene_rekey_setup_view(nfc_magic); | ||||
| 
 | ||||
|     // Setup and start worker
 | ||||
|     nfc_magic_worker_start( | ||||
|         nfc_magic->worker, | ||||
|         NfcMagicWorkerStateRekey, | ||||
|         nfc_magic->dev, | ||||
|         &nfc_magic->source_dev->dev_data, | ||||
|         nfc_magic->new_password, | ||||
|         nfc_magic_rekey_worker_callback, | ||||
|         nfc_magic); | ||||
|     nfc_magic_blink_start(nfc_magic); | ||||
| } | ||||
| 
 | ||||
| bool nfc_magic_scene_rekey_on_event(void* context, SceneManagerEvent event) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == NfcMagicWorkerEventSuccess) { | ||||
|             nfc_magic->dev->password = nfc_magic->new_password; | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess); | ||||
|             consumed = true; | ||||
|         } else if(event.event == NfcMagicWorkerEventFail) { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneRekeyFail); | ||||
|             consumed = true; | ||||
|         } else if(event.event == NfcMagicWorkerEventCardDetected) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardFound); | ||||
|             nfc_magic_scene_rekey_setup_view(nfc_magic); | ||||
|             consumed = true; | ||||
|         } else if(event.event == NfcMagicWorkerEventNoCardDetected) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch); | ||||
|             nfc_magic_scene_rekey_setup_view(nfc_magic); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_rekey_on_exit(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     nfc_magic_worker_stop(nfc_magic->worker); | ||||
|     scene_manager_set_scene_state( | ||||
|         nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch); | ||||
|     // Clear view
 | ||||
|     popup_reset(nfc_magic->popup); | ||||
| 
 | ||||
|     nfc_magic_blink_stop(nfc_magic); | ||||
| } | ||||
							
								
								
									
										50
									
								
								applications/external/nfc_magic/scenes/nfc_magic_scene_rekey_fail.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,50 @@ | ||||
| #include "../nfc_magic_i.h" | ||||
| 
 | ||||
| void nfc_magic_scene_rekey_fail_widget_callback( | ||||
|     GuiButtonType result, | ||||
|     InputType type, | ||||
|     void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     if(type == InputTypeShort) { | ||||
|         view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_rekey_fail_on_enter(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     Widget* widget = nfc_magic->widget; | ||||
| 
 | ||||
|     notification_message(nfc_magic->notifications, &sequence_error); | ||||
| 
 | ||||
|     widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); | ||||
|     widget_add_string_element( | ||||
|         widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Can't change password!"); | ||||
| 
 | ||||
|     widget_add_button_element( | ||||
|         widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_rekey_fail_widget_callback, nfc_magic); | ||||
| 
 | ||||
|     // Setup and start worker
 | ||||
|     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||
| } | ||||
| 
 | ||||
| bool nfc_magic_scene_rekey_fail_on_event(void* context, SceneManagerEvent event) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == GuiButtonTypeLeft) { | ||||
|             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|                 nfc_magic->scene_manager, NfcMagicSceneStart); | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeBack) { | ||||
|         consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|             nfc_magic->scene_manager, NfcMagicSceneStart); | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_scene_rekey_fail_on_exit(void* context) { | ||||
|     NfcMagic* nfc_magic = context; | ||||
| 
 | ||||
|     widget_reset(nfc_magic->widget); | ||||
| } | ||||
| @ -1,8 +1,7 @@ | ||||
| #include "../nfc_magic_i.h" | ||||
| enum SubmenuIndex { | ||||
|     SubmenuIndexCheck, | ||||
|     SubmenuIndexWriteGen1A, | ||||
|     SubmenuIndexWipe, | ||||
|     SubmenuIndexAuthenticateGen4, | ||||
| }; | ||||
| 
 | ||||
| void nfc_magic_scene_start_submenu_callback(void* context, uint32_t index) { | ||||
| @ -22,12 +21,10 @@ void nfc_magic_scene_start_on_enter(void* context) { | ||||
|         nfc_magic); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Write Gen1A", | ||||
|         SubmenuIndexWriteGen1A, | ||||
|         "Authenticate Gen4", | ||||
|         SubmenuIndexAuthenticateGen4, | ||||
|         nfc_magic_scene_start_submenu_callback, | ||||
|         nfc_magic); | ||||
|     submenu_add_item( | ||||
|         submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_start_submenu_callback, nfc_magic); | ||||
| 
 | ||||
|     submenu_set_selected_item( | ||||
|         submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart)); | ||||
| @ -40,23 +37,13 @@ bool nfc_magic_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SubmenuIndexCheck) { | ||||
|             nfc_magic->dev->password = MAGIC_GEN4_DEFAULT_PWD; | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexCheck); | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexWriteGen1A) { | ||||
|             // Explicitly save state in each branch so that the
 | ||||
|             // correct option is reselected if the user cancels
 | ||||
|             // loading a file.
 | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexWriteGen1A); | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexWipe) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexWipe); | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexAuthenticateGen4) { | ||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneKeyInput); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -22,7 +22,12 @@ static void nfc_magic_scene_wipe_setup_view(NfcMagic* nfc_magic) { | ||||
|     if(state == NfcMagicSceneWipeStateCardSearch) { | ||||
|         popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); | ||||
|         popup_set_text( | ||||
|             nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); | ||||
|             nfc_magic->popup, | ||||
|             "Apply the\nsame card\nto the back", | ||||
|             128, | ||||
|             32, | ||||
|             AlignRight, | ||||
|             AlignCenter); | ||||
|     } else { | ||||
|         popup_set_icon(popup, 12, 23, &I_Loading_24); | ||||
|         popup_set_header(popup, "Wiping\nDon't move...", 52, 32, AlignLeft, AlignCenter); | ||||
| @ -42,7 +47,9 @@ void nfc_magic_scene_wipe_on_enter(void* context) { | ||||
|     nfc_magic_worker_start( | ||||
|         nfc_magic->worker, | ||||
|         NfcMagicWorkerStateWipe, | ||||
|         &nfc_magic->nfc_dev->dev_data, | ||||
|         nfc_magic->dev, | ||||
|         &nfc_magic->source_dev->dev_data, | ||||
|         nfc_magic->new_password, | ||||
|         nfc_magic_wipe_worker_callback, | ||||
|         nfc_magic); | ||||
|     nfc_magic_blink_start(nfc_magic); | ||||
|  | ||||
| @ -21,7 +21,12 @@ static void nfc_magic_scene_write_setup_view(NfcMagic* nfc_magic) { | ||||
| 
 | ||||
|     if(state == NfcMagicSceneWriteStateCardSearch) { | ||||
|         popup_set_text( | ||||
|             nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); | ||||
|             nfc_magic->popup, | ||||
|             "Apply the\nsame card\nto the back", | ||||
|             128, | ||||
|             32, | ||||
|             AlignRight, | ||||
|             AlignCenter); | ||||
|         popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); | ||||
|     } else { | ||||
|         popup_set_icon(popup, 12, 23, &I_Loading_24); | ||||
| @ -42,7 +47,9 @@ void nfc_magic_scene_write_on_enter(void* context) { | ||||
|     nfc_magic_worker_start( | ||||
|         nfc_magic->worker, | ||||
|         NfcMagicWorkerStateWrite, | ||||
|         &nfc_magic->nfc_dev->dev_data, | ||||
|         nfc_magic->dev, | ||||
|         &nfc_magic->source_dev->dev_data, | ||||
|         nfc_magic->new_password, | ||||
|         nfc_magic_write_worker_callback, | ||||
|         nfc_magic); | ||||
|     nfc_magic_blink_start(nfc_magic); | ||||
|  | ||||
| @ -26,7 +26,7 @@ void nfc_magic_scene_wrong_card_on_enter(void* context) { | ||||
|         AlignLeft, | ||||
|         AlignTop, | ||||
|         FontSecondary, | ||||
|         "Writing is supported\nonly for 4 bytes UID\nMifare Classic 1k"); | ||||
|         "Writing this file is\nnot supported for\nthis magic card."); | ||||
|     widget_add_button_element( | ||||
|         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic); | ||||
| 
 | ||||
|  | ||||
| @ -16,10 +16,11 @@ | ||||
|     (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) | ||||
| 
 | ||||
| typedef enum { | ||||
|     WorkerEvtToggle = (1 << 0), | ||||
|     WorkerEvtEnd = (1 << 1), | ||||
|     WorkerEvtConnect = (1 << 2), | ||||
|     WorkerEvtDisconnect = (1 << 3), | ||||
|     WorkerEvtStartStop = (1 << 0), | ||||
|     WorkerEvtPauseResume = (1 << 1), | ||||
|     WorkerEvtEnd = (1 << 2), | ||||
|     WorkerEvtConnect = (1 << 3), | ||||
|     WorkerEvtDisconnect = (1 << 4), | ||||
| } WorkerEvtFlags; | ||||
| 
 | ||||
| static const char ducky_cmd_id[] = {"ID"}; | ||||
| @ -372,6 +373,7 @@ static int32_t bad_usb_worker(void* context) { | ||||
|     BadUsbScript* bad_usb = context; | ||||
| 
 | ||||
|     BadUsbWorkerState worker_state = BadUsbStateInit; | ||||
|     BadUsbWorkerState pause_state = BadUsbStateRunning; | ||||
|     int32_t delay_val = 0; | ||||
| 
 | ||||
|     FURI_LOG_I(WORKER_TAG, "Init"); | ||||
| @ -406,24 +408,24 @@ static int32_t bad_usb_worker(void* context) { | ||||
| 
 | ||||
|         } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
 | ||||
|             uint32_t flags = bad_usb_flags_get( | ||||
|                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); | ||||
|                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); | ||||
| 
 | ||||
|             if(flags & WorkerEvtEnd) { | ||||
|                 break; | ||||
|             } else if(flags & WorkerEvtConnect) { | ||||
|                 worker_state = BadUsbStateIdle; // Ready to run
 | ||||
|             } else if(flags & WorkerEvtToggle) { | ||||
|             } else if(flags & WorkerEvtStartStop) { | ||||
|                 worker_state = BadUsbStateWillRun; // Will run when USB is connected
 | ||||
|             } | ||||
|             bad_usb->st.state = worker_state; | ||||
| 
 | ||||
|         } else if(worker_state == BadUsbStateIdle) { // State: ready to start
 | ||||
|             uint32_t flags = bad_usb_flags_get( | ||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever); | ||||
|                 WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever); | ||||
| 
 | ||||
|             if(flags & WorkerEvtEnd) { | ||||
|                 break; | ||||
|             } else if(flags & WorkerEvtToggle) { // Start executing script
 | ||||
|             } else if(flags & WorkerEvtStartStop) { // Start executing script
 | ||||
|                 DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); | ||||
|                 delay_val = 0; | ||||
|                 bad_usb->buf_len = 0; | ||||
| @ -442,7 +444,7 @@ static int32_t bad_usb_worker(void* context) { | ||||
| 
 | ||||
|         } else if(worker_state == BadUsbStateWillRun) { // State: start on connection
 | ||||
|             uint32_t flags = bad_usb_flags_get( | ||||
|                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); | ||||
|                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); | ||||
| 
 | ||||
|             if(flags & WorkerEvtEnd) { | ||||
|                 break; | ||||
| @ -458,17 +460,17 @@ static int32_t bad_usb_worker(void* context) { | ||||
|                 storage_file_seek(script_file, 0, true); | ||||
|                 // extra time for PC to recognize Flipper as keyboard
 | ||||
|                 flags = furi_thread_flags_wait( | ||||
|                     WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtToggle, | ||||
|                     WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop, | ||||
|                     FuriFlagWaitAny | FuriFlagNoClear, | ||||
|                     1500); | ||||
|                 if(flags == (unsigned)FuriFlagErrorTimeout) { | ||||
|                     // If nothing happened - start script execution
 | ||||
|                     worker_state = BadUsbStateRunning; | ||||
|                 } else if(flags & WorkerEvtToggle) { | ||||
|                 } else if(flags & WorkerEvtStartStop) { | ||||
|                     worker_state = BadUsbStateIdle; | ||||
|                     furi_thread_flags_clear(WorkerEvtToggle); | ||||
|                     furi_thread_flags_clear(WorkerEvtStartStop); | ||||
|                 } | ||||
|             } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
 | ||||
|             } else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution
 | ||||
|                 worker_state = BadUsbStateNotConnected; | ||||
|             } | ||||
|             bad_usb->st.state = worker_state; | ||||
| @ -476,18 +478,23 @@ static int32_t bad_usb_worker(void* context) { | ||||
|         } else if(worker_state == BadUsbStateRunning) { // State: running
 | ||||
|             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); | ||||
|             uint32_t flags = furi_thread_flags_wait( | ||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); | ||||
|                 WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, | ||||
|                 FuriFlagWaitAny, | ||||
|                 delay_cur); | ||||
| 
 | ||||
|             delay_val -= delay_cur; | ||||
|             if(!(flags & FuriFlagError)) { | ||||
|                 if(flags & WorkerEvtEnd) { | ||||
|                     break; | ||||
|                 } else if(flags & WorkerEvtToggle) { | ||||
|                 } else if(flags & WorkerEvtStartStop) { | ||||
|                     worker_state = BadUsbStateIdle; // Stop executing script
 | ||||
|                     furi_hal_hid_kb_release_all(); | ||||
|                 } else if(flags & WorkerEvtDisconnect) { | ||||
|                     worker_state = BadUsbStateNotConnected; // USB disconnected
 | ||||
|                     furi_hal_hid_kb_release_all(); | ||||
|                 } else if(flags & WorkerEvtPauseResume) { | ||||
|                     pause_state = BadUsbStateRunning; | ||||
|                     worker_state = BadUsbStatePaused; // Pause
 | ||||
|                 } | ||||
|                 bad_usb->st.state = worker_state; | ||||
|                 continue; | ||||
| @ -526,13 +533,13 @@ static int32_t bad_usb_worker(void* context) { | ||||
|                 furi_check((flags & FuriFlagError) == 0); | ||||
|             } | ||||
|         } else if(worker_state == BadUsbStateWaitForBtn) { // State: Wait for button Press
 | ||||
|             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); | ||||
|             uint32_t flags = furi_thread_flags_wait( | ||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); | ||||
|             uint32_t flags = bad_usb_flags_get( | ||||
|                 WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, | ||||
|                 FuriWaitForever); | ||||
|             if(!(flags & FuriFlagError)) { | ||||
|                 if(flags & WorkerEvtEnd) { | ||||
|                     break; | ||||
|                 } else if(flags & WorkerEvtToggle) { | ||||
|                 } else if(flags & WorkerEvtStartStop) { | ||||
|                     delay_val = 0; | ||||
|                     worker_state = BadUsbStateRunning; | ||||
|                 } else if(flags & WorkerEvtDisconnect) { | ||||
| @ -542,21 +549,55 @@ static int32_t bad_usb_worker(void* context) { | ||||
|                 bad_usb->st.state = worker_state; | ||||
|                 continue; | ||||
|             } | ||||
|         } else if(worker_state == BadUsbStatePaused) { // State: Paused
 | ||||
|             uint32_t flags = bad_usb_flags_get( | ||||
|                 WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, | ||||
|                 FuriWaitForever); | ||||
|             if(!(flags & FuriFlagError)) { | ||||
|                 if(flags & WorkerEvtEnd) { | ||||
|                     break; | ||||
|                 } else if(flags & WorkerEvtStartStop) { | ||||
|                     worker_state = BadUsbStateIdle; // Stop executing script
 | ||||
|                     bad_usb->st.state = worker_state; | ||||
|                     furi_hal_hid_kb_release_all(); | ||||
|                 } else if(flags & WorkerEvtDisconnect) { | ||||
|                     worker_state = BadUsbStateNotConnected; // USB disconnected
 | ||||
|                     bad_usb->st.state = worker_state; | ||||
|                     furi_hal_hid_kb_release_all(); | ||||
|                 } else if(flags & WorkerEvtPauseResume) { | ||||
|                     if(pause_state == BadUsbStateRunning) { | ||||
|                         if(delay_val > 0) { | ||||
|                             bad_usb->st.state = BadUsbStateDelay; | ||||
|                             bad_usb->st.delay_remain = delay_val / 1000; | ||||
|                         } else { | ||||
|                             bad_usb->st.state = BadUsbStateRunning; | ||||
|                             delay_val = 0; | ||||
|                         } | ||||
|                         worker_state = BadUsbStateRunning; // Resume
 | ||||
|                     } else if(pause_state == BadUsbStateStringDelay) { | ||||
|                         bad_usb->st.state = BadUsbStateRunning; | ||||
|                         worker_state = BadUsbStateStringDelay; // Resume
 | ||||
|                     } | ||||
|                 } | ||||
|                 continue; | ||||
|             } | ||||
|         } else if(worker_state == BadUsbStateStringDelay) { // State: print string with delays
 | ||||
|             uint32_t flags = furi_thread_flags_wait( | ||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, | ||||
|                 FuriFlagWaitAny, | ||||
|             uint32_t flags = bad_usb_flags_get( | ||||
|                 WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, | ||||
|                 bad_usb->stringdelay); | ||||
| 
 | ||||
|             if(!(flags & FuriFlagError)) { | ||||
|                 if(flags & WorkerEvtEnd) { | ||||
|                     break; | ||||
|                 } else if(flags & WorkerEvtToggle) { | ||||
|                 } else if(flags & WorkerEvtStartStop) { | ||||
|                     worker_state = BadUsbStateIdle; // Stop executing script
 | ||||
|                     furi_hal_hid_kb_release_all(); | ||||
|                 } else if(flags & WorkerEvtDisconnect) { | ||||
|                     worker_state = BadUsbStateNotConnected; // USB disconnected
 | ||||
|                     furi_hal_hid_kb_release_all(); | ||||
|                 } else if(flags & WorkerEvtPauseResume) { | ||||
|                     pause_state = BadUsbStateStringDelay; | ||||
|                     worker_state = BadUsbStatePaused; // Pause
 | ||||
|                 } | ||||
|                 bad_usb->st.state = worker_state; | ||||
|                 continue; | ||||
| @ -651,9 +692,14 @@ void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layou | ||||
|     storage_file_free(layout_file); | ||||
| } | ||||
| 
 | ||||
| void bad_usb_script_toggle(BadUsbScript* bad_usb) { | ||||
| void bad_usb_script_start_stop(BadUsbScript* bad_usb) { | ||||
|     furi_assert(bad_usb); | ||||
|     furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle); | ||||
|     furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtStartStop); | ||||
| } | ||||
| 
 | ||||
| void bad_usb_script_pause_resume(BadUsbScript* bad_usb) { | ||||
|     furi_assert(bad_usb); | ||||
|     furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtPauseResume); | ||||
| } | ||||
| 
 | ||||
| BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb) { | ||||
|  | ||||
| @ -16,6 +16,7 @@ typedef enum { | ||||
|     BadUsbStateDelay, | ||||
|     BadUsbStateStringDelay, | ||||
|     BadUsbStateWaitForBtn, | ||||
|     BadUsbStatePaused, | ||||
|     BadUsbStateDone, | ||||
|     BadUsbStateScriptError, | ||||
|     BadUsbStateFileError, | ||||
| @ -42,7 +43,9 @@ void bad_usb_script_start(BadUsbScript* bad_usb); | ||||
| 
 | ||||
| void bad_usb_script_stop(BadUsbScript* bad_usb); | ||||
| 
 | ||||
| void bad_usb_script_toggle(BadUsbScript* bad_usb); | ||||
| void bad_usb_script_start_stop(BadUsbScript* bad_usb); | ||||
| 
 | ||||
| void bad_usb_script_pause_resume(BadUsbScript* bad_usb); | ||||
| 
 | ||||
| BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb); | ||||
| 
 | ||||
|  | ||||
| @ -21,7 +21,10 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { | ||||
|             } | ||||
|             consumed = true; | ||||
|         } else if(event.event == InputKeyOk) { | ||||
|             bad_usb_script_toggle(app->bad_usb_script); | ||||
|             bad_usb_script_start_stop(app->bad_usb_script); | ||||
|             consumed = true; | ||||
|         } else if(event.event == InputKeyRight) { | ||||
|             bad_usb_script_pause_resume(app->bad_usb_script); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeTick) { | ||||
|  | ||||
| @ -16,6 +16,7 @@ typedef struct { | ||||
|     char file_name[MAX_NAME_LEN]; | ||||
|     char layout[MAX_NAME_LEN]; | ||||
|     BadUsbState state; | ||||
|     bool pause_wait; | ||||
|     uint8_t anim_frame; | ||||
| } BadUsbModel; | ||||
| 
 | ||||
| @ -31,11 +32,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | ||||
|     if(strlen(model->layout) == 0) { | ||||
|         furi_string_set(disp_str, "(default)"); | ||||
|     } else { | ||||
|         furi_string_reset(disp_str); | ||||
|         furi_string_push_back(disp_str, '('); | ||||
|         for(size_t i = 0; i < strlen(model->layout); i++) | ||||
|             furi_string_push_back(disp_str, model->layout[i]); | ||||
|         furi_string_push_back(disp_str, ')'); | ||||
|         furi_string_printf(disp_str, "(%s)", model->layout); | ||||
|     } | ||||
|     elements_string_fit_width(canvas, disp_str, 128 - 2); | ||||
|     canvas_draw_str( | ||||
| @ -45,34 +42,42 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | ||||
| 
 | ||||
|     canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); | ||||
| 
 | ||||
|     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || | ||||
|        (model->state.state == BadUsbStateNotConnected)) { | ||||
|     BadUsbWorkerState state = model->state.state; | ||||
| 
 | ||||
|     if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || | ||||
|        (state == BadUsbStateNotConnected)) { | ||||
|         elements_button_center(canvas, "Run"); | ||||
|         elements_button_left(canvas, "Config"); | ||||
|     } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { | ||||
|     } else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) { | ||||
|         elements_button_center(canvas, "Stop"); | ||||
|     } else if(model->state.state == BadUsbStateWaitForBtn) { | ||||
|         if(!model->pause_wait) { | ||||
|             elements_button_right(canvas, "Pause"); | ||||
|         } | ||||
|     } else if(state == BadUsbStatePaused) { | ||||
|         elements_button_center(canvas, "End"); | ||||
|         elements_button_right(canvas, "Resume"); | ||||
|     } else if(state == BadUsbStateWaitForBtn) { | ||||
|         elements_button_center(canvas, "Press to continue"); | ||||
|     } else if(model->state.state == BadUsbStateWillRun) { | ||||
|     } else if(state == BadUsbStateWillRun) { | ||||
|         elements_button_center(canvas, "Cancel"); | ||||
|     } | ||||
| 
 | ||||
|     if(model->state.state == BadUsbStateNotConnected) { | ||||
|     if(state == BadUsbStateNotConnected) { | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to USB"); | ||||
|     } else if(model->state.state == BadUsbStateWillRun) { | ||||
|     } else if(state == BadUsbStateWillRun) { | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); | ||||
|     } else if(model->state.state == BadUsbStateFileError) { | ||||
|     } else if(state == BadUsbStateFileError) { | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); | ||||
|     } else if(model->state.state == BadUsbStateScriptError) { | ||||
|     } else if(state == BadUsbStateScriptError) { | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); | ||||
| @ -87,12 +92,12 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | ||||
|         canvas_draw_str_aligned( | ||||
|             canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||
|         furi_string_reset(disp_str); | ||||
|     } else if(model->state.state == BadUsbStateIdle) { | ||||
|     } else if(state == BadUsbStateIdle) { | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); | ||||
|         canvas_set_font(canvas, FontBigNumbers); | ||||
|         canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); | ||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||
|     } else if(model->state.state == BadUsbStateRunning) { | ||||
|     } else if(state == BadUsbStateRunning) { | ||||
|         if(model->anim_frame == 0) { | ||||
|             canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); | ||||
|         } else { | ||||
| @ -105,13 +110,13 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | ||||
|             canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||
|         furi_string_reset(disp_str); | ||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||
|     } else if(model->state.state == BadUsbStateDone) { | ||||
|     } else if(state == BadUsbStateDone) { | ||||
|         canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); | ||||
|         canvas_set_font(canvas, FontBigNumbers); | ||||
|         canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); | ||||
|         furi_string_reset(disp_str); | ||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||
|     } else if(model->state.state == BadUsbStateDelay) { | ||||
|     } else if(state == BadUsbStateDelay) { | ||||
|         if(model->anim_frame == 0) { | ||||
|             canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); | ||||
|         } else { | ||||
| @ -129,6 +134,22 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | ||||
|         canvas_draw_str_aligned( | ||||
|             canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||
|         furi_string_reset(disp_str); | ||||
|     } else if((state == BadUsbStatePaused) || (state == BadUsbStateWaitForBtn)) { | ||||
|         if(model->anim_frame == 0) { | ||||
|             canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); | ||||
|         } else { | ||||
|             canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); | ||||
|         } | ||||
|         canvas_set_font(canvas, FontBigNumbers); | ||||
|         furi_string_printf( | ||||
|             disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); | ||||
|         canvas_draw_str_aligned( | ||||
|             canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||
|         furi_string_reset(disp_str); | ||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||
|         canvas_set_font(canvas, FontSecondary); | ||||
|         canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused"); | ||||
|         furi_string_reset(disp_str); | ||||
|     } else { | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); | ||||
|     } | ||||
| @ -142,7 +163,27 @@ static bool bad_usb_input_callback(InputEvent* event, void* context) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) { | ||||
|         if(event->key == InputKeyLeft) { | ||||
|             consumed = true; | ||||
|             furi_assert(bad_usb->callback); | ||||
|             bad_usb->callback(event->key, bad_usb->context); | ||||
|         } else if(event->key == InputKeyOk) { | ||||
|             with_view_model( | ||||
|                 bad_usb->view, BadUsbModel * model, { model->pause_wait = false; }, true); | ||||
|             consumed = true; | ||||
|             furi_assert(bad_usb->callback); | ||||
|             bad_usb->callback(event->key, bad_usb->context); | ||||
|         } else if(event->key == InputKeyRight) { | ||||
|             with_view_model( | ||||
|                 bad_usb->view, | ||||
|                 BadUsbModel * model, | ||||
|                 { | ||||
|                     if((model->state.state == BadUsbStateRunning) || | ||||
|                        (model->state.state == BadUsbStateDelay)) { | ||||
|                         model->pause_wait = true; | ||||
|                     } | ||||
|                 }, | ||||
|                 true); | ||||
|             consumed = true; | ||||
|             furi_assert(bad_usb->callback); | ||||
|             bad_usb->callback(event->key, bad_usb->context); | ||||
| @ -215,6 +256,9 @@ void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { | ||||
|         { | ||||
|             memcpy(&(model->state), st, sizeof(BadUsbState)); | ||||
|             model->anim_frame ^= 1; | ||||
|             if(model->state.state == BadUsbStatePaused) { | ||||
|                 model->pause_wait = false; | ||||
|             } | ||||
|         }, | ||||
|         true); | ||||
| } | ||||
|  | ||||
| @ -312,7 +312,8 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { | ||||
| 
 | ||||
|     if(infrared_signal_is_raw(signal)) { | ||||
|         InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); | ||||
|         infrared_worker_set_raw_signal(infrared->worker, raw->timings, raw->timings_size); | ||||
|         infrared_worker_set_raw_signal( | ||||
|             infrared->worker, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle); | ||||
|     } else { | ||||
|         InfraredMessage* message = infrared_signal_get_message(signal); | ||||
|         infrared_worker_set_decoded_signal(infrared->worker, message); | ||||
|  | ||||
| @ -52,6 +52,7 @@ void nfc_scene_device_info_on_enter(void* context) { | ||||
|         } | ||||
|     } else if( | ||||
|         dev_data->protocol == NfcDeviceProtocolMifareClassic || | ||||
|         dev_data->protocol == NfcDeviceProtocolMifareDesfire || | ||||
|         dev_data->protocol == NfcDeviceProtocolMifareUl) { | ||||
|         furi_string_set(temp_str, nfc->dev->dev_data.parsed_data); | ||||
|     } | ||||
|  | ||||
| @ -20,7 +20,11 @@ void nfc_scene_mf_desfire_read_success_on_enter(void* context) { | ||||
|     Widget* widget = nfc->widget; | ||||
| 
 | ||||
|     // Prepare string for data display
 | ||||
|     FuriString* temp_str = furi_string_alloc_printf("\e#MIFARE DESfire\n"); | ||||
|     FuriString* temp_str = NULL; | ||||
|     if(furi_string_size(nfc->dev->dev_data.parsed_data)) { | ||||
|         temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); | ||||
|     } else { | ||||
|         temp_str = furi_string_alloc_printf("\e#MIFARE DESFire\n"); | ||||
|         furi_string_cat_printf(temp_str, "UID:"); | ||||
|         for(size_t i = 0; i < nfc_data->uid_len; i++) { | ||||
|             furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); | ||||
| @ -50,6 +54,7 @@ void nfc_scene_mf_desfire_read_success_on_enter(void* context) { | ||||
|         if(n_files != 1) { | ||||
|             furi_string_push_back(temp_str, 's'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     notification_message_block(nfc->notifications, &sequence_set_green_255); | ||||
| 
 | ||||
|  | ||||
| @ -19,7 +19,7 @@ void nfc_scene_mf_ultralight_menu_on_enter(void* context) { | ||||
|     Submenu* submenu = nfc->submenu; | ||||
|     MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data; | ||||
| 
 | ||||
|     if(!mf_ul_is_full_capture(data)) { | ||||
|     if(!mf_ul_is_full_capture(data) && data->type != MfUltralightTypeULC) { | ||||
|         submenu_add_item( | ||||
|             submenu, | ||||
|             "Unlock", | ||||
| @ -29,12 +29,14 @@ void nfc_scene_mf_ultralight_menu_on_enter(void* context) { | ||||
|     } | ||||
|     submenu_add_item( | ||||
|         submenu, "Save", SubmenuIndexSave, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); | ||||
|     if(mf_ul_emulation_supported(data)) { | ||||
|         submenu_add_item( | ||||
|             submenu, | ||||
|             "Emulate", | ||||
|             SubmenuIndexEmulate, | ||||
|             nfc_scene_mf_ultralight_menu_submenu_callback, | ||||
|             nfc); | ||||
|     } | ||||
|     submenu_add_item( | ||||
|         submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); | ||||
| 
 | ||||
|  | ||||
| @ -40,7 +40,7 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { | ||||
|         furi_string_cat_printf( | ||||
|             temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type)); | ||||
|     } else if(protocol == NfcDeviceProtocolMifareDesfire) { | ||||
|         furi_string_cat_printf(temp_str, "\e#MIFARE DESfire\n"); | ||||
|         furi_string_cat_printf(temp_str, "\e#MIFARE DESFire\n"); | ||||
|     } else { | ||||
|         furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n"); | ||||
|     } | ||||
|  | ||||
| @ -42,7 +42,8 @@ void nfc_scene_saved_menu_on_enter(void* context) { | ||||
|                 nfc); | ||||
|         } | ||||
|     } else if( | ||||
|         nfc->dev->format == NfcDeviceSaveFormatMifareUl || | ||||
|         (nfc->dev->format == NfcDeviceSaveFormatMifareUl && | ||||
|          mf_ul_emulation_supported(&nfc->dev->dev_data.mf_ul_data)) || | ||||
|         nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { | ||||
|         submenu_add_item( | ||||
|             submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); | ||||
| @ -72,6 +73,7 @@ void nfc_scene_saved_menu_on_enter(void* context) { | ||||
|     submenu_add_item( | ||||
|         submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); | ||||
|     if(nfc->dev->format == NfcDeviceSaveFormatMifareUl && | ||||
|        nfc->dev->dev_data.mf_ul_data.type != MfUltralightTypeULC && | ||||
|        !mf_ul_is_full_capture(&nfc->dev->dev_data.mf_ul_data)) { | ||||
|         submenu_add_item( | ||||
|             submenu, | ||||
| @ -146,6 +148,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { | ||||
|                 application_info_present = true; | ||||
|             } else if( | ||||
|                 dev_data->protocol == NfcDeviceProtocolMifareClassic || | ||||
|                 dev_data->protocol == NfcDeviceProtocolMifareDesfire || | ||||
|                 dev_data->protocol == NfcDeviceProtocolMifareUl) { | ||||
|                 application_info_present = nfc_supported_card_verify_and_parse(dev_data); | ||||
|             } | ||||
|  | ||||
| @ -10,4 +10,5 @@ typedef enum { | ||||
|         1, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */ | ||||
|     SubGhzErrorTypeOnlyRX = | ||||
|         2, /** Transmission on this frequency is blocked by regional settings */ | ||||
|     SubGhzErrorTypeParserOthers = 3, /** Error in protocol parameters description */ | ||||
| } SubGhzErrorType; | ||||
|  | ||||
| @ -40,15 +40,26 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { | ||||
|         } else if(event.event == SubGhzCustomEventSceneRpcButtonPress) { | ||||
|             bool result = false; | ||||
|             if((state == SubGhzRpcStateLoaded)) { | ||||
|                 result = subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); | ||||
|                 state = SubGhzRpcStateTx; | ||||
|                 if(result) subghz_blink_start(subghz); | ||||
|             } | ||||
|             if(!result) { | ||||
|                 switch( | ||||
|                     subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) { | ||||
|                 case SubGhzTxRxStartTxStateErrorOnlyRx: | ||||
|                     rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeOnlyRX); | ||||
|                     rpc_system_app_set_error_text( | ||||
|                         subghz->rpc_ctx, | ||||
|                         "Transmission on this frequency is restricted in your region"); | ||||
|                     break; | ||||
|                 case SubGhzTxRxStartTxStateErrorParserOthers: | ||||
|                     rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeParserOthers); | ||||
|                     rpc_system_app_set_error_text( | ||||
|                         subghz->rpc_ctx, "Error in protocol parameters description"); | ||||
|                     break; | ||||
| 
 | ||||
|                 default: //if(SubGhzTxRxStartTxStateOk)
 | ||||
|                     result = true; | ||||
|                     subghz_blink_start(subghz); | ||||
|                     state = SubGhzRpcStateTx; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); | ||||
|         } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { | ||||
| @ -56,9 +67,9 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { | ||||
|             if(state == SubGhzRpcStateTx) { | ||||
|                 subghz_txrx_stop(subghz->txrx); | ||||
|                 subghz_blink_stop(subghz); | ||||
|                 state = SubGhzRpcStateIdle; | ||||
|                 result = true; | ||||
|             } | ||||
|             state = SubGhzRpcStateIdle; | ||||
|             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonRelease, result); | ||||
|         } else if(event.event == SubGhzCustomEventSceneRpcLoad) { | ||||
|             bool result = false; | ||||
| @ -95,7 +106,7 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { | ||||
| void subghz_scene_rpc_on_exit(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     SubGhzRpcState state = scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneRpc); | ||||
|     if(state != SubGhzRpcStateIdle) { | ||||
|     if(state == SubGhzRpcStateTx) { | ||||
|         subghz_txrx_stop(subghz->txrx); | ||||
|         subghz_blink_stop(subghz); | ||||
|     } | ||||
|  | ||||
| @ -176,16 +176,19 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { | ||||
| 
 | ||||
|     furi_hal_power_suppress_charge_enter(); | ||||
| 
 | ||||
|     furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter); | ||||
| 
 | ||||
|     if(furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter)) { | ||||
|         while(!(furi_hal_subghz_is_async_tx_complete() || cli_cmd_interrupt_received(cli))) { | ||||
|             printf("."); | ||||
|             fflush(stdout); | ||||
|             furi_delay_ms(333); | ||||
|         } | ||||
|         furi_hal_subghz_stop_async_tx(); | ||||
|     furi_hal_subghz_sleep(); | ||||
| 
 | ||||
|     } else { | ||||
|         printf("Transmission on this frequency is restricted in your region\r\n"); | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_subghz_sleep(); | ||||
|     furi_hal_power_suppress_charge_exit(); | ||||
| 
 | ||||
|     flipper_format_free(flipper_format); | ||||
|  | ||||
| @ -147,6 +147,9 @@ void desktop_lock(Desktop* desktop) { | ||||
|         desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); | ||||
|     scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); | ||||
|     notification_message(desktop->notification, &sequence_display_backlight_off_delay_1000); | ||||
| 
 | ||||
|     DesktopStatus status = {.locked = true}; | ||||
|     furi_pubsub_publish(desktop->status_pubsub, &status); | ||||
| } | ||||
| 
 | ||||
| void desktop_unlock(Desktop* desktop) { | ||||
| @ -165,6 +168,9 @@ void desktop_unlock(Desktop* desktop) { | ||||
|         cli_session_open(cli, &cli_vcp); | ||||
|         furi_record_close(RECORD_CLI); | ||||
|     } | ||||
| 
 | ||||
|     DesktopStatus status = {.locked = false}; | ||||
|     furi_pubsub_publish(desktop->status_pubsub, &status); | ||||
| } | ||||
| 
 | ||||
| void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { | ||||
| @ -308,63 +314,13 @@ Desktop* desktop_alloc() { | ||||
|     desktop->auto_lock_timer = | ||||
|         furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop); | ||||
| 
 | ||||
|     desktop->status_pubsub = furi_pubsub_alloc(); | ||||
| 
 | ||||
|     furi_record_create(RECORD_DESKTOP, desktop); | ||||
| 
 | ||||
|     return desktop; | ||||
| } | ||||
| 
 | ||||
| void desktop_free(Desktop* desktop) { | ||||
|     furi_assert(desktop); | ||||
|     furi_check(furi_record_destroy(RECORD_DESKTOP)); | ||||
| 
 | ||||
|     furi_pubsub_unsubscribe( | ||||
|         loader_get_pubsub(desktop->loader), desktop->app_start_stop_subscription); | ||||
| 
 | ||||
|     if(desktop->input_events_subscription) { | ||||
|         furi_pubsub_unsubscribe(desktop->input_events_pubsub, desktop->input_events_subscription); | ||||
|         desktop->input_events_subscription = NULL; | ||||
|     } | ||||
| 
 | ||||
|     desktop->loader = NULL; | ||||
|     desktop->input_events_pubsub = NULL; | ||||
|     furi_record_close(RECORD_LOADER); | ||||
|     furi_record_close(RECORD_NOTIFICATION); | ||||
|     furi_record_close(RECORD_INPUT_EVENTS); | ||||
| 
 | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdMain); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLockMenu); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLocked); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdDebug); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinInput); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinTimeout); | ||||
| 
 | ||||
|     view_dispatcher_free(desktop->view_dispatcher); | ||||
|     scene_manager_free(desktop->scene_manager); | ||||
| 
 | ||||
|     animation_manager_free(desktop->animation_manager); | ||||
|     view_stack_free(desktop->main_view_stack); | ||||
|     desktop_main_free(desktop->main_view); | ||||
|     view_stack_free(desktop->locked_view_stack); | ||||
|     desktop_view_locked_free(desktop->locked_view); | ||||
|     desktop_lock_menu_free(desktop->lock_menu); | ||||
|     desktop_view_locked_free(desktop->locked_view); | ||||
|     desktop_debug_free(desktop->debug_view); | ||||
|     popup_free(desktop->hw_mismatch_popup); | ||||
|     desktop_view_pin_timeout_free(desktop->pin_timeout_view); | ||||
| 
 | ||||
|     furi_record_close(RECORD_GUI); | ||||
|     desktop->gui = NULL; | ||||
| 
 | ||||
|     furi_thread_free(desktop->scene_thread); | ||||
| 
 | ||||
|     furi_record_close("menu"); | ||||
| 
 | ||||
|     furi_timer_free(desktop->auto_lock_timer); | ||||
| 
 | ||||
|     free(desktop); | ||||
| } | ||||
| 
 | ||||
| static bool desktop_check_file_flag(const char* flag_path) { | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
|     bool exists = storage_common_stat(storage, flag_path, NULL) == FSE_OK; | ||||
| @ -383,6 +339,11 @@ void desktop_api_unlock(Desktop* instance) { | ||||
|     view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopLockedEventUnlocked); | ||||
| } | ||||
| 
 | ||||
| FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance) { | ||||
|     furi_assert(instance); | ||||
|     return instance->status_pubsub; | ||||
| } | ||||
| 
 | ||||
| int32_t desktop_srv(void* p) { | ||||
|     UNUSED(p); | ||||
| 
 | ||||
| @ -427,7 +388,8 @@ int32_t desktop_srv(void* p) { | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_run(desktop->view_dispatcher); | ||||
|     desktop_free(desktop); | ||||
| 
 | ||||
|     furi_crash("That was unexpected"); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi.h> | ||||
| 
 | ||||
| typedef struct Desktop Desktop; | ||||
| 
 | ||||
| #define RECORD_DESKTOP "desktop" | ||||
| @ -7,3 +9,9 @@ typedef struct Desktop Desktop; | ||||
| bool desktop_api_is_locked(Desktop* instance); | ||||
| 
 | ||||
| void desktop_api_unlock(Desktop* instance); | ||||
| 
 | ||||
| typedef struct { | ||||
|     bool locked; | ||||
| } DesktopStatus; | ||||
| 
 | ||||
| FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance); | ||||
|  | ||||
| @ -71,6 +71,8 @@ struct Desktop { | ||||
|     FuriPubSubSubscription* input_events_subscription; | ||||
|     FuriTimer* auto_lock_timer; | ||||
| 
 | ||||
|     FuriPubSub* status_pubsub; | ||||
| 
 | ||||
|     bool in_transition; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -36,8 +36,6 @@ | ||||
| #define MIN_PIN_SIZE 4 | ||||
| #define MAX_APP_LENGTH 128 | ||||
| 
 | ||||
| #define FAP_LOADER_APP_NAME "Applications" | ||||
| 
 | ||||
| typedef struct { | ||||
|     InputKey data[MAX_PIN_SIZE]; | ||||
|     uint8_t length; | ||||
|  | ||||
| @ -16,6 +16,8 @@ | ||||
| #define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") | ||||
| #define CLOCK_APP EXT_PATH("/apps/Tools/clock.fap") | ||||
| 
 | ||||
| #define FAP_LOADER_APP_NAME "Applications" | ||||
| 
 | ||||
| static void desktop_scene_main_new_idle_animation_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     Desktop* desktop = context; | ||||
| @ -77,6 +79,21 @@ static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* | ||||
|     } while(false); | ||||
| } | ||||
| 
 | ||||
| static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { | ||||
|     LoaderStatus status = LoaderStatusErrorInternal; | ||||
|     if(application->is_external) { | ||||
|         status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, application->name_or_path); | ||||
|     } else if(strlen(application->name_or_path) > 0) { | ||||
|         status = loader_start(desktop->loader, application->name_or_path, NULL); | ||||
|     } else { | ||||
|         status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, NULL); | ||||
|     } | ||||
| 
 | ||||
|     if(status != LoaderStatusOk) { | ||||
|         FURI_LOG_E(TAG, "loader_start failed: %d", status); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_main_callback(DesktopEvent event, void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     if(desktop->in_transition) return; | ||||
| @ -141,40 +158,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
|         case DesktopMainEventOpenFavoritePrimary: | ||||
|             DESKTOP_SETTINGS_LOAD(&desktop->settings); | ||||
|             if(desktop->settings.favorite_primary.is_external) { | ||||
|                 LoaderStatus status = loader_start( | ||||
|                     desktop->loader, | ||||
|                     FAP_LOADER_APP_NAME, | ||||
|                     desktop->settings.favorite_primary.name_or_path); | ||||
|                 if(status != LoaderStatusOk) { | ||||
|                     FURI_LOG_E(TAG, "loader_start failed: %d", status); | ||||
|                 } | ||||
|             } else { | ||||
|                 LoaderStatus status = loader_start( | ||||
|                     desktop->loader, desktop->settings.favorite_primary.name_or_path, NULL); | ||||
|                 if(status != LoaderStatusOk) { | ||||
|                     FURI_LOG_E(TAG, "loader_start failed: %d", status); | ||||
|                 } | ||||
|             } | ||||
|             desktop_scene_main_start_favorite(desktop, &desktop->settings.favorite_primary); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case DesktopMainEventOpenFavoriteSecondary: | ||||
|             DESKTOP_SETTINGS_LOAD(&desktop->settings); | ||||
|             if(desktop->settings.favorite_secondary.is_external) { | ||||
|                 LoaderStatus status = loader_start( | ||||
|                     desktop->loader, | ||||
|                     FAP_LOADER_APP_NAME, | ||||
|                     desktop->settings.favorite_secondary.name_or_path); | ||||
|                 if(status != LoaderStatusOk) { | ||||
|                     FURI_LOG_E(TAG, "loader_start failed: %d", status); | ||||
|                 } | ||||
|             } else { | ||||
|                 LoaderStatus status = loader_start( | ||||
|                     desktop->loader, desktop->settings.favorite_secondary.name_or_path, NULL); | ||||
|                 if(status != LoaderStatusOk) { | ||||
|                     FURI_LOG_E(TAG, "loader_start failed: %d", status); | ||||
|                 } | ||||
|             } | ||||
|             desktop_scene_main_start_favorite(desktop, &desktop->settings.favorite_secondary); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case DesktopAnimationEventCheckAnimation: | ||||
|  | ||||
| @ -89,15 +89,6 @@ Dolphin* dolphin_alloc() { | ||||
|     return dolphin; | ||||
| } | ||||
| 
 | ||||
| void dolphin_free(Dolphin* dolphin) { | ||||
|     furi_assert(dolphin); | ||||
| 
 | ||||
|     dolphin_state_free(dolphin->state); | ||||
|     furi_message_queue_free(dolphin->event_queue); | ||||
| 
 | ||||
|     free(dolphin); | ||||
| } | ||||
| 
 | ||||
| void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) { | ||||
|     furi_assert(dolphin); | ||||
|     furi_assert(event); | ||||
| @ -204,7 +195,7 @@ int32_t dolphin_srv(void* p) { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     dolphin_free(dolphin); | ||||
|     furi_crash("That was unexpected"); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @ -37,8 +37,6 @@ struct Dolphin { | ||||
| 
 | ||||
| Dolphin* dolphin_alloc(); | ||||
| 
 | ||||
| void dolphin_free(Dolphin* dolphin); | ||||
| 
 | ||||
| void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event); | ||||
| 
 | ||||
| void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event); | ||||
|  | ||||
| @ -85,30 +85,6 @@ Power* power_alloc() { | ||||
|     return power; | ||||
| } | ||||
| 
 | ||||
| void power_free(Power* power) { | ||||
|     furi_assert(power); | ||||
| 
 | ||||
|     // Gui
 | ||||
|     view_dispatcher_remove_view(power->view_dispatcher, PowerViewOff); | ||||
|     power_off_free(power->power_off); | ||||
|     view_dispatcher_remove_view(power->view_dispatcher, PowerViewUnplugUsb); | ||||
|     power_unplug_usb_free(power->power_unplug_usb); | ||||
| 
 | ||||
|     view_port_free(power->battery_view_port); | ||||
| 
 | ||||
|     // State
 | ||||
|     furi_mutex_free(power->api_mtx); | ||||
| 
 | ||||
|     // FuriPubSub
 | ||||
|     furi_pubsub_free(power->event_pubsub); | ||||
| 
 | ||||
|     // Records
 | ||||
|     furi_record_close(RECORD_NOTIFICATION); | ||||
|     furi_record_close(RECORD_GUI); | ||||
| 
 | ||||
|     free(power); | ||||
| } | ||||
| 
 | ||||
| static void power_check_charging_state(Power* power) { | ||||
|     if(furi_hal_power_is_charging()) { | ||||
|         if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) { | ||||
| @ -252,7 +228,7 @@ int32_t power_srv(void* p) { | ||||
|         furi_delay_ms(1000); | ||||
|     } | ||||
| 
 | ||||
|     power_free(power); | ||||
|     furi_crash("That was unexpected"); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @ -8,6 +8,8 @@ | ||||
| typedef struct { | ||||
|     RpcSession* session; | ||||
|     Desktop* desktop; | ||||
|     FuriPubSub* status_pubsub; | ||||
|     FuriPubSubSubscription* status_subscription; | ||||
| } RpcDesktop; | ||||
| 
 | ||||
| static void rpc_desktop_on_is_locked_request(const PB_Main* request, void* context) { | ||||
| @ -39,11 +41,63 @@ static void rpc_desktop_on_unlock_request(const PB_Main* request, void* context) | ||||
|     rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); | ||||
| } | ||||
| 
 | ||||
| static void rpc_desktop_on_desktop_pubsub(const void* message, void* context) { | ||||
|     RpcDesktop* rpc_desktop = context; | ||||
|     RpcSession* session = rpc_desktop->session; | ||||
|     const DesktopStatus* status = message; | ||||
| 
 | ||||
|     PB_Main rpc_message = { | ||||
|         .command_id = 0, | ||||
|         .command_status = PB_CommandStatus_OK, | ||||
|         .has_next = false, | ||||
|         .which_content = PB_Main_desktop_status_tag, | ||||
|         .content.desktop_status.locked = status->locked, | ||||
|     }; | ||||
|     rpc_send_and_release(session, &rpc_message); | ||||
| } | ||||
| 
 | ||||
| static void rpc_desktop_on_status_subscribe_request(const PB_Main* request, void* context) { | ||||
|     furi_assert(request); | ||||
|     furi_assert(context); | ||||
|     furi_assert(request->which_content == PB_Main_desktop_status_subscribe_request_tag); | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "StatusSubscribeRequest"); | ||||
|     RpcDesktop* rpc_desktop = context; | ||||
|     RpcSession* session = rpc_desktop->session; | ||||
| 
 | ||||
|     if(rpc_desktop->status_subscription) { | ||||
|         rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_ERROR); | ||||
|     } else { | ||||
|         rpc_desktop->status_subscription = furi_pubsub_subscribe( | ||||
|             rpc_desktop->status_pubsub, rpc_desktop_on_desktop_pubsub, rpc_desktop); | ||||
|         rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void rpc_desktop_on_status_unsubscribe_request(const PB_Main* request, void* context) { | ||||
|     furi_assert(request); | ||||
|     furi_assert(context); | ||||
|     furi_assert(request->which_content == PB_Main_desktop_status_unsubscribe_request_tag); | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "StatusUnsubscribeRequest"); | ||||
|     RpcDesktop* rpc_desktop = context; | ||||
|     RpcSession* session = rpc_desktop->session; | ||||
| 
 | ||||
|     if(rpc_desktop->status_subscription) { | ||||
|         furi_pubsub_unsubscribe(rpc_desktop->status_pubsub, rpc_desktop->status_subscription); | ||||
|         rpc_desktop->status_subscription = NULL; | ||||
|         rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); | ||||
|     } else { | ||||
|         rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_ERROR); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void* rpc_desktop_alloc(RpcSession* session) { | ||||
|     furi_assert(session); | ||||
| 
 | ||||
|     RpcDesktop* rpc_desktop = malloc(sizeof(RpcDesktop)); | ||||
|     rpc_desktop->desktop = furi_record_open(RECORD_DESKTOP); | ||||
|     rpc_desktop->status_pubsub = desktop_api_get_status_pubsub(rpc_desktop->desktop); | ||||
|     rpc_desktop->session = session; | ||||
| 
 | ||||
|     RpcHandler rpc_handler = { | ||||
| @ -58,6 +112,12 @@ void* rpc_desktop_alloc(RpcSession* session) { | ||||
|     rpc_handler.message_handler = rpc_desktop_on_unlock_request; | ||||
|     rpc_add_handler(session, PB_Main_desktop_unlock_request_tag, &rpc_handler); | ||||
| 
 | ||||
|     rpc_handler.message_handler = rpc_desktop_on_status_subscribe_request; | ||||
|     rpc_add_handler(session, PB_Main_desktop_status_subscribe_request_tag, &rpc_handler); | ||||
| 
 | ||||
|     rpc_handler.message_handler = rpc_desktop_on_status_unsubscribe_request; | ||||
|     rpc_add_handler(session, PB_Main_desktop_status_unsubscribe_request_tag, &rpc_handler); | ||||
| 
 | ||||
|     return rpc_desktop; | ||||
| } | ||||
| 
 | ||||
| @ -65,6 +125,10 @@ void rpc_desktop_free(void* context) { | ||||
|     furi_assert(context); | ||||
|     RpcDesktop* rpc_desktop = context; | ||||
| 
 | ||||
|     if(rpc_desktop->status_subscription) { | ||||
|         furi_pubsub_unsubscribe(rpc_desktop->status_pubsub, rpc_desktop->status_subscription); | ||||
|     } | ||||
| 
 | ||||
|     furi_assert(rpc_desktop->desktop); | ||||
|     furi_record_close(RECORD_DESKTOP); | ||||
| 
 | ||||
|  | ||||
| @ -226,7 +226,7 @@ FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* filei | ||||
|  */ | ||||
| FS_Error storage_common_remove(Storage* storage, const char* path); | ||||
| 
 | ||||
| /** Renames file/directory, file/directory must not be open
 | ||||
| /** Renames file/directory, file/directory must not be open. Will overwrite existing file.
 | ||||
|  * @param app pointer to the api | ||||
|  * @param old_path old path | ||||
|  * @param new_path new path | ||||
|  | ||||
| @ -422,12 +422,27 @@ FS_Error storage_common_remove(Storage* storage, const char* path) { | ||||
| } | ||||
| 
 | ||||
| FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) { | ||||
|     FS_Error error = storage_common_copy(storage, old_path, new_path); | ||||
|     if(error == FSE_OK) { | ||||
|     FS_Error error; | ||||
| 
 | ||||
|     do { | ||||
|         if(!storage_common_exists(storage, old_path)) { | ||||
|             error = FSE_INVALID_NAME; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(storage_file_exists(storage, new_path)) { | ||||
|             storage_common_remove(storage, new_path); | ||||
|         } | ||||
| 
 | ||||
|         error = storage_common_copy(storage, old_path, new_path); | ||||
|         if(error != FSE_OK) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!storage_simply_remove_recursive(storage, old_path)) { | ||||
|             error = FSE_INTERNAL; | ||||
|         } | ||||
|     } | ||||
|     } while(false); | ||||
| 
 | ||||
|     return error; | ||||
| } | ||||
|  | ||||
| @ -5,6 +5,9 @@ | ||||
| #include <dialogs/dialogs.h> | ||||
| #include <fap_loader/fap_loader_app.h> | ||||
| 
 | ||||
| #define EXTERNAL_APPLICATION_NAME ("[External Application]") | ||||
| #define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 1) | ||||
| 
 | ||||
| static bool favorite_fap_selector_item_callback( | ||||
|     FuriString* file_path, | ||||
|     void* context, | ||||
| @ -44,6 +47,8 @@ void desktop_settings_scene_favorite_on_enter(void* context) { | ||||
|     uint32_t primary_favorite = | ||||
|         scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); | ||||
|     uint32_t pre_select_item = 0; | ||||
|     FavoriteApp* curr_favorite_app = primary_favorite ? &app->settings.favorite_primary : | ||||
|                                                         &app->settings.favorite_secondary; | ||||
| 
 | ||||
|     for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||
|         submenu_add_item( | ||||
| @ -53,20 +58,24 @@ void desktop_settings_scene_favorite_on_enter(void* context) { | ||||
|             desktop_settings_scene_favorite_submenu_callback, | ||||
|             app); | ||||
| 
 | ||||
|         if(primary_favorite) { // Select favorite item in submenu
 | ||||
|             if((app->settings.favorite_primary.is_external && | ||||
|                 !strcmp(FLIPPER_APPS[i].name, FAP_LOADER_APP_NAME)) || | ||||
|                (!strcmp(FLIPPER_APPS[i].name, app->settings.favorite_primary.name_or_path))) { | ||||
|                 pre_select_item = i; | ||||
|             } | ||||
|         } else { | ||||
|             if((app->settings.favorite_secondary.is_external && | ||||
|                 !strcmp(FLIPPER_APPS[i].name, FAP_LOADER_APP_NAME)) || | ||||
|                (!strcmp(FLIPPER_APPS[i].name, app->settings.favorite_secondary.name_or_path))) { | ||||
|         // Select favorite item in submenu
 | ||||
|         if(!curr_favorite_app->is_external && | ||||
|            !strcmp(FLIPPER_APPS[i].name, curr_favorite_app->name_or_path)) { | ||||
|             pre_select_item = i; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| #ifdef APP_FAP_LOADER | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         EXTERNAL_APPLICATION_NAME, | ||||
|         EXTERNAL_APPLICATION_INDEX, | ||||
|         desktop_settings_scene_favorite_submenu_callback, | ||||
|         app); | ||||
|     if(curr_favorite_app->is_external) { | ||||
|         pre_select_item = EXTERNAL_APPLICATION_INDEX; | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     submenu_set_header( | ||||
|         submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:"); | ||||
| @ -82,23 +91,11 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e | ||||
| 
 | ||||
|     uint32_t primary_favorite = | ||||
|         scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); | ||||
|     FavoriteApp* curr_favorite_app = primary_favorite ? &app->settings.favorite_primary : | ||||
|                                                         &app->settings.favorite_secondary; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(strcmp(FLIPPER_APPS[event.event].name, FAP_LOADER_APP_NAME) != 0) { | ||||
|             if(primary_favorite) { | ||||
|                 app->settings.favorite_primary.is_external = false; | ||||
|                 strncpy( | ||||
|                     app->settings.favorite_primary.name_or_path, | ||||
|                     FLIPPER_APPS[event.event].name, | ||||
|                     MAX_APP_LENGTH); | ||||
|             } else { | ||||
|                 app->settings.favorite_secondary.is_external = false; | ||||
|                 strncpy( | ||||
|                     app->settings.favorite_secondary.name_or_path, | ||||
|                     FLIPPER_APPS[event.event].name, | ||||
|                     MAX_APP_LENGTH); | ||||
|             } | ||||
|         } else { | ||||
|         if(event.event == EXTERNAL_APPLICATION_INDEX) { | ||||
|             const DialogsFileBrowserOptions browser_options = { | ||||
|                 .extension = ".fap", | ||||
|                 .icon = &I_unknown_10px, | ||||
| @ -109,36 +106,29 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e | ||||
|                 .base_path = EXT_PATH("apps"), | ||||
|             }; | ||||
| 
 | ||||
|             if(primary_favorite) { // Select favorite fap in file browser
 | ||||
|                 if(favorite_fap_selector_file_exists( | ||||
|                        app->settings.favorite_primary.name_or_path)) { | ||||
|                     furi_string_set_str(temp_path, app->settings.favorite_primary.name_or_path); | ||||
|                 } | ||||
|             } else { | ||||
|                 if(favorite_fap_selector_file_exists( | ||||
|                        app->settings.favorite_secondary.name_or_path)) { | ||||
|                     furi_string_set_str(temp_path, app->settings.favorite_secondary.name_or_path); | ||||
|                 } | ||||
|             // Select favorite fap in file browser
 | ||||
|             if(favorite_fap_selector_file_exists(curr_favorite_app->name_or_path)) { | ||||
|                 furi_string_set_str(temp_path, curr_favorite_app->name_or_path); | ||||
|             } | ||||
| 
 | ||||
|             submenu_reset(app->submenu); | ||||
|             if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) { | ||||
|                 if(primary_favorite) { | ||||
|                     app->settings.favorite_primary.is_external = true; | ||||
|                 submenu_reset(app->submenu); // Prevent menu from being shown when we exiting scene
 | ||||
|                 curr_favorite_app->is_external = true; | ||||
|                 strncpy( | ||||
|                         app->settings.favorite_primary.name_or_path, | ||||
|                     curr_favorite_app->name_or_path, | ||||
|                     furi_string_get_cstr(temp_path), | ||||
|                     MAX_APP_LENGTH); | ||||
|                 consumed = true; | ||||
|             } | ||||
|         } else { | ||||
|                     app->settings.favorite_secondary.is_external = true; | ||||
|             curr_favorite_app->is_external = false; | ||||
|             strncpy( | ||||
|                         app->settings.favorite_secondary.name_or_path, | ||||
|                         furi_string_get_cstr(temp_path), | ||||
|                         MAX_APP_LENGTH); | ||||
|                 } | ||||
|             } | ||||
|                 curr_favorite_app->name_or_path, FLIPPER_APPS[event.event].name, MAX_APP_LENGTH); | ||||
|             consumed = true; | ||||
|         } | ||||
|         if(consumed) { | ||||
|             scene_manager_previous_scene(app->scene_manager); | ||||
|         }; | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_0.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_1.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_10.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_11.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_12.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_13.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_14.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_15.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_16.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_17.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_18.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 828 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_19.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 817 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_2.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_20.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_21.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_22.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_23.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_24.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_25.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_26.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_27.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_28.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_29.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_3.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_30.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_31.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_32.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_33.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_34.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_35.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_36.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_37.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_38.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_39.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_4.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_40.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_41.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_42.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_43.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_44.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_Kaiju_128x64/frame_45.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
 Aleksandr Kutuzov
						Aleksandr Kutuzov