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' |       - name: 'Upload artifacts to update server' | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} |         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||||
|         run: | |         run: | | ||||||
|           mkdir -p ~/.ssh |           FILES=$(for CUR in $(ls artifacts/); do echo "-F files=@artifacts/$CUR"; done) | ||||||
|           ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts |           curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ | ||||||
|           echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; |               -F "branch=${BRANCH_NAME}" \ | ||||||
|           chmod 600 ./deploy_key; |               ${FILES[@]} \ | ||||||
|           rsync -avzP --delete --mkpath \ |               "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles | ||||||
|               -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 }} |  | ||||||
| 
 | 
 | ||||||
|       - name: 'Find Previous Comment' |       - name: 'Find Previous Comment' | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} |         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/workflows/reindex.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -11,4 +11,5 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - name: Trigger reindex |       - name: Trigger reindex | ||||||
|         run: | |         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 | .mxproject | ||||||
| Brewfile.lock.json | Brewfile.lock.json | ||||||
| 
 | 
 | ||||||
| # Visual Studio Code |  | ||||||
| /.vscode/ |  | ||||||
| 
 |  | ||||||
| # Kate | # Kate | ||||||
| .kateproject | .kateproject | ||||||
| .kateconfig | .kateconfig | ||||||
| 
 | 
 | ||||||
| # legendary cmake's |  | ||||||
| build |  | ||||||
| CMakeLists.txt |  | ||||||
| 
 |  | ||||||
| # bundle output |  | ||||||
| dist |  | ||||||
| 
 |  | ||||||
| # kde | # kde | ||||||
| .directory | .directory | ||||||
| 
 | 
 | ||||||
| # SCons | # SCons | ||||||
| .sconsign.dblite | .sconsign.dblite | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Visual Studio Code | ||||||
|  | /.vscode | ||||||
|  | 
 | ||||||
|  | # bundle output | ||||||
|  | /dist | ||||||
|  | 
 | ||||||
| # SCons build dir | # SCons build dir | ||||||
| build/ | /build | ||||||
| 
 | 
 | ||||||
| # Toolchain | # Toolchain | ||||||
| /toolchain | /toolchain | ||||||
| @ -64,3 +62,5 @@ PVS-Studio.log | |||||||
| *.PVS-Studio.* | *.PVS-Studio.* | ||||||
| 
 | 
 | ||||||
| .gdbinit | .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_set_callback(comparator_trigger_callback, app); | ||||||
|     furi_hal_rfid_comp_start(); |     furi_hal_rfid_comp_start(); | ||||||
| 
 | 
 | ||||||
|     furi_hal_rfid_pins_read(); |     furi_hal_rfid_tim_read_start(125000, 0.5); | ||||||
|     furi_hal_rfid_tim_read(125000, 0.5); |  | ||||||
|     furi_hal_rfid_tim_read_start(); |  | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidDebugViewTune); |     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_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog); | ||||||
|     furi_hal_rfid_tim_read_stop(); |     furi_hal_rfid_tim_read_stop(); | ||||||
|     furi_hal_rfid_tim_reset(); |  | ||||||
|     furi_hal_rfid_pins_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_TEST_DATA_MAX_LEN 18 | ||||||
| #define NFC_TETS_TIMINGS_MAX_LEN 1350 | #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 { | typedef struct { | ||||||
|     Storage* storage; |     Storage* storage; | ||||||
|     NfcaSignal* signal; |     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( | static bool nfc_test_digital_signal_test_encode( | ||||||
|     const char* file_name, |     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 timing_tolerance, | ||||||
|     uint32_t timings_sum_tolerance) { |     uint32_t timings_sum_tolerance) { | ||||||
|     furi_assert(nfc_test); |     furi_assert(nfc_test); | ||||||
| 
 | 
 | ||||||
|     bool success = false; |     bool success = false; | ||||||
|     uint32_t time = 0; |  | ||||||
|     uint32_t dut_timings_sum = 0; |     uint32_t dut_timings_sum = 0; | ||||||
|     uint32_t ref_timings_sum = 0; |     uint32_t ref_timings_sum = 0; | ||||||
|     uint8_t parity[10] = {}; |     uint8_t parity[10] = {}; | ||||||
| @ -109,17 +115,37 @@ static bool nfc_test_digital_signal_test_encode( | |||||||
| 
 | 
 | ||||||
|         // Encode signal
 |         // Encode signal
 | ||||||
|         FURI_CRITICAL_ENTER(); |         FURI_CRITICAL_ENTER(); | ||||||
|         time = DWT->CYCCNT; |         uint32_t time_start = DWT->CYCCNT; | ||||||
|  | 
 | ||||||
|         nfca_signal_encode( |         nfca_signal_encode( | ||||||
|             nfc_test->signal, nfc_test->test_data, nfc_test->test_data_len * 8, parity); |             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); |         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(); |         FURI_CRITICAL_EXIT(); | ||||||
| 
 | 
 | ||||||
|         // Check timings
 |         // Check timings
 | ||||||
|         if(time > encode_max_time) { |         if(time_signal > build_signal_max_time_us) { | ||||||
|             FURI_LOG_E( |             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; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -156,7 +182,16 @@ static bool nfc_test_digital_signal_test_encode( | |||||||
|             break; |             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( |         FURI_LOG_I( | ||||||
|             TAG, |             TAG, | ||||||
|             "Timings sum difference: %ld [1/64MHZ]. Acceptable difference: %ld [1/64MHz]", |             "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_TEST(nfc_digital_signal_test) { | ||||||
|     mu_assert( |     mu_assert( | ||||||
|         nfc_test_digital_signal_test_encode( |         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"); |         "NFC short digital signal test failed\r\n"); | ||||||
|     mu_assert( |     mu_assert( | ||||||
|         nfc_test_digital_signal_test_encode( |         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"); |         "NFC long digital signal test failed\r\n"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,11 +1,10 @@ | |||||||
| #include "magic.h" | #include "classic_gen1.h" | ||||||
| 
 | 
 | ||||||
| #include <furi_hal_nfc.h> | #include <furi_hal_nfc.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "Magic" | #define TAG "Magic" | ||||||
| 
 | 
 | ||||||
| #define MAGIC_CMD_WUPA (0x40) | #define MAGIC_CMD_WUPA (0x40) | ||||||
| #define MAGIC_CMD_WIPE (0x41) |  | ||||||
| #define MAGIC_CMD_ACCESS (0x43) | #define MAGIC_CMD_ACCESS (0x43) | ||||||
| 
 | 
 | ||||||
| #define MAGIC_MIFARE_READ_CMD (0x30) | #define MAGIC_MIFARE_READ_CMD (0x30) | ||||||
| @ -15,7 +14,7 @@ | |||||||
| 
 | 
 | ||||||
| #define MAGIC_BUFFER_SIZE (32) | #define MAGIC_BUFFER_SIZE (32) | ||||||
| 
 | 
 | ||||||
| bool magic_wupa() { | bool magic_gen1_wupa() { | ||||||
|     bool magic_activated = false; |     bool magic_activated = false; | ||||||
|     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; |     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; |     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
| @ -23,19 +22,6 @@ bool magic_wupa() { | |||||||
|     FuriHalNfcReturn ret = 0; |     FuriHalNfcReturn ret = 0; | ||||||
| 
 | 
 | ||||||
|     do { |     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
 |         // Start communication
 | ||||||
|         tx_data[0] = MAGIC_CMD_WUPA; |         tx_data[0] = MAGIC_CMD_WUPA; | ||||||
|         ret = furi_hal_nfc_ll_txrx_bits( |         ret = furi_hal_nfc_ll_txrx_bits( | ||||||
| @ -53,15 +39,10 @@ bool magic_wupa() { | |||||||
|         magic_activated = true; |         magic_activated = true; | ||||||
|     } while(false); |     } while(false); | ||||||
| 
 | 
 | ||||||
|     if(!magic_activated) { |  | ||||||
|         furi_hal_nfc_ll_txrx_off(); |  | ||||||
|         furi_hal_nfc_start_sleep(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return magic_activated; |     return magic_activated; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool magic_data_access_cmd() { | bool magic_gen1_data_access_cmd() { | ||||||
|     bool write_cmd_success = false; |     bool write_cmd_success = false; | ||||||
|     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; |     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; |     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
| @ -86,15 +67,10 @@ bool magic_data_access_cmd() { | |||||||
|         write_cmd_success = true; |         write_cmd_success = true; | ||||||
|     } while(false); |     } while(false); | ||||||
| 
 | 
 | ||||||
|     if(!write_cmd_success) { |  | ||||||
|         furi_hal_nfc_ll_txrx_off(); |  | ||||||
|         furi_hal_nfc_start_sleep(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return write_cmd_success; |     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); |     furi_assert(data); | ||||||
| 
 | 
 | ||||||
|     bool read_success = false; |     bool read_success = false; | ||||||
| @ -122,15 +98,10 @@ bool magic_read_block(uint8_t block_num, MfClassicBlock* data) { | |||||||
|         read_success = true; |         read_success = true; | ||||||
|     } while(false); |     } while(false); | ||||||
| 
 | 
 | ||||||
|     if(!read_success) { |  | ||||||
|         furi_hal_nfc_ll_txrx_off(); |  | ||||||
|         furi_hal_nfc_start_sleep(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return read_success; |     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); |     furi_assert(data); | ||||||
| 
 | 
 | ||||||
|     bool write_success = false; |     bool write_success = false; | ||||||
| @ -170,44 +141,5 @@ bool magic_write_blk(uint8_t block_num, MfClassicBlock* data) { | |||||||
|         write_success = true; |         write_success = true; | ||||||
|     } while(false); |     } while(false); | ||||||
| 
 | 
 | ||||||
|     if(!write_success) { |  | ||||||
|         furi_hal_nfc_ll_txrx_off(); |  | ||||||
|         furi_hal_nfc_start_sleep(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return write_success; |     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_magic->view_dispatcher, nfc_magic_tick_event_callback, 100); | ||||||
| 
 | 
 | ||||||
|     // Nfc device
 |     // Nfc device
 | ||||||
|     nfc_magic->nfc_dev = nfc_device_alloc(); |     nfc_magic->dev = malloc(sizeof(NfcMagicDevice)); | ||||||
|     furi_string_set(nfc_magic->nfc_dev->folder, NFC_APP_FOLDER); |     nfc_magic->source_dev = nfc_device_alloc(); | ||||||
|  |     furi_string_set(nfc_magic->source_dev->folder, NFC_APP_FOLDER); | ||||||
| 
 | 
 | ||||||
|     // Open GUI record
 |     // Open GUI record
 | ||||||
|     nfc_magic->gui = furi_record_open(RECORD_GUI); |     nfc_magic->gui = furi_record_open(RECORD_GUI); | ||||||
| @ -81,6 +82,13 @@ NfcMagic* nfc_magic_alloc() { | |||||||
|         NfcMagicViewTextInput, |         NfcMagicViewTextInput, | ||||||
|         text_input_get_view(nfc_magic->text_input)); |         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
 |     // Custom Widget
 | ||||||
|     nfc_magic->widget = widget_alloc(); |     nfc_magic->widget = widget_alloc(); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
| @ -93,7 +101,8 @@ void nfc_magic_free(NfcMagic* nfc_magic) { | |||||||
|     furi_assert(nfc_magic); |     furi_assert(nfc_magic); | ||||||
| 
 | 
 | ||||||
|     // Nfc device
 |     // Nfc device
 | ||||||
|     nfc_device_free(nfc_magic->nfc_dev); |     free(nfc_magic->dev); | ||||||
|  |     nfc_device_free(nfc_magic->source_dev); | ||||||
| 
 | 
 | ||||||
|     // Submenu
 |     // Submenu
 | ||||||
|     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); |     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); |     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewLoading); | ||||||
|     loading_free(nfc_magic->loading); |     loading_free(nfc_magic->loading); | ||||||
| 
 | 
 | ||||||
|     // TextInput
 |     // Text Input
 | ||||||
|     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewTextInput); |     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewTextInput); | ||||||
|     text_input_free(nfc_magic->text_input); |     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
 |     // Custom Widget
 | ||||||
|     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); |     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||||
|     widget_free(nfc_magic->widget); |     widget_free(nfc_magic->widget); | ||||||
| @ -164,6 +177,7 @@ int32_t nfc_magic_app(void* p) { | |||||||
| 
 | 
 | ||||||
|     view_dispatcher_run(nfc_magic->view_dispatcher); |     view_dispatcher_run(nfc_magic->view_dispatcher); | ||||||
| 
 | 
 | ||||||
|  |     magic_deactivate(); | ||||||
|     nfc_magic_free(nfc_magic); |     nfc_magic_free(nfc_magic); | ||||||
| 
 | 
 | ||||||
|     return 0; |     return 0; | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								applications/external/nfc_magic/nfc_magic.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,3 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | typedef struct NfcMagicDevice NfcMagicDevice; | ||||||
|  | 
 | ||||||
| typedef struct NfcMagic NfcMagic; | 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.h" | ||||||
| #include "nfc_magic_worker.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 <furi.h> | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| @ -15,6 +18,7 @@ | |||||||
| #include <gui/modules/popup.h> | #include <gui/modules/popup.h> | ||||||
| #include <gui/modules/loading.h> | #include <gui/modules/loading.h> | ||||||
| #include <gui/modules/text_input.h> | #include <gui/modules/text_input.h> | ||||||
|  | #include <gui/modules/byte_input.h> | ||||||
| #include <gui/modules/widget.h> | #include <gui/modules/widget.h> | ||||||
| 
 | 
 | ||||||
| #include <input/input.h> | #include <input/input.h> | ||||||
| @ -39,14 +43,22 @@ enum NfcMagicCustomEvent { | |||||||
|     NfcMagicCustomEventTextInputDone, |     NfcMagicCustomEventTextInputDone, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct NfcMagicDevice { | ||||||
|  |     MagicType type; | ||||||
|  |     uint32_t cuid; | ||||||
|  |     uint32_t password; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| struct NfcMagic { | struct NfcMagic { | ||||||
|     NfcMagicWorker* worker; |     NfcMagicWorker* worker; | ||||||
|     ViewDispatcher* view_dispatcher; |     ViewDispatcher* view_dispatcher; | ||||||
|     Gui* gui; |     Gui* gui; | ||||||
|     NotificationApp* notifications; |     NotificationApp* notifications; | ||||||
|     SceneManager* scene_manager; |     SceneManager* scene_manager; | ||||||
|     // NfcMagicDevice* dev;
 |     struct NfcMagicDevice* dev; | ||||||
|     NfcDevice* nfc_dev; |     NfcDevice* source_dev; | ||||||
|  | 
 | ||||||
|  |     uint32_t new_password; | ||||||
| 
 | 
 | ||||||
|     FuriString* text_box_store; |     FuriString* text_box_store; | ||||||
| 
 | 
 | ||||||
| @ -55,6 +67,7 @@ struct NfcMagic { | |||||||
|     Popup* popup; |     Popup* popup; | ||||||
|     Loading* loading; |     Loading* loading; | ||||||
|     TextInput* text_input; |     TextInput* text_input; | ||||||
|  |     ByteInput* byte_input; | ||||||
|     Widget* widget; |     Widget* widget; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -63,6 +76,7 @@ typedef enum { | |||||||
|     NfcMagicViewPopup, |     NfcMagicViewPopup, | ||||||
|     NfcMagicViewLoading, |     NfcMagicViewLoading, | ||||||
|     NfcMagicViewTextInput, |     NfcMagicViewTextInput, | ||||||
|  |     NfcMagicViewByteInput, | ||||||
|     NfcMagicViewWidget, |     NfcMagicViewWidget, | ||||||
| } NfcMagicView; | } NfcMagicView; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										350
									
								
								applications/external/nfc_magic/nfc_magic_worker.c
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,6 +1,9 @@ | |||||||
| #include "nfc_magic_worker_i.h" | #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" | #define TAG "NfcMagicWorker" | ||||||
| 
 | 
 | ||||||
| @ -43,15 +46,20 @@ void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker) { | |||||||
| void nfc_magic_worker_start( | void nfc_magic_worker_start( | ||||||
|     NfcMagicWorker* nfc_magic_worker, |     NfcMagicWorker* nfc_magic_worker, | ||||||
|     NfcMagicWorkerState state, |     NfcMagicWorkerState state, | ||||||
|  |     NfcMagicDevice* magic_dev, | ||||||
|     NfcDeviceData* dev_data, |     NfcDeviceData* dev_data, | ||||||
|  |     uint32_t new_password, | ||||||
|     NfcMagicWorkerCallback callback, |     NfcMagicWorkerCallback callback, | ||||||
|     void* context) { |     void* context) { | ||||||
|     furi_assert(nfc_magic_worker); |     furi_assert(nfc_magic_worker); | ||||||
|  |     furi_assert(magic_dev); | ||||||
|     furi_assert(dev_data); |     furi_assert(dev_data); | ||||||
| 
 | 
 | ||||||
|     nfc_magic_worker->callback = callback; |     nfc_magic_worker->callback = callback; | ||||||
|     nfc_magic_worker->context = context; |     nfc_magic_worker->context = context; | ||||||
|  |     nfc_magic_worker->magic_dev = magic_dev; | ||||||
|     nfc_magic_worker->dev_data = dev_data; |     nfc_magic_worker->dev_data = dev_data; | ||||||
|  |     nfc_magic_worker->new_password = new_password; | ||||||
|     nfc_magic_worker_change_state(nfc_magic_worker, state); |     nfc_magic_worker_change_state(nfc_magic_worker, state); | ||||||
|     furi_thread_start(nfc_magic_worker->thread); |     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); |         nfc_magic_worker_check(nfc_magic_worker); | ||||||
|     } else if(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { |     } else if(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { | ||||||
|         nfc_magic_worker_write(nfc_magic_worker); |         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) { |     } else if(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { | ||||||
|         nfc_magic_worker_wipe(nfc_magic_worker); |         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) { | void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { | ||||||
|     bool card_found_notified = false; |     bool card_found_notified = false; | ||||||
|  |     bool done = false; | ||||||
|     FuriHalNfcDevData nfc_data = {}; |     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) { |     while(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { | ||||||
|  |         do { | ||||||
|  |             if(magic_dev->type == MagicTypeClassicGen1) { | ||||||
|                 if(furi_hal_nfc_detect(&nfc_data, 200)) { |                 if(furi_hal_nfc_detect(&nfc_data, 200)) { | ||||||
|             if(!card_found_notified) { |                     magic_deactivate(); | ||||||
|                 nfc_magic_worker->callback( |                     magic_activate(); | ||||||
|                     NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); |                     if(!magic_gen1_wupa()) { | ||||||
|                 card_found_notified = true; |  | ||||||
|             } |  | ||||||
|             furi_hal_nfc_sleep(); |  | ||||||
|             if(!magic_wupa()) { |  | ||||||
|                         FURI_LOG_E(TAG, "No card response to WUPA (not a magic card)"); |                         FURI_LOG_E(TAG, "No card response to WUPA (not a magic card)"); | ||||||
|                         nfc_magic_worker->callback( |                         nfc_magic_worker->callback( | ||||||
|                             NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); |                             NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); | ||||||
|  |                         done = true; | ||||||
|                         break; |                         break; | ||||||
|                     } |                     } | ||||||
|             furi_hal_nfc_sleep(); |                     magic_deactivate(); | ||||||
|                 } |                 } | ||||||
|         if(magic_wupa()) { |                 magic_activate(); | ||||||
|             if(!magic_data_access_cmd()) { |                 if(magic_gen1_wupa()) { | ||||||
|                 FURI_LOG_E(TAG, "No card response to data access command (not a magic card)"); |                     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( |                         nfc_magic_worker->callback( | ||||||
|                             NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); |                             NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); | ||||||
|  |                         done = true; | ||||||
|                         break; |                         break; | ||||||
|                     } |                     } | ||||||
|  | 
 | ||||||
|  |                     MfClassicData* mfc_data = &dev_data->mf_classic_data; | ||||||
|                     for(size_t i = 0; i < 64; i++) { |                     for(size_t i = 0; i < 64; i++) { | ||||||
|                         FURI_LOG_D(TAG, "Writing block %d", 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); |                             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; |                             break; | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|             nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | 
 | ||||||
|  |                     done = true; | ||||||
|  |                     nfc_magic_worker->callback( | ||||||
|  |                         NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||||
|                     break; |                     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 { |                     } 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) { |         if(card_found_notified) { | ||||||
|             nfc_magic_worker->callback( |             nfc_magic_worker->callback( | ||||||
|                 NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); |                 NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); | ||||||
|             card_found_notified = false; |             card_found_notified = false; | ||||||
|         } |         } | ||||||
|         } | 
 | ||||||
|         furi_delay_ms(300); |         furi_delay_ms(300); | ||||||
|     } |     } | ||||||
|     magic_deactivate(); |     magic_deactivate(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { | void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { | ||||||
|  |     NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; | ||||||
|     bool card_found_notified = false; |     bool card_found_notified = false; | ||||||
|  |     uint8_t gen4_config[MAGIC_GEN4_CONFIG_LEN]; | ||||||
| 
 | 
 | ||||||
|     while(nfc_magic_worker->state == NfcMagicWorkerStateCheck) { |     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) { |             if(!card_found_notified) { | ||||||
|                 nfc_magic_worker->callback( |                 nfc_magic_worker->callback( | ||||||
|                     NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); |                     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); |             nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||||
|             break; |             break; | ||||||
|         } else { |         } | ||||||
|  | 
 | ||||||
|         if(card_found_notified) { |         if(card_found_notified) { | ||||||
|             nfc_magic_worker->callback( |             nfc_magic_worker->callback( | ||||||
|                 NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); |                 NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); | ||||||
|             card_found_notified = false; |             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); |         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) { | 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; |     MfClassicBlock block; | ||||||
|     memset(&block, 0, sizeof(MfClassicBlock)); |     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[0] = 0x01; | ||||||
|     block.value[1] = 0x02; |     block.value[1] = 0x02; | ||||||
|     block.value[2] = 0x03; |     block.value[2] = 0x03; | ||||||
| @ -158,15 +406,69 @@ void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { | |||||||
|     block.value[5] = 0x08; |     block.value[5] = 0x08; | ||||||
|     block.value[6] = 0x04; |     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) { |     while(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { | ||||||
|  |         do { | ||||||
|             magic_deactivate(); |             magic_deactivate(); | ||||||
|             furi_delay_ms(300); |             furi_delay_ms(300); | ||||||
|         if(!magic_wupa()) continue; |             if(!magic_activate()) break; | ||||||
|         if(!magic_wipe()) continue; |             if(magic_dev->type == MagicTypeClassicGen1) { | ||||||
|         if(!magic_data_access_cmd()) continue; |                 if(!magic_gen1_wupa()) break; | ||||||
|         if(!magic_write_blk(0, &block)) continue; |                 if(!card_found_notified) { | ||||||
|         nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); |                     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; |                         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(); |     magic_deactivate(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <lib/nfc/nfc_device.h> | #include <lib/nfc/nfc_device.h> | ||||||
|  | #include "nfc_magic.h" | ||||||
| 
 | 
 | ||||||
| typedef struct NfcMagicWorker NfcMagicWorker; | typedef struct NfcMagicWorker NfcMagicWorker; | ||||||
| 
 | 
 | ||||||
| @ -9,6 +10,7 @@ typedef enum { | |||||||
| 
 | 
 | ||||||
|     NfcMagicWorkerStateCheck, |     NfcMagicWorkerStateCheck, | ||||||
|     NfcMagicWorkerStateWrite, |     NfcMagicWorkerStateWrite, | ||||||
|  |     NfcMagicWorkerStateRekey, | ||||||
|     NfcMagicWorkerStateWipe, |     NfcMagicWorkerStateWipe, | ||||||
| 
 | 
 | ||||||
|     NfcMagicWorkerStateStop, |     NfcMagicWorkerStateStop, | ||||||
| @ -33,6 +35,8 @@ void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker); | |||||||
| void nfc_magic_worker_start( | void nfc_magic_worker_start( | ||||||
|     NfcMagicWorker* nfc_magic_worker, |     NfcMagicWorker* nfc_magic_worker, | ||||||
|     NfcMagicWorkerState state, |     NfcMagicWorkerState state, | ||||||
|  |     NfcMagicDevice* magic_dev, | ||||||
|     NfcDeviceData* dev_data, |     NfcDeviceData* dev_data, | ||||||
|  |     uint32_t new_password, | ||||||
|     NfcMagicWorkerCallback callback, |     NfcMagicWorkerCallback callback, | ||||||
|     void* context); |     void* context); | ||||||
|  | |||||||
| @ -3,11 +3,14 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | 
 | ||||||
| #include "nfc_magic_worker.h" | #include "nfc_magic_worker.h" | ||||||
|  | #include "lib/magic/common.h" | ||||||
| 
 | 
 | ||||||
| struct NfcMagicWorker { | struct NfcMagicWorker { | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
| 
 | 
 | ||||||
|  |     NfcMagicDevice* magic_dev; | ||||||
|     NfcDeviceData* dev_data; |     NfcDeviceData* dev_data; | ||||||
|  |     uint32_t new_password; | ||||||
| 
 | 
 | ||||||
|     NfcMagicWorkerCallback callback; |     NfcMagicWorkerCallback callback; | ||||||
|     void* context; |     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_write(NfcMagicWorker* nfc_magic_worker); | ||||||
| 
 | 
 | ||||||
|  | void nfc_magic_worker_rekey(NfcMagicWorker* nfc_magic_worker); | ||||||
|  | 
 | ||||||
| void nfc_magic_worker_wipe(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_start( | ||||||
|         nfc_magic->worker, |         nfc_magic->worker, | ||||||
|         NfcMagicWorkerStateCheck, |         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_check_worker_callback, | ||||||
|         nfc_magic); |         nfc_magic); | ||||||
|     nfc_magic_blink_start(nfc_magic); |     nfc_magic_blink_start(nfc_magic); | ||||||
|  | |||||||
| @ -1,4 +1,8 @@ | |||||||
| ADD_SCENE(nfc_magic, start, Start) | 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, file_select, FileSelect) | ||||||
| ADD_SCENE(nfc_magic, write_confirm, WriteConfirm) | ADD_SCENE(nfc_magic, write_confirm, WriteConfirm) | ||||||
| ADD_SCENE(nfc_magic, wrong_card, WrongCard) | 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, check, Check) | ||||||
| ADD_SCENE(nfc_magic, not_magic, NotMagic) | ADD_SCENE(nfc_magic, not_magic, NotMagic) | ||||||
| ADD_SCENE(nfc_magic, magic_info, MagicInfo) | 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, Wipe) | ||||||
| ADD_SCENE(nfc_magic, wipe_fail, WipeFail) | ADD_SCENE(nfc_magic, wipe_fail, WipeFail) | ||||||
|  | |||||||
| @ -1,22 +1,60 @@ | |||||||
| #include "../nfc_magic_i.h" | #include "../nfc_magic_i.h" | ||||||
| 
 | 
 | ||||||
| static bool nfc_magic_scene_file_select_is_file_suitable(NfcDevice* nfc_dev) { | static bool nfc_magic_scene_file_select_is_file_suitable(NfcMagic* nfc_magic) { | ||||||
|     return (nfc_dev->format == NfcDeviceSaveFormatMifareClassic) && |     NfcDevice* nfc_dev = nfc_magic->source_dev; | ||||||
|            (nfc_dev->dev_data.mf_classic_data.type == MfClassicType1k) && |     if(nfc_dev->format == NfcDeviceSaveFormatMifareClassic) { | ||||||
|            (nfc_dev->dev_data.nfc_data.uid_len == 4); |         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) { | void nfc_magic_scene_file_select_on_enter(void* context) { | ||||||
|     NfcMagic* nfc_magic = context; |     NfcMagic* nfc_magic = context; | ||||||
|     // Process file_select return
 |     // 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)) { |     if(!furi_string_size(nfc_magic->source_dev->load_path)) { | ||||||
|         furi_string_set_str(nfc_magic->nfc_dev->load_path, NFC_APP_FOLDER); |         furi_string_set_str(nfc_magic->source_dev->load_path, NFC_APP_FOLDER); | ||||||
|     } |     } | ||||||
| 
 |     if(nfc_file_select(nfc_magic->source_dev)) { | ||||||
|     if(nfc_file_select(nfc_magic->nfc_dev)) { |         if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic)) { | ||||||
|         if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic->nfc_dev)) { |  | ||||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm); |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm); | ||||||
|         } else { |         } else { | ||||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrongCard); |             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) { | void nfc_magic_scene_file_select_on_exit(void* context) { | ||||||
|     NfcMagic* nfc_magic = 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 "../nfc_magic_i.h" | ||||||
|  | #include "../lib/magic/types.h" | ||||||
| 
 | 
 | ||||||
| void nfc_magic_scene_magic_info_widget_callback( | void nfc_magic_scene_magic_info_widget_callback( | ||||||
|     GuiButtonType result, |     GuiButtonType result, | ||||||
| @ -13,14 +14,18 @@ void nfc_magic_scene_magic_info_widget_callback( | |||||||
| void nfc_magic_scene_magic_info_on_enter(void* context) { | void nfc_magic_scene_magic_info_on_enter(void* context) { | ||||||
|     NfcMagic* nfc_magic = context; |     NfcMagic* nfc_magic = context; | ||||||
|     Widget* widget = nfc_magic->widget; |     Widget* widget = nfc_magic->widget; | ||||||
|  |     const char* card_type = nfc_magic_type(nfc_magic->dev->type); | ||||||
| 
 | 
 | ||||||
|     notification_message(nfc_magic->notifications, &sequence_success); |     notification_message(nfc_magic->notifications, &sequence_success); | ||||||
| 
 | 
 | ||||||
|     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); |     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); | ||||||
|     widget_add_string_element( |     widget_add_string_element( | ||||||
|         widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected"); |         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_add_button_element( | ||||||
|         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, nfc_magic); |         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
 |     // Setup and start worker
 | ||||||
|     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); |     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.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == GuiButtonTypeLeft) { |         if(event.event == GuiButtonTypeLeft) { | ||||||
|             consumed = scene_manager_previous_scene(nfc_magic->scene_manager); |             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; |     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" | #include "../nfc_magic_i.h" | ||||||
| enum SubmenuIndex { | enum SubmenuIndex { | ||||||
|     SubmenuIndexCheck, |     SubmenuIndexCheck, | ||||||
|     SubmenuIndexWriteGen1A, |     SubmenuIndexAuthenticateGen4, | ||||||
|     SubmenuIndexWipe, |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| void nfc_magic_scene_start_submenu_callback(void* context, uint32_t index) { | 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); |         nfc_magic); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, |         submenu, | ||||||
|         "Write Gen1A", |         "Authenticate Gen4", | ||||||
|         SubmenuIndexWriteGen1A, |         SubmenuIndexAuthenticateGen4, | ||||||
|         nfc_magic_scene_start_submenu_callback, |         nfc_magic_scene_start_submenu_callback, | ||||||
|         nfc_magic); |         nfc_magic); | ||||||
|     submenu_add_item( |  | ||||||
|         submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_start_submenu_callback, nfc_magic); |  | ||||||
| 
 | 
 | ||||||
|     submenu_set_selected_item( |     submenu_set_selected_item( | ||||||
|         submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart)); |         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.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == SubmenuIndexCheck) { |         if(event.event == SubmenuIndexCheck) { | ||||||
|  |             nfc_magic->dev->password = MAGIC_GEN4_DEFAULT_PWD; | ||||||
|             scene_manager_set_scene_state( |             scene_manager_set_scene_state( | ||||||
|                 nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexCheck); |                 nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexCheck); | ||||||
|             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexWriteGen1A) { |         } else if(event.event == SubmenuIndexAuthenticateGen4) { | ||||||
|             // Explicitly save state in each branch so that the
 |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneKeyInput); | ||||||
|             // 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; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -22,7 +22,12 @@ static void nfc_magic_scene_wipe_setup_view(NfcMagic* nfc_magic) { | |||||||
|     if(state == NfcMagicSceneWipeStateCardSearch) { |     if(state == NfcMagicSceneWipeStateCardSearch) { | ||||||
|         popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); |         popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); | ||||||
|         popup_set_text( |         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 { |     } else { | ||||||
|         popup_set_icon(popup, 12, 23, &I_Loading_24); |         popup_set_icon(popup, 12, 23, &I_Loading_24); | ||||||
|         popup_set_header(popup, "Wiping\nDon't move...", 52, 32, AlignLeft, AlignCenter); |         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_start( | ||||||
|         nfc_magic->worker, |         nfc_magic->worker, | ||||||
|         NfcMagicWorkerStateWipe, |         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_wipe_worker_callback, | ||||||
|         nfc_magic); |         nfc_magic); | ||||||
|     nfc_magic_blink_start(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) { |     if(state == NfcMagicSceneWriteStateCardSearch) { | ||||||
|         popup_set_text( |         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); |         popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); | ||||||
|     } else { |     } else { | ||||||
|         popup_set_icon(popup, 12, 23, &I_Loading_24); |         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_start( | ||||||
|         nfc_magic->worker, |         nfc_magic->worker, | ||||||
|         NfcMagicWorkerStateWrite, |         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_write_worker_callback, | ||||||
|         nfc_magic); |         nfc_magic); | ||||||
|     nfc_magic_blink_start(nfc_magic); |     nfc_magic_blink_start(nfc_magic); | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ void nfc_magic_scene_wrong_card_on_enter(void* context) { | |||||||
|         AlignLeft, |         AlignLeft, | ||||||
|         AlignTop, |         AlignTop, | ||||||
|         FontSecondary, |         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_add_button_element( | ||||||
|         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic); |         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) |     (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     WorkerEvtToggle = (1 << 0), |     WorkerEvtStartStop = (1 << 0), | ||||||
|     WorkerEvtEnd = (1 << 1), |     WorkerEvtPauseResume = (1 << 1), | ||||||
|     WorkerEvtConnect = (1 << 2), |     WorkerEvtEnd = (1 << 2), | ||||||
|     WorkerEvtDisconnect = (1 << 3), |     WorkerEvtConnect = (1 << 3), | ||||||
|  |     WorkerEvtDisconnect = (1 << 4), | ||||||
| } WorkerEvtFlags; | } WorkerEvtFlags; | ||||||
| 
 | 
 | ||||||
| static const char ducky_cmd_id[] = {"ID"}; | static const char ducky_cmd_id[] = {"ID"}; | ||||||
| @ -372,6 +373,7 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|     BadUsbScript* bad_usb = context; |     BadUsbScript* bad_usb = context; | ||||||
| 
 | 
 | ||||||
|     BadUsbWorkerState worker_state = BadUsbStateInit; |     BadUsbWorkerState worker_state = BadUsbStateInit; | ||||||
|  |     BadUsbWorkerState pause_state = BadUsbStateRunning; | ||||||
|     int32_t delay_val = 0; |     int32_t delay_val = 0; | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(WORKER_TAG, "Init"); |     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
 |         } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
 | ||||||
|             uint32_t flags = bad_usb_flags_get( |             uint32_t flags = bad_usb_flags_get( | ||||||
|                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); |                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); | ||||||
| 
 | 
 | ||||||
|             if(flags & WorkerEvtEnd) { |             if(flags & WorkerEvtEnd) { | ||||||
|                 break; |                 break; | ||||||
|             } else if(flags & WorkerEvtConnect) { |             } else if(flags & WorkerEvtConnect) { | ||||||
|                 worker_state = BadUsbStateIdle; // Ready to run
 |                 worker_state = BadUsbStateIdle; // Ready to run
 | ||||||
|             } else if(flags & WorkerEvtToggle) { |             } else if(flags & WorkerEvtStartStop) { | ||||||
|                 worker_state = BadUsbStateWillRun; // Will run when USB is connected
 |                 worker_state = BadUsbStateWillRun; // Will run when USB is connected
 | ||||||
|             } |             } | ||||||
|             bad_usb->st.state = worker_state; |             bad_usb->st.state = worker_state; | ||||||
| 
 | 
 | ||||||
|         } else if(worker_state == BadUsbStateIdle) { // State: ready to start
 |         } else if(worker_state == BadUsbStateIdle) { // State: ready to start
 | ||||||
|             uint32_t flags = bad_usb_flags_get( |             uint32_t flags = bad_usb_flags_get( | ||||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever); |                 WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever); | ||||||
| 
 | 
 | ||||||
|             if(flags & WorkerEvtEnd) { |             if(flags & WorkerEvtEnd) { | ||||||
|                 break; |                 break; | ||||||
|             } else if(flags & WorkerEvtToggle) { // Start executing script
 |             } else if(flags & WorkerEvtStartStop) { // Start executing script
 | ||||||
|                 DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); |                 DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); | ||||||
|                 delay_val = 0; |                 delay_val = 0; | ||||||
|                 bad_usb->buf_len = 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
 |         } else if(worker_state == BadUsbStateWillRun) { // State: start on connection
 | ||||||
|             uint32_t flags = bad_usb_flags_get( |             uint32_t flags = bad_usb_flags_get( | ||||||
|                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); |                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); | ||||||
| 
 | 
 | ||||||
|             if(flags & WorkerEvtEnd) { |             if(flags & WorkerEvtEnd) { | ||||||
|                 break; |                 break; | ||||||
| @ -458,17 +460,17 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|                 storage_file_seek(script_file, 0, true); |                 storage_file_seek(script_file, 0, true); | ||||||
|                 // extra time for PC to recognize Flipper as keyboard
 |                 // extra time for PC to recognize Flipper as keyboard
 | ||||||
|                 flags = furi_thread_flags_wait( |                 flags = furi_thread_flags_wait( | ||||||
|                     WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtToggle, |                     WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop, | ||||||
|                     FuriFlagWaitAny | FuriFlagNoClear, |                     FuriFlagWaitAny | FuriFlagNoClear, | ||||||
|                     1500); |                     1500); | ||||||
|                 if(flags == (unsigned)FuriFlagErrorTimeout) { |                 if(flags == (unsigned)FuriFlagErrorTimeout) { | ||||||
|                     // If nothing happened - start script execution
 |                     // If nothing happened - start script execution
 | ||||||
|                     worker_state = BadUsbStateRunning; |                     worker_state = BadUsbStateRunning; | ||||||
|                 } else if(flags & WorkerEvtToggle) { |                 } else if(flags & WorkerEvtStartStop) { | ||||||
|                     worker_state = BadUsbStateIdle; |                     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; |                 worker_state = BadUsbStateNotConnected; | ||||||
|             } |             } | ||||||
|             bad_usb->st.state = worker_state; |             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
 |         } else if(worker_state == BadUsbStateRunning) { // State: running
 | ||||||
|             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); |             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); | ||||||
|             uint32_t flags = furi_thread_flags_wait( |             uint32_t flags = furi_thread_flags_wait( | ||||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); |                 WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, | ||||||
|  |                 FuriFlagWaitAny, | ||||||
|  |                 delay_cur); | ||||||
| 
 | 
 | ||||||
|             delay_val -= delay_cur; |             delay_val -= delay_cur; | ||||||
|             if(!(flags & FuriFlagError)) { |             if(!(flags & FuriFlagError)) { | ||||||
|                 if(flags & WorkerEvtEnd) { |                 if(flags & WorkerEvtEnd) { | ||||||
|                     break; |                     break; | ||||||
|                 } else if(flags & WorkerEvtToggle) { |                 } else if(flags & WorkerEvtStartStop) { | ||||||
|                     worker_state = BadUsbStateIdle; // Stop executing script
 |                     worker_state = BadUsbStateIdle; // Stop executing script
 | ||||||
|                     furi_hal_hid_kb_release_all(); |                     furi_hal_hid_kb_release_all(); | ||||||
|                 } else if(flags & WorkerEvtDisconnect) { |                 } else if(flags & WorkerEvtDisconnect) { | ||||||
|                     worker_state = BadUsbStateNotConnected; // USB disconnected
 |                     worker_state = BadUsbStateNotConnected; // USB disconnected
 | ||||||
|                     furi_hal_hid_kb_release_all(); |                     furi_hal_hid_kb_release_all(); | ||||||
|  |                 } else if(flags & WorkerEvtPauseResume) { | ||||||
|  |                     pause_state = BadUsbStateRunning; | ||||||
|  |                     worker_state = BadUsbStatePaused; // Pause
 | ||||||
|                 } |                 } | ||||||
|                 bad_usb->st.state = worker_state; |                 bad_usb->st.state = worker_state; | ||||||
|                 continue; |                 continue; | ||||||
| @ -526,13 +533,13 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|                 furi_check((flags & FuriFlagError) == 0); |                 furi_check((flags & FuriFlagError) == 0); | ||||||
|             } |             } | ||||||
|         } else if(worker_state == BadUsbStateWaitForBtn) { // State: Wait for button Press
 |         } else if(worker_state == BadUsbStateWaitForBtn) { // State: Wait for button Press
 | ||||||
|             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); |             uint32_t flags = bad_usb_flags_get( | ||||||
|             uint32_t flags = furi_thread_flags_wait( |                 WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, | ||||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); |                 FuriWaitForever); | ||||||
|             if(!(flags & FuriFlagError)) { |             if(!(flags & FuriFlagError)) { | ||||||
|                 if(flags & WorkerEvtEnd) { |                 if(flags & WorkerEvtEnd) { | ||||||
|                     break; |                     break; | ||||||
|                 } else if(flags & WorkerEvtToggle) { |                 } else if(flags & WorkerEvtStartStop) { | ||||||
|                     delay_val = 0; |                     delay_val = 0; | ||||||
|                     worker_state = BadUsbStateRunning; |                     worker_state = BadUsbStateRunning; | ||||||
|                 } else if(flags & WorkerEvtDisconnect) { |                 } else if(flags & WorkerEvtDisconnect) { | ||||||
| @ -542,21 +549,55 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|                 bad_usb->st.state = worker_state; |                 bad_usb->st.state = worker_state; | ||||||
|                 continue; |                 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
 |         } else if(worker_state == BadUsbStateStringDelay) { // State: print string with delays
 | ||||||
|             uint32_t flags = furi_thread_flags_wait( |             uint32_t flags = bad_usb_flags_get( | ||||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, |                 WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, | ||||||
|                 FuriFlagWaitAny, |  | ||||||
|                 bad_usb->stringdelay); |                 bad_usb->stringdelay); | ||||||
| 
 | 
 | ||||||
|             if(!(flags & FuriFlagError)) { |             if(!(flags & FuriFlagError)) { | ||||||
|                 if(flags & WorkerEvtEnd) { |                 if(flags & WorkerEvtEnd) { | ||||||
|                     break; |                     break; | ||||||
|                 } else if(flags & WorkerEvtToggle) { |                 } else if(flags & WorkerEvtStartStop) { | ||||||
|                     worker_state = BadUsbStateIdle; // Stop executing script
 |                     worker_state = BadUsbStateIdle; // Stop executing script
 | ||||||
|                     furi_hal_hid_kb_release_all(); |                     furi_hal_hid_kb_release_all(); | ||||||
|                 } else if(flags & WorkerEvtDisconnect) { |                 } else if(flags & WorkerEvtDisconnect) { | ||||||
|                     worker_state = BadUsbStateNotConnected; // USB disconnected
 |                     worker_state = BadUsbStateNotConnected; // USB disconnected
 | ||||||
|                     furi_hal_hid_kb_release_all(); |                     furi_hal_hid_kb_release_all(); | ||||||
|  |                 } else if(flags & WorkerEvtPauseResume) { | ||||||
|  |                     pause_state = BadUsbStateStringDelay; | ||||||
|  |                     worker_state = BadUsbStatePaused; // Pause
 | ||||||
|                 } |                 } | ||||||
|                 bad_usb->st.state = worker_state; |                 bad_usb->st.state = worker_state; | ||||||
|                 continue; |                 continue; | ||||||
| @ -651,9 +692,14 @@ void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layou | |||||||
|     storage_file_free(layout_file); |     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_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) { | BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb) { | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ typedef enum { | |||||||
|     BadUsbStateDelay, |     BadUsbStateDelay, | ||||||
|     BadUsbStateStringDelay, |     BadUsbStateStringDelay, | ||||||
|     BadUsbStateWaitForBtn, |     BadUsbStateWaitForBtn, | ||||||
|  |     BadUsbStatePaused, | ||||||
|     BadUsbStateDone, |     BadUsbStateDone, | ||||||
|     BadUsbStateScriptError, |     BadUsbStateScriptError, | ||||||
|     BadUsbStateFileError, |     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_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); | 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; |             consumed = true; | ||||||
|         } else if(event.event == InputKeyOk) { |         } 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; |             consumed = true; | ||||||
|         } |         } | ||||||
|     } else if(event.type == SceneManagerEventTypeTick) { |     } else if(event.type == SceneManagerEventTypeTick) { | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ typedef struct { | |||||||
|     char file_name[MAX_NAME_LEN]; |     char file_name[MAX_NAME_LEN]; | ||||||
|     char layout[MAX_NAME_LEN]; |     char layout[MAX_NAME_LEN]; | ||||||
|     BadUsbState state; |     BadUsbState state; | ||||||
|  |     bool pause_wait; | ||||||
|     uint8_t anim_frame; |     uint8_t anim_frame; | ||||||
| } BadUsbModel; | } BadUsbModel; | ||||||
| 
 | 
 | ||||||
| @ -31,11 +32,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | |||||||
|     if(strlen(model->layout) == 0) { |     if(strlen(model->layout) == 0) { | ||||||
|         furi_string_set(disp_str, "(default)"); |         furi_string_set(disp_str, "(default)"); | ||||||
|     } else { |     } else { | ||||||
|         furi_string_reset(disp_str); |         furi_string_printf(disp_str, "(%s)", model->layout); | ||||||
|         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, ')'); |  | ||||||
|     } |     } | ||||||
|     elements_string_fit_width(canvas, disp_str, 128 - 2); |     elements_string_fit_width(canvas, disp_str, 128 - 2); | ||||||
|     canvas_draw_str( |     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); |     canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); | ||||||
| 
 | 
 | ||||||
|     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || |     BadUsbWorkerState state = model->state.state; | ||||||
|        (model->state.state == BadUsbStateNotConnected)) { | 
 | ||||||
|  |     if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || | ||||||
|  |        (state == BadUsbStateNotConnected)) { | ||||||
|         elements_button_center(canvas, "Run"); |         elements_button_center(canvas, "Run"); | ||||||
|         elements_button_left(canvas, "Config"); |         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"); |         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"); |         elements_button_center(canvas, "Press to continue"); | ||||||
|     } else if(model->state.state == BadUsbStateWillRun) { |     } else if(state == BadUsbStateWillRun) { | ||||||
|         elements_button_center(canvas, "Cancel"); |         elements_button_center(canvas, "Cancel"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(model->state.state == BadUsbStateNotConnected) { |     if(state == BadUsbStateNotConnected) { | ||||||
|         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); |         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); | ||||||
|         canvas_set_font(canvas, FontPrimary); |         canvas_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); |         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to USB"); |         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_draw_icon(canvas, 4, 26, &I_Clock_18x18); | ||||||
|         canvas_set_font(canvas, FontPrimary); |         canvas_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); |         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); |         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_draw_icon(canvas, 4, 26, &I_Error_18x18); | ||||||
|         canvas_set_font(canvas, FontPrimary); |         canvas_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); |         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); |         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_draw_icon(canvas, 4, 26, &I_Error_18x18); | ||||||
|         canvas_set_font(canvas, FontPrimary); |         canvas_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); |         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_draw_str_aligned( | ||||||
|             canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); |             canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||||
|         furi_string_reset(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_draw_icon(canvas, 4, 26, &I_Smile_18x18); | ||||||
|         canvas_set_font(canvas, FontBigNumbers); |         canvas_set_font(canvas, FontBigNumbers); | ||||||
|         canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); |         canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); | ||||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); |         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||||
|     } else if(model->state.state == BadUsbStateRunning) { |     } else if(state == BadUsbStateRunning) { | ||||||
|         if(model->anim_frame == 0) { |         if(model->anim_frame == 0) { | ||||||
|             canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); |             canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); | ||||||
|         } else { |         } 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)); |             canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||||
|         furi_string_reset(disp_str); |         furi_string_reset(disp_str); | ||||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); |         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_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); | ||||||
|         canvas_set_font(canvas, FontBigNumbers); |         canvas_set_font(canvas, FontBigNumbers); | ||||||
|         canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); |         canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); | ||||||
|         furi_string_reset(disp_str); |         furi_string_reset(disp_str); | ||||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); |         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||||
|     } else if(model->state.state == BadUsbStateDelay) { |     } else if(state == BadUsbStateDelay) { | ||||||
|         if(model->anim_frame == 0) { |         if(model->anim_frame == 0) { | ||||||
|             canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); |             canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); | ||||||
|         } else { |         } else { | ||||||
| @ -129,6 +134,22 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | |||||||
|         canvas_draw_str_aligned( |         canvas_draw_str_aligned( | ||||||
|             canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); |             canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||||
|         furi_string_reset(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 { |     } else { | ||||||
|         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); |         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; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == InputTypeShort) { |     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; |             consumed = true; | ||||||
|             furi_assert(bad_usb->callback); |             furi_assert(bad_usb->callback); | ||||||
|             bad_usb->callback(event->key, bad_usb->context); |             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)); |             memcpy(&(model->state), st, sizeof(BadUsbState)); | ||||||
|             model->anim_frame ^= 1; |             model->anim_frame ^= 1; | ||||||
|  |             if(model->state.state == BadUsbStatePaused) { | ||||||
|  |                 model->pause_wait = false; | ||||||
|  |             } | ||||||
|         }, |         }, | ||||||
|         true); |         true); | ||||||
| } | } | ||||||
|  | |||||||
| @ -312,7 +312,8 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { | |||||||
| 
 | 
 | ||||||
|     if(infrared_signal_is_raw(signal)) { |     if(infrared_signal_is_raw(signal)) { | ||||||
|         InfraredRawSignal* raw = infrared_signal_get_raw_signal(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 { |     } else { | ||||||
|         InfraredMessage* message = infrared_signal_get_message(signal); |         InfraredMessage* message = infrared_signal_get_message(signal); | ||||||
|         infrared_worker_set_decoded_signal(infrared->worker, message); |         infrared_worker_set_decoded_signal(infrared->worker, message); | ||||||
|  | |||||||
| @ -52,6 +52,7 @@ void nfc_scene_device_info_on_enter(void* context) { | |||||||
|         } |         } | ||||||
|     } else if( |     } else if( | ||||||
|         dev_data->protocol == NfcDeviceProtocolMifareClassic || |         dev_data->protocol == NfcDeviceProtocolMifareClassic || | ||||||
|  |         dev_data->protocol == NfcDeviceProtocolMifareDesfire || | ||||||
|         dev_data->protocol == NfcDeviceProtocolMifareUl) { |         dev_data->protocol == NfcDeviceProtocolMifareUl) { | ||||||
|         furi_string_set(temp_str, nfc->dev->dev_data.parsed_data); |         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; |     Widget* widget = nfc->widget; | ||||||
| 
 | 
 | ||||||
|     // Prepare string for data display
 |     // 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:"); |         furi_string_cat_printf(temp_str, "UID:"); | ||||||
|         for(size_t i = 0; i < nfc_data->uid_len; i++) { |         for(size_t i = 0; i < nfc_data->uid_len; i++) { | ||||||
|             furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[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) { |         if(n_files != 1) { | ||||||
|             furi_string_push_back(temp_str, 's'); |             furi_string_push_back(temp_str, 's'); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     notification_message_block(nfc->notifications, &sequence_set_green_255); |     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; |     Submenu* submenu = nfc->submenu; | ||||||
|     MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data; |     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_add_item( | ||||||
|             submenu, |             submenu, | ||||||
|             "Unlock", |             "Unlock", | ||||||
| @ -29,12 +29,14 @@ void nfc_scene_mf_ultralight_menu_on_enter(void* context) { | |||||||
|     } |     } | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, "Save", SubmenuIndexSave, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); |         submenu, "Save", SubmenuIndexSave, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); | ||||||
|  |     if(mf_ul_emulation_supported(data)) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
|             submenu, |             submenu, | ||||||
|             "Emulate", |             "Emulate", | ||||||
|             SubmenuIndexEmulate, |             SubmenuIndexEmulate, | ||||||
|             nfc_scene_mf_ultralight_menu_submenu_callback, |             nfc_scene_mf_ultralight_menu_submenu_callback, | ||||||
|             nfc); |             nfc); | ||||||
|  |     } | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); |         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( |         furi_string_cat_printf( | ||||||
|             temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type)); |             temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type)); | ||||||
|     } else if(protocol == NfcDeviceProtocolMifareDesfire) { |     } 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 { |     } else { | ||||||
|         furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n"); |         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); |                 nfc); | ||||||
|         } |         } | ||||||
|     } else if( |     } 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) { |         nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
|             submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); |             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_add_item( | ||||||
|         submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); |         submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); | ||||||
|     if(nfc->dev->format == NfcDeviceSaveFormatMifareUl && |     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)) { |        !mf_ul_is_full_capture(&nfc->dev->dev_data.mf_ul_data)) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
|             submenu, |             submenu, | ||||||
| @ -146,6 +148,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
|                 application_info_present = true; |                 application_info_present = true; | ||||||
|             } else if( |             } else if( | ||||||
|                 dev_data->protocol == NfcDeviceProtocolMifareClassic || |                 dev_data->protocol == NfcDeviceProtocolMifareClassic || | ||||||
|  |                 dev_data->protocol == NfcDeviceProtocolMifareDesfire || | ||||||
|                 dev_data->protocol == NfcDeviceProtocolMifareUl) { |                 dev_data->protocol == NfcDeviceProtocolMifareUl) { | ||||||
|                 application_info_present = nfc_supported_card_verify_and_parse(dev_data); |                 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 */ |         1, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */ | ||||||
|     SubGhzErrorTypeOnlyRX = |     SubGhzErrorTypeOnlyRX = | ||||||
|         2, /** Transmission on this frequency is blocked by regional settings */ |         2, /** Transmission on this frequency is blocked by regional settings */ | ||||||
|  |     SubGhzErrorTypeParserOthers = 3, /** Error in protocol parameters description */ | ||||||
| } SubGhzErrorType; | } SubGhzErrorType; | ||||||
|  | |||||||
| @ -40,15 +40,26 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { | |||||||
|         } else if(event.event == SubGhzCustomEventSceneRpcButtonPress) { |         } else if(event.event == SubGhzCustomEventSceneRpcButtonPress) { | ||||||
|             bool result = false; |             bool result = false; | ||||||
|             if((state == SubGhzRpcStateLoaded)) { |             if((state == SubGhzRpcStateLoaded)) { | ||||||
|                 result = subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); |                 switch( | ||||||
|                 state = SubGhzRpcStateTx; |                     subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) { | ||||||
|                 if(result) subghz_blink_start(subghz); |                 case SubGhzTxRxStartTxStateErrorOnlyRx: | ||||||
|             } |  | ||||||
|             if(!result) { |  | ||||||
|                     rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeOnlyRX); |                     rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeOnlyRX); | ||||||
|                     rpc_system_app_set_error_text( |                     rpc_system_app_set_error_text( | ||||||
|                         subghz->rpc_ctx, |                         subghz->rpc_ctx, | ||||||
|                         "Transmission on this frequency is restricted in your region"); |                         "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); |             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); | ||||||
|         } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { |         } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { | ||||||
| @ -56,9 +67,9 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { | |||||||
|             if(state == SubGhzRpcStateTx) { |             if(state == SubGhzRpcStateTx) { | ||||||
|                 subghz_txrx_stop(subghz->txrx); |                 subghz_txrx_stop(subghz->txrx); | ||||||
|                 subghz_blink_stop(subghz); |                 subghz_blink_stop(subghz); | ||||||
|                 state = SubGhzRpcStateIdle; |  | ||||||
|                 result = true; |                 result = true; | ||||||
|             } |             } | ||||||
|  |             state = SubGhzRpcStateIdle; | ||||||
|             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonRelease, result); |             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonRelease, result); | ||||||
|         } else if(event.event == SubGhzCustomEventSceneRpcLoad) { |         } else if(event.event == SubGhzCustomEventSceneRpcLoad) { | ||||||
|             bool result = false; |             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) { | void subghz_scene_rpc_on_exit(void* context) { | ||||||
|     SubGhz* subghz = context; |     SubGhz* subghz = context; | ||||||
|     SubGhzRpcState state = scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneRpc); |     SubGhzRpcState state = scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneRpc); | ||||||
|     if(state != SubGhzRpcStateIdle) { |     if(state == SubGhzRpcStateTx) { | ||||||
|         subghz_txrx_stop(subghz->txrx); |         subghz_txrx_stop(subghz->txrx); | ||||||
|         subghz_blink_stop(subghz); |         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_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))) { |         while(!(furi_hal_subghz_is_async_tx_complete() || cli_cmd_interrupt_received(cli))) { | ||||||
|             printf("."); |             printf("."); | ||||||
|             fflush(stdout); |             fflush(stdout); | ||||||
|             furi_delay_ms(333); |             furi_delay_ms(333); | ||||||
|         } |         } | ||||||
|         furi_hal_subghz_stop_async_tx(); |         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(); |     furi_hal_power_suppress_charge_exit(); | ||||||
| 
 | 
 | ||||||
|     flipper_format_free(flipper_format); |     flipper_format_free(flipper_format); | ||||||
|  | |||||||
| @ -147,6 +147,9 @@ void desktop_lock(Desktop* desktop) { | |||||||
|         desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); |         desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); | ||||||
|     scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); |     scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); | ||||||
|     notification_message(desktop->notification, &sequence_display_backlight_off_delay_1000); |     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) { | void desktop_unlock(Desktop* desktop) { | ||||||
| @ -165,6 +168,9 @@ void desktop_unlock(Desktop* desktop) { | |||||||
|         cli_session_open(cli, &cli_vcp); |         cli_session_open(cli, &cli_vcp); | ||||||
|         furi_record_close(RECORD_CLI); |         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) { | void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { | ||||||
| @ -308,63 +314,13 @@ Desktop* desktop_alloc() { | |||||||
|     desktop->auto_lock_timer = |     desktop->auto_lock_timer = | ||||||
|         furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop); |         furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop); | ||||||
| 
 | 
 | ||||||
|  |     desktop->status_pubsub = furi_pubsub_alloc(); | ||||||
|  | 
 | ||||||
|     furi_record_create(RECORD_DESKTOP, desktop); |     furi_record_create(RECORD_DESKTOP, desktop); | ||||||
| 
 | 
 | ||||||
|     return 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) { | static bool desktop_check_file_flag(const char* flag_path) { | ||||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); |     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||||
|     bool exists = storage_common_stat(storage, flag_path, NULL) == FSE_OK; |     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); |     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) { | int32_t desktop_srv(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
| 
 | 
 | ||||||
| @ -427,7 +388,8 @@ int32_t desktop_srv(void* p) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_run(desktop->view_dispatcher); |     view_dispatcher_run(desktop->view_dispatcher); | ||||||
|     desktop_free(desktop); | 
 | ||||||
|  |     furi_crash("That was unexpected"); | ||||||
| 
 | 
 | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
| typedef struct Desktop Desktop; | typedef struct Desktop Desktop; | ||||||
| 
 | 
 | ||||||
| #define RECORD_DESKTOP "desktop" | #define RECORD_DESKTOP "desktop" | ||||||
| @ -7,3 +9,9 @@ typedef struct Desktop Desktop; | |||||||
| bool desktop_api_is_locked(Desktop* instance); | bool desktop_api_is_locked(Desktop* instance); | ||||||
| 
 | 
 | ||||||
| void desktop_api_unlock(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; |     FuriPubSubSubscription* input_events_subscription; | ||||||
|     FuriTimer* auto_lock_timer; |     FuriTimer* auto_lock_timer; | ||||||
| 
 | 
 | ||||||
|  |     FuriPubSub* status_pubsub; | ||||||
|  | 
 | ||||||
|     bool in_transition; |     bool in_transition; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -36,8 +36,6 @@ | |||||||
| #define MIN_PIN_SIZE 4 | #define MIN_PIN_SIZE 4 | ||||||
| #define MAX_APP_LENGTH 128 | #define MAX_APP_LENGTH 128 | ||||||
| 
 | 
 | ||||||
| #define FAP_LOADER_APP_NAME "Applications" |  | ||||||
| 
 |  | ||||||
| typedef struct { | typedef struct { | ||||||
|     InputKey data[MAX_PIN_SIZE]; |     InputKey data[MAX_PIN_SIZE]; | ||||||
|     uint8_t length; |     uint8_t length; | ||||||
|  | |||||||
| @ -16,6 +16,8 @@ | |||||||
| #define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") | #define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") | ||||||
| #define CLOCK_APP EXT_PATH("/apps/Tools/clock.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) { | static void desktop_scene_main_new_idle_animation_callback(void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     Desktop* desktop = context; |     Desktop* desktop = context; | ||||||
| @ -77,6 +79,21 @@ static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* | |||||||
|     } while(false); |     } 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) { | void desktop_scene_main_callback(DesktopEvent event, void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     if(desktop->in_transition) return; |     if(desktop->in_transition) return; | ||||||
| @ -141,40 +158,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|         case DesktopMainEventOpenFavoritePrimary: |         case DesktopMainEventOpenFavoritePrimary: | ||||||
|             DESKTOP_SETTINGS_LOAD(&desktop->settings); |             DESKTOP_SETTINGS_LOAD(&desktop->settings); | ||||||
|             if(desktop->settings.favorite_primary.is_external) { |             desktop_scene_main_start_favorite(desktop, &desktop->settings.favorite_primary); | ||||||
|                 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); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|         case DesktopMainEventOpenFavoriteSecondary: |         case DesktopMainEventOpenFavoriteSecondary: | ||||||
|             DESKTOP_SETTINGS_LOAD(&desktop->settings); |             DESKTOP_SETTINGS_LOAD(&desktop->settings); | ||||||
|             if(desktop->settings.favorite_secondary.is_external) { |             desktop_scene_main_start_favorite(desktop, &desktop->settings.favorite_secondary); | ||||||
|                 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); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|         case DesktopAnimationEventCheckAnimation: |         case DesktopAnimationEventCheckAnimation: | ||||||
|  | |||||||
| @ -89,15 +89,6 @@ Dolphin* dolphin_alloc() { | |||||||
|     return dolphin; |     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) { | void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) { | ||||||
|     furi_assert(dolphin); |     furi_assert(dolphin); | ||||||
|     furi_assert(event); |     furi_assert(event); | ||||||
| @ -204,7 +195,7 @@ int32_t dolphin_srv(void* p) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     dolphin_free(dolphin); |     furi_crash("That was unexpected"); | ||||||
| 
 | 
 | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  | |||||||
| @ -37,8 +37,6 @@ struct Dolphin { | |||||||
| 
 | 
 | ||||||
| Dolphin* dolphin_alloc(); | Dolphin* dolphin_alloc(); | ||||||
| 
 | 
 | ||||||
| void dolphin_free(Dolphin* dolphin); |  | ||||||
| 
 |  | ||||||
| void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event); | void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event); | ||||||
| 
 | 
 | ||||||
| void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event); | void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event); | ||||||
|  | |||||||
| @ -85,30 +85,6 @@ Power* power_alloc() { | |||||||
|     return power; |     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) { | static void power_check_charging_state(Power* power) { | ||||||
|     if(furi_hal_power_is_charging()) { |     if(furi_hal_power_is_charging()) { | ||||||
|         if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) { |         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); |         furi_delay_ms(1000); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     power_free(power); |     furi_crash("That was unexpected"); | ||||||
| 
 | 
 | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  | |||||||
| @ -8,6 +8,8 @@ | |||||||
| typedef struct { | typedef struct { | ||||||
|     RpcSession* session; |     RpcSession* session; | ||||||
|     Desktop* desktop; |     Desktop* desktop; | ||||||
|  |     FuriPubSub* status_pubsub; | ||||||
|  |     FuriPubSubSubscription* status_subscription; | ||||||
| } RpcDesktop; | } RpcDesktop; | ||||||
| 
 | 
 | ||||||
| static void rpc_desktop_on_is_locked_request(const PB_Main* request, void* context) { | 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); |     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) { | void* rpc_desktop_alloc(RpcSession* session) { | ||||||
|     furi_assert(session); |     furi_assert(session); | ||||||
| 
 | 
 | ||||||
|     RpcDesktop* rpc_desktop = malloc(sizeof(RpcDesktop)); |     RpcDesktop* rpc_desktop = malloc(sizeof(RpcDesktop)); | ||||||
|     rpc_desktop->desktop = furi_record_open(RECORD_DESKTOP); |     rpc_desktop->desktop = furi_record_open(RECORD_DESKTOP); | ||||||
|  |     rpc_desktop->status_pubsub = desktop_api_get_status_pubsub(rpc_desktop->desktop); | ||||||
|     rpc_desktop->session = session; |     rpc_desktop->session = session; | ||||||
| 
 | 
 | ||||||
|     RpcHandler rpc_handler = { |     RpcHandler rpc_handler = { | ||||||
| @ -58,6 +112,12 @@ void* rpc_desktop_alloc(RpcSession* session) { | |||||||
|     rpc_handler.message_handler = rpc_desktop_on_unlock_request; |     rpc_handler.message_handler = rpc_desktop_on_unlock_request; | ||||||
|     rpc_add_handler(session, PB_Main_desktop_unlock_request_tag, &rpc_handler); |     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; |     return rpc_desktop; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -65,6 +125,10 @@ void rpc_desktop_free(void* context) { | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     RpcDesktop* rpc_desktop = 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_assert(rpc_desktop->desktop); | ||||||
|     furi_record_close(RECORD_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); | 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 app pointer to the api | ||||||
|  * @param old_path old path |  * @param old_path old path | ||||||
|  * @param new_path new 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 storage_common_rename(Storage* storage, const char* old_path, const char* new_path) { | ||||||
|     FS_Error error = storage_common_copy(storage, old_path, new_path); |     FS_Error error; | ||||||
|     if(error == FSE_OK) { | 
 | ||||||
|  |     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)) { |         if(!storage_simply_remove_recursive(storage, old_path)) { | ||||||
|             error = FSE_INTERNAL; |             error = FSE_INTERNAL; | ||||||
|         } |         } | ||||||
|     } |     } while(false); | ||||||
| 
 | 
 | ||||||
|     return error; |     return error; | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,6 +5,9 @@ | |||||||
| #include <dialogs/dialogs.h> | #include <dialogs/dialogs.h> | ||||||
| #include <fap_loader/fap_loader_app.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( | static bool favorite_fap_selector_item_callback( | ||||||
|     FuriString* file_path, |     FuriString* file_path, | ||||||
|     void* context, |     void* context, | ||||||
| @ -44,6 +47,8 @@ void desktop_settings_scene_favorite_on_enter(void* context) { | |||||||
|     uint32_t primary_favorite = |     uint32_t primary_favorite = | ||||||
|         scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); |         scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); | ||||||
|     uint32_t pre_select_item = 0; |     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++) { |     for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
| @ -53,20 +58,24 @@ void desktop_settings_scene_favorite_on_enter(void* context) { | |||||||
|             desktop_settings_scene_favorite_submenu_callback, |             desktop_settings_scene_favorite_submenu_callback, | ||||||
|             app); |             app); | ||||||
| 
 | 
 | ||||||
|         if(primary_favorite) { // Select favorite item in submenu
 |         // Select favorite item in submenu
 | ||||||
|             if((app->settings.favorite_primary.is_external && |         if(!curr_favorite_app->is_external && | ||||||
|                 !strcmp(FLIPPER_APPS[i].name, FAP_LOADER_APP_NAME)) || |            !strcmp(FLIPPER_APPS[i].name, curr_favorite_app->name_or_path)) { | ||||||
|                (!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))) { |  | ||||||
|             pre_select_item = i; |             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_set_header( | ||||||
|         submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:"); |         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 = |     uint32_t primary_favorite = | ||||||
|         scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); |         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(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(strcmp(FLIPPER_APPS[event.event].name, FAP_LOADER_APP_NAME) != 0) { |         if(event.event == EXTERNAL_APPLICATION_INDEX) { | ||||||
|             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 { |  | ||||||
|             const DialogsFileBrowserOptions browser_options = { |             const DialogsFileBrowserOptions browser_options = { | ||||||
|                 .extension = ".fap", |                 .extension = ".fap", | ||||||
|                 .icon = &I_unknown_10px, |                 .icon = &I_unknown_10px, | ||||||
| @ -109,36 +106,29 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e | |||||||
|                 .base_path = EXT_PATH("apps"), |                 .base_path = EXT_PATH("apps"), | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             if(primary_favorite) { // Select favorite fap in file browser
 |             // Select favorite fap in file browser
 | ||||||
|                 if(favorite_fap_selector_file_exists( |             if(favorite_fap_selector_file_exists(curr_favorite_app->name_or_path)) { | ||||||
|                        app->settings.favorite_primary.name_or_path)) { |                 furi_string_set_str(temp_path, curr_favorite_app->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); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             submenu_reset(app->submenu); |  | ||||||
|             if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) { |             if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) { | ||||||
|                 if(primary_favorite) { |                 submenu_reset(app->submenu); // Prevent menu from being shown when we exiting scene
 | ||||||
|                     app->settings.favorite_primary.is_external = true; |                 curr_favorite_app->is_external = true; | ||||||
|                 strncpy( |                 strncpy( | ||||||
|                         app->settings.favorite_primary.name_or_path, |                     curr_favorite_app->name_or_path, | ||||||
|                     furi_string_get_cstr(temp_path), |                     furi_string_get_cstr(temp_path), | ||||||
|                     MAX_APP_LENGTH); |                     MAX_APP_LENGTH); | ||||||
|  |                 consumed = true; | ||||||
|  |             } | ||||||
|         } else { |         } else { | ||||||
|                     app->settings.favorite_secondary.is_external = true; |             curr_favorite_app->is_external = false; | ||||||
|             strncpy( |             strncpy( | ||||||
|                         app->settings.favorite_secondary.name_or_path, |                 curr_favorite_app->name_or_path, FLIPPER_APPS[event.event].name, MAX_APP_LENGTH); | ||||||
|                         furi_string_get_cstr(temp_path), |             consumed = true; | ||||||
|                         MAX_APP_LENGTH); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |         if(consumed) { | ||||||
|             scene_manager_previous_scene(app->scene_manager); |             scene_manager_previous_scene(app->scene_manager); | ||||||
|  |         }; | ||||||
|         consumed = true; |         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