Merge remote-tracking branch 'origin/release-candidate' into release
| @ -1 +1 @@ | |||||||
| --ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* | --ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								SConstruct
									
									
									
									
									
								
							
							
						
						| @ -288,13 +288,17 @@ distenv.PhonyTarget( | |||||||
|     LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]], |     LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]], | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| # PY_LINT_SOURCES contains recursively-built modules' SConscript files + application manifests | # PY_LINT_SOURCES contains recursively-built modules' SConscript files | ||||||
| # Here we add additional Python files residing in repo root | # Here we add additional Python files residing in repo root | ||||||
| firmware_env.Append( | firmware_env.Append( | ||||||
|     PY_LINT_SOURCES=[ |     PY_LINT_SOURCES=[ | ||||||
|         # Py code folders |         # Py code folders | ||||||
|         "site_scons", |         "site_scons", | ||||||
|         "scripts", |         "scripts", | ||||||
|  |         "applications", | ||||||
|  |         "applications_user", | ||||||
|  |         "assets", | ||||||
|  |         "targets", | ||||||
|         # Extra files |         # Extra files | ||||||
|         "SConstruct", |         "SConstruct", | ||||||
|         "firmware.scons", |         "firmware.scons", | ||||||
| @ -304,7 +308,10 @@ firmware_env.Append( | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| black_commandline = "@${PYTHON3} -m black ${PY_BLACK_ARGS} ${PY_LINT_SOURCES}" | black_commandline = "@${PYTHON3} -m black ${PY_BLACK_ARGS} ${PY_LINT_SOURCES}" | ||||||
| black_base_args = ["--include", '"\\.scons|\\.py|SConscript|SConstruct"'] | black_base_args = [ | ||||||
|  |     "--include", | ||||||
|  |     '"(\\.scons|\\.py|SConscript|SConstruct|\\.fam)$"', | ||||||
|  | ] | ||||||
| 
 | 
 | ||||||
| distenv.PhonyTarget( | distenv.PhonyTarget( | ||||||
|     "lint_py", |     "lint_py", | ||||||
|  | |||||||
| @ -13,12 +13,12 @@ struct ISO7816_Command_APDU { | |||||||
|     //body
 |     //body
 | ||||||
|     uint8_t Lc; |     uint8_t Lc; | ||||||
|     uint8_t Le; |     uint8_t Le; | ||||||
| } __attribute__((packed)); | } FURI_PACKED; | ||||||
| 
 | 
 | ||||||
| struct ISO7816_Response_APDU { | struct ISO7816_Response_APDU { | ||||||
|     uint8_t SW1; |     uint8_t SW1; | ||||||
|     uint8_t SW2; |     uint8_t SW2; | ||||||
| } __attribute__((packed)); | } FURI_PACKED; | ||||||
| 
 | 
 | ||||||
| void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen); | void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen); | ||||||
| void iso7816_read_command_apdu( | void iso7816_read_command_apdu( | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ App( | |||||||
|     apptype=FlipperAppType.DEBUG, |     apptype=FlipperAppType.DEBUG, | ||||||
|     entry_point="display_test_app", |     entry_point="display_test_app", | ||||||
|     requires=["gui"], |     requires=["gui"], | ||||||
|     fap_libs=["misc"], |     fap_libs=["u8g2"], | ||||||
|     stack_size=1 * 1024, |     stack_size=1 * 1024, | ||||||
|     order=120, |     order=120, | ||||||
|     fap_category="Debug", |     fap_category="Debug", | ||||||
|  | |||||||
| @ -455,4 +455,19 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { | |||||||
|     return NfcErrorNone; |     return NfcErrorNone; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | NfcError nfc_felica_listener_set_sensf_res_data( | ||||||
|  |     Nfc* instance, | ||||||
|  |     const uint8_t* idm, | ||||||
|  |     const uint8_t idm_len, | ||||||
|  |     const uint8_t* pmm, | ||||||
|  |     const uint8_t pmm_len) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     furi_assert(idm); | ||||||
|  |     furi_assert(pmm); | ||||||
|  |     furi_assert(idm_len == 8); | ||||||
|  |     furi_assert(pmm_len == 8); | ||||||
|  | 
 | ||||||
|  |     return NfcErrorNone; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -1,27 +1,31 @@ | |||||||
| #include "flipper.pb.h" |  | ||||||
| #include <core/check.h> | #include <core/check.h> | ||||||
| #include <core/record.h> | #include <core/record.h> | ||||||
| #include "pb_decode.h" |  | ||||||
| #include <rpc/rpc.h> |  | ||||||
| #include "rpc/rpc_i.h" |  | ||||||
| #include "storage.pb.h" |  | ||||||
| #include "storage/filesystem_api_defines.h" |  | ||||||
| #include "storage/storage.h" |  | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include "../minunit.h" |  | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <pb.h> |  | ||||||
| #include <pb_encode.h> |  | ||||||
| #include <m-list.h> |  | ||||||
| #include <lib/toolbox/md5_calc.h> |  | ||||||
| #include <lib/toolbox/path.h> |  | ||||||
| #include <cli/cli.h> |  | ||||||
| #include <loader/loader.h> |  | ||||||
| #include <protobuf_version.h> |  | ||||||
| 
 | 
 | ||||||
| #include <FreeRTOS.h> | #include <FreeRTOS.h> | ||||||
| #include <semphr.h> | #include <semphr.h> | ||||||
| 
 | 
 | ||||||
|  | #include <rpc/rpc.h> | ||||||
|  | #include <rpc/rpc_i.h> | ||||||
|  | #include <cli/cli.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <loader/loader.h> | ||||||
|  | #include <storage/filesystem_api_defines.h> | ||||||
|  | 
 | ||||||
|  | #include <lib/toolbox/md5_calc.h> | ||||||
|  | #include <lib/toolbox/path.h> | ||||||
|  | 
 | ||||||
|  | #include <m-list.h> | ||||||
|  | #include "../minunit.h" | ||||||
|  | 
 | ||||||
|  | #include <protobuf_version.h> | ||||||
|  | #include <pb.h> | ||||||
|  | #include <pb_encode.h> | ||||||
|  | #include <pb_decode.h> | ||||||
|  | #include <storage.pb.h> | ||||||
|  | #include <flipper.pb.h> | ||||||
|  | 
 | ||||||
| LIST_DEF(MsgList, PB_Main, M_POD_OPLIST) | LIST_DEF(MsgList, PB_Main, M_POD_OPLIST) | ||||||
| #define M_OPL_MsgList_t() LIST_OPLIST(MsgList) | #define M_OPL_MsgList_t() LIST_OPLIST(MsgList) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ App( | |||||||
|     entry_point="advanced_plugin1_ep", |     entry_point="advanced_plugin1_ep", | ||||||
|     requires=["example_advanced_plugins"], |     requires=["example_advanced_plugins"], | ||||||
|     sources=["plugin1.c"], |     sources=["plugin1.c"], | ||||||
|  |     fal_embedded=True, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| App( | App( | ||||||
| @ -22,4 +23,5 @@ App( | |||||||
|     entry_point="advanced_plugin2_ep", |     entry_point="advanced_plugin2_ep", | ||||||
|     requires=["example_advanced_plugins"], |     requires=["example_advanced_plugins"], | ||||||
|     sources=["plugin2.c"], |     sources=["plugin2.c"], | ||||||
|  |     fal_embedded=True, | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -23,7 +23,10 @@ int32_t example_advanced_plugins_app(void* p) { | |||||||
|         PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); |         PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if(plugin_manager_load_all(manager, APP_DATA_PATH("plugins")) != PluginManagerErrorNone) { |         // For built-in .fals (fal_embedded==True), use APP_ASSETS_PATH
 | ||||||
|  |         // Otherwise, use APP_DATA_PATH
 | ||||||
|  |         if(plugin_manager_load_all(manager, APP_ASSETS_PATH("plugins")) != | ||||||
|  |            PluginManagerErrorNone) { | ||||||
|             FURI_LOG_E(TAG, "Failed to load all libs"); |             FURI_LOG_E(TAG, "Failed to load all libs"); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ App( | |||||||
|         "!plugins", |         "!plugins", | ||||||
|         "!nfc_cli.c", |         "!nfc_cli.c", | ||||||
|     ], |     ], | ||||||
|     fap_libs=["assets"], |     fap_libs=["assets", "mbedtls"], | ||||||
|     fap_icon="icon.png", |     fap_icon="icon.png", | ||||||
|     fap_category="NFC", |     fap_category="NFC", | ||||||
| ) | ) | ||||||
| @ -74,6 +74,15 @@ App( | |||||||
|     sources=["plugins/supported_cards/two_cities.c"], |     sources=["plugins/supported_cards/two_cities.c"], | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | App( | ||||||
|  |     appid="aime_parser", | ||||||
|  |     apptype=FlipperAppType.PLUGIN, | ||||||
|  |     entry_point="aime_plugin_ep", | ||||||
|  |     targets=["f7"], | ||||||
|  |     requires=["nfc"], | ||||||
|  |     sources=["plugins/supported_cards/aime.c"], | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| App( | App( | ||||||
|     appid="nfc_start", |     appid="nfc_start", | ||||||
|     targets=["f7"], |     targets=["f7"], | ||||||
|  | |||||||
| @ -67,8 +67,14 @@ static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t even | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { | ||||||
|  |     const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); | ||||||
|  |     instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data); | ||||||
|  |     nfc_listener_start(instance->listener, NULL, NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const NfcProtocolSupportBase nfc_protocol_support_felica = { | const NfcProtocolSupportBase nfc_protocol_support_felica = { | ||||||
|     .features = NfcProtocolFeatureNone, |     .features = NfcProtocolFeatureEmulateUid, | ||||||
| 
 | 
 | ||||||
|     .scene_info = |     .scene_info = | ||||||
|         { |         { | ||||||
| @ -102,7 +108,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { | |||||||
|         }, |         }, | ||||||
|     .scene_emulate = |     .scene_emulate = | ||||||
|         { |         { | ||||||
|             .on_enter = nfc_protocol_support_common_on_enter_empty, |             .on_enter = nfc_scene_emulate_on_enter_felica, | ||||||
|             .on_event = nfc_protocol_support_common_on_event_empty, |             .on_event = nfc_protocol_support_common_on_event_empty, | ||||||
|         }, |         }, | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ enum { | |||||||
|     SubmenuIndexUnlock = SubmenuIndexCommonMax, |     SubmenuIndexUnlock = SubmenuIndexCommonMax, | ||||||
|     SubmenuIndexUnlockByReader, |     SubmenuIndexUnlockByReader, | ||||||
|     SubmenuIndexUnlockByPassword, |     SubmenuIndexUnlockByPassword, | ||||||
|  |     SubmenuIndexWrite, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) { | static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) { | ||||||
| @ -106,6 +107,15 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc | |||||||
|             SubmenuIndexUnlock, |             SubmenuIndexUnlock, | ||||||
|             nfc_protocol_support_common_submenu_callback, |             nfc_protocol_support_common_submenu_callback, | ||||||
|             instance); |             instance); | ||||||
|  |     } else if( | ||||||
|  |         data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 || | ||||||
|  |         data->type == MfUltralightTypeNTAG216) { | ||||||
|  |         submenu_add_item( | ||||||
|  |             submenu, | ||||||
|  |             "Write", | ||||||
|  |             SubmenuIndexWrite, | ||||||
|  |             nfc_protocol_support_common_submenu_callback, | ||||||
|  |             instance); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -146,6 +156,9 @@ static bool | |||||||
|     if(event == SubmenuIndexUnlock) { |     if(event == SubmenuIndexUnlock) { | ||||||
|         scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); |         scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); | ||||||
|         return true; |         return true; | ||||||
|  |     } else if(event == SubmenuIndexWrite) { | ||||||
|  |         scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); | ||||||
|  |         return true; | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
|  | |||||||
| @ -29,7 +29,9 @@ static NfcCommand nfc_scene_read_poller_callback_st25tb(NfcGenericEvent event, v | |||||||
|     NfcApp* instance = context; |     NfcApp* instance = context; | ||||||
|     const St25tbPollerEvent* st25tb_event = event.event_data; |     const St25tbPollerEvent* st25tb_event = event.event_data; | ||||||
| 
 | 
 | ||||||
|     if(st25tb_event->type == St25tbPollerEventTypeReady) { |     if(st25tb_event->type == St25tbPollerEventTypeRequestMode) { | ||||||
|  |         st25tb_event->data->mode_request.mode = St25tbPollerModeRead; | ||||||
|  |     } else if(st25tb_event->type == St25tbPollerEventTypeSuccess) { | ||||||
|         nfc_device_set_data( |         nfc_device_set_data( | ||||||
|             instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller)); |             instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller)); | ||||||
|         view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); |         view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); | ||||||
|  | |||||||
| @ -400,15 +400,16 @@ bool nfc_load_from_file_select(NfcApp* instance) { | |||||||
|     browser_options.base_path = NFC_APP_FOLDER; |     browser_options.base_path = NFC_APP_FOLDER; | ||||||
|     browser_options.hide_dot_files = true; |     browser_options.hide_dot_files = true; | ||||||
| 
 | 
 | ||||||
|  |     bool success = false; | ||||||
|  |     do { | ||||||
|         // Input events and views are managed by file_browser
 |         // Input events and views are managed by file_browser
 | ||||||
|     bool result = dialog_file_browser_show( |         if(!dialog_file_browser_show( | ||||||
|         instance->dialogs, instance->file_path, instance->file_path, &browser_options); |                instance->dialogs, instance->file_path, instance->file_path, &browser_options)) | ||||||
|  |             break; | ||||||
|  |         success = nfc_load_file(instance, instance->file_path, true); | ||||||
|  |     } while(!success); | ||||||
| 
 | 
 | ||||||
|     if(result) { |     return success; | ||||||
|         result = nfc_load_file(instance, instance->file_path, true); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void nfc_show_loading_popup(void* context, bool show) { | void nfc_show_loading_popup(void* context, bool show) { | ||||||
|  | |||||||
							
								
								
									
										169
									
								
								applications/main/nfc/plugins/supported_cards/aime.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,169 @@ | |||||||
|  | #include "nfc_supported_card_plugin.h" | ||||||
|  | 
 | ||||||
|  | #include <flipper_application/flipper_application.h> | ||||||
|  | 
 | ||||||
|  | #include <nfc/nfc_device.h> | ||||||
|  | #include <nfc/helpers/nfc_util.h> | ||||||
|  | #include <nfc/protocols/mf_classic/mf_classic_poller_sync.h> | ||||||
|  | 
 | ||||||
|  | #define TAG "Aime" | ||||||
|  | 
 | ||||||
|  | static const uint64_t aime_key = 0x574343467632; | ||||||
|  | 
 | ||||||
|  | bool aime_verify(Nfc* nfc) { | ||||||
|  |     bool verified = false; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         const uint8_t verify_sector = 0; | ||||||
|  |         uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); | ||||||
|  |         FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); | ||||||
|  | 
 | ||||||
|  |         MfClassicKey key = {}; | ||||||
|  |         nfc_util_num2bytes(aime_key, COUNT_OF(key.data), key.data); | ||||||
|  | 
 | ||||||
|  |         MfClassicAuthContext auth_ctx = {}; | ||||||
|  |         MfClassicError error = | ||||||
|  |             mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx); | ||||||
|  | 
 | ||||||
|  |         if(error != MfClassicErrorNone) { | ||||||
|  |             FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         verified = true; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     return verified; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool aime_read(Nfc* nfc, NfcDevice* device) { | ||||||
|  |     furi_assert(nfc); | ||||||
|  |     furi_assert(device); | ||||||
|  | 
 | ||||||
|  |     bool is_read = false; | ||||||
|  | 
 | ||||||
|  |     MfClassicData* data = mf_classic_alloc(); | ||||||
|  |     nfc_device_copy_data(device, NfcProtocolMfClassic, data); | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         MfClassicType type = MfClassicType1k; | ||||||
|  |         MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); | ||||||
|  |         if(error != MfClassicErrorNone) break; | ||||||
|  | 
 | ||||||
|  |         data->type = type; | ||||||
|  |         MfClassicDeviceKeys keys = {}; | ||||||
|  |         for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { | ||||||
|  |             nfc_util_num2bytes(aime_key, sizeof(MfClassicKey), keys.key_a[i].data); | ||||||
|  |             FURI_BIT_SET(keys.key_a_mask, i); | ||||||
|  |             nfc_util_num2bytes(aime_key, sizeof(MfClassicKey), keys.key_b[i].data); | ||||||
|  |             FURI_BIT_SET(keys.key_b_mask, i); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         error = mf_classic_poller_sync_read(nfc, &keys, data); | ||||||
|  |         if(error != MfClassicErrorNone) { | ||||||
|  |             FURI_LOG_W(TAG, "Failed to read data"); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         nfc_device_set_data(device, NfcProtocolMfClassic, data); | ||||||
|  | 
 | ||||||
|  |         is_read = true; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     mf_classic_free(data); | ||||||
|  | 
 | ||||||
|  |     return is_read; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool aime_parse(const NfcDevice* device, FuriString* parsed_data) { | ||||||
|  |     furi_assert(device); | ||||||
|  | 
 | ||||||
|  |     const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); | ||||||
|  | 
 | ||||||
|  |     bool parsed = false; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         // verify key
 | ||||||
|  |         MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0); | ||||||
|  |         uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); | ||||||
|  |         if(key != aime_key) break; | ||||||
|  | 
 | ||||||
|  |         // Aime Magic is stored at block 1, starts from byte 0, len 4 bytes
 | ||||||
|  |         const uint8_t* aime_magic = &data->block[1].data[0]; | ||||||
|  | 
 | ||||||
|  |         // verify aime magic
 | ||||||
|  |         if(aime_magic[0] != 'S' || aime_magic[1] != 'B' || aime_magic[2] != 'S' || | ||||||
|  |            aime_magic[3] != 'D') | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         // Aime checksum is stored at block 1, starts from byte 13, len 3 bytes
 | ||||||
|  |         // seems like only old games checks this? e.g., old versions of Chunithm
 | ||||||
|  |         const uint8_t* aime_checksum = &data->block[1].data[13]; | ||||||
|  | 
 | ||||||
|  |         // Aime access code is stored as decimal hex representation in block 2, starts from byte 6, len 10 bytes
 | ||||||
|  |         const uint8_t* aime_accesscode = &data->block[2].data[6]; | ||||||
|  | 
 | ||||||
|  |         char aime_accesscode_str[24 + 1]; | ||||||
|  |         snprintf( | ||||||
|  |             aime_accesscode_str, | ||||||
|  |             sizeof(aime_accesscode_str), | ||||||
|  |             "%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x", | ||||||
|  |             aime_accesscode[0], | ||||||
|  |             aime_accesscode[1], | ||||||
|  |             aime_accesscode[2], | ||||||
|  |             aime_accesscode[3], | ||||||
|  |             aime_accesscode[4], | ||||||
|  |             aime_accesscode[5], | ||||||
|  |             aime_accesscode[6], | ||||||
|  |             aime_accesscode[7], | ||||||
|  |             aime_accesscode[8], | ||||||
|  |             aime_accesscode[9]); | ||||||
|  | 
 | ||||||
|  |         // validate decimal hex representation
 | ||||||
|  |         bool code_is_hex = true; | ||||||
|  |         for(int i = 0; i < 24; i++) { | ||||||
|  |             if(aime_accesscode_str[i] == ' ') continue; | ||||||
|  |             if(aime_accesscode_str[i] < '0' || aime_accesscode_str[i] > '9') { | ||||||
|  |                 code_is_hex = false; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(!code_is_hex) break; | ||||||
|  | 
 | ||||||
|  |         // Note: Aime access code has some other self-check algorithms that are not public.
 | ||||||
|  |         // This parser does not try to verify the number.
 | ||||||
|  | 
 | ||||||
|  |         furi_string_printf( | ||||||
|  |             parsed_data, | ||||||
|  |             "\e#Aime Card\nAccess Code: \n%s\nChecksum: %02X%02X%02X\n", | ||||||
|  |             aime_accesscode_str, | ||||||
|  |             aime_checksum[0], | ||||||
|  |             aime_checksum[1], | ||||||
|  |             aime_checksum[2]); | ||||||
|  | 
 | ||||||
|  |         parsed = true; | ||||||
|  | 
 | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     return parsed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Actual implementation of app<>plugin interface */ | ||||||
|  | static const NfcSupportedCardsPlugin aime_plugin = { | ||||||
|  |     .protocol = NfcProtocolMfClassic, | ||||||
|  |     .verify = aime_verify, | ||||||
|  |     .read = aime_read, | ||||||
|  |     .parse = aime_parse, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* Plugin descriptor to comply with basic plugin specification */ | ||||||
|  | static const FlipperAppPluginDescriptor aime_plugin_descriptor = { | ||||||
|  |     .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, | ||||||
|  |     .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, | ||||||
|  |     .entry_point = &aime_plugin, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* Plugin entry point - must return a pointer to const descriptor  */ | ||||||
|  | const FlipperAppPluginDescriptor* aime_plugin_ep() { | ||||||
|  |     return &aime_plugin_descriptor; | ||||||
|  | } | ||||||
| @ -24,6 +24,10 @@ ADD_SCENE(nfc, field, Field) | |||||||
| ADD_SCENE(nfc, retry_confirm, RetryConfirm) | ADD_SCENE(nfc, retry_confirm, RetryConfirm) | ||||||
| ADD_SCENE(nfc, exit_confirm, ExitConfirm) | ADD_SCENE(nfc, exit_confirm, ExitConfirm) | ||||||
| 
 | 
 | ||||||
|  | ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite) | ||||||
|  | ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess) | ||||||
|  | ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail) | ||||||
|  | ADD_SCENE(nfc, mf_ultralight_wrong_card, MfUltralightWrongCard) | ||||||
| ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) | ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) | ||||||
| ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) | ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) | ||||||
| ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) | ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) | ||||||
|  | |||||||
| @ -37,6 +37,7 @@ bool nfc_scene_detect_on_event(void* context, SceneManagerEvent event) { | |||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == NfcCustomEventWorkerExit) { |         if(event.event == NfcCustomEventWorkerExit) { | ||||||
|             if(instance->protocols_detected_num > 1) { |             if(instance->protocols_detected_num > 1) { | ||||||
|  |                 notification_message(instance->notifications, &sequence_single_vibro); | ||||||
|                 scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol); |                 scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol); | ||||||
|             } else { |             } else { | ||||||
|                 scene_manager_next_scene(instance->scene_manager, NfcSceneRead); |                 scene_manager_next_scene(instance->scene_manager, NfcSceneRead); | ||||||
|  | |||||||
| @ -45,11 +45,7 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == SubmenuIndexMfClassicKeys) { |         if(event.event == SubmenuIndexMfClassicKeys) { | ||||||
|             if(nfc_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { |  | ||||||
|             scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); |             scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); | ||||||
|             } else { |  | ||||||
|                 scene_manager_previous_scene(instance->scene_manager); |  | ||||||
|             } |  | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexMfUltralightUnlock) { |         } else if(event.event == SubmenuIndexMfUltralightUnlock) { | ||||||
|             mf_ultralight_auth_reset(instance->mf_ul_auth); |             mf_ultralight_auth_reset(instance->mf_ul_auth); | ||||||
|  | |||||||
| @ -52,11 +52,12 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve | |||||||
| 
 | 
 | ||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     nfc->protocols_detected[0] = nfc_device_get_protocol(nfc->nfc_device); |  | ||||||
|     MfUltralightAuthType type = nfc->mf_ul_auth->type; |     MfUltralightAuthType type = nfc->mf_ul_auth->type; | ||||||
|     if((type == MfUltralightAuthTypeReader) || (type == MfUltralightAuthTypeManual)) { |     if((type == MfUltralightAuthTypeReader) || (type == MfUltralightAuthTypeManual)) { | ||||||
|         if(event.type == SceneManagerEventTypeCustom) { |         if(event.type == SceneManagerEventTypeCustom) { | ||||||
|             if(event.event == DialogExResultRight) { |             if(event.event == DialogExResultRight) { | ||||||
|  |                 const NfcProtocol mfu_protocol[] = {NfcProtocolMfUltralight}; | ||||||
|  |                 nfc_app_set_detected_protocols(nfc, mfu_protocol, COUNT_OF(mfu_protocol)); | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); | ||||||
|                 dolphin_deed(DolphinDeedNfcRead); |                 dolphin_deed(DolphinDeedNfcRead); | ||||||
|                 consumed = true; |                 consumed = true; | ||||||
|  | |||||||
							
								
								
									
										119
									
								
								applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,119 @@ | |||||||
|  | #include "../nfc_app_i.h" | ||||||
|  | 
 | ||||||
|  | #include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h> | ||||||
|  | 
 | ||||||
|  | enum { | ||||||
|  |     NfcSceneMfUltralightWriteStateCardSearch, | ||||||
|  |     NfcSceneMfUltralightWriteStateCardFound, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | NfcCommand nfc_scene_mf_ultralight_write_worker_callback(NfcGenericEvent event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     furi_assert(event.event_data); | ||||||
|  |     furi_assert(event.protocol == NfcProtocolMfUltralight); | ||||||
|  | 
 | ||||||
|  |     NfcCommand command = NfcCommandContinue; | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     MfUltralightPollerEvent* mfu_event = event.event_data; | ||||||
|  | 
 | ||||||
|  |     if(mfu_event->type == MfUltralightPollerEventTypeRequestMode) { | ||||||
|  |         mfu_event->data->poller_mode = MfUltralightPollerModeWrite; | ||||||
|  |         view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); | ||||||
|  |     } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { | ||||||
|  |         mfu_event->data->auth_context.skip_auth = true; | ||||||
|  |     } else if(mfu_event->type == MfUltralightPollerEventTypeRequestWriteData) { | ||||||
|  |         mfu_event->data->write_data = | ||||||
|  |             nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); | ||||||
|  |     } else if(mfu_event->type == MfUltralightPollerEventTypeCardMismatch) { | ||||||
|  |         view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); | ||||||
|  |         command = NfcCommandStop; | ||||||
|  |     } else if(mfu_event->type == MfUltralightPollerEventTypeCardLocked) { | ||||||
|  |         view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); | ||||||
|  |         command = NfcCommandStop; | ||||||
|  |     } else if(mfu_event->type == MfUltralightPollerEventTypeWriteFail) { | ||||||
|  |         command = NfcCommandStop; | ||||||
|  |     } else if(mfu_event->type == MfUltralightPollerEventTypeWriteSuccess) { | ||||||
|  |         view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); | ||||||
|  |         command = NfcCommandStop; | ||||||
|  |     } | ||||||
|  |     return command; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void nfc_scene_mf_ultralight_write_setup_view(NfcApp* instance) { | ||||||
|  |     Popup* popup = instance->popup; | ||||||
|  |     popup_reset(popup); | ||||||
|  |     uint32_t state = | ||||||
|  |         scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightWrite); | ||||||
|  | 
 | ||||||
|  |     if(state == NfcSceneMfUltralightWriteStateCardSearch) { | ||||||
|  |         popup_set_text( | ||||||
|  |             instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); | ||||||
|  |         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); | ||||||
|  |     } else { | ||||||
|  |         popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); | ||||||
|  |         popup_set_icon(popup, 12, 23, &A_Loading_24); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_write_on_enter(void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     dolphin_deed(DolphinDeedNfcEmulate); | ||||||
|  | 
 | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         instance->scene_manager, | ||||||
|  |         NfcSceneMfUltralightWrite, | ||||||
|  |         NfcSceneMfUltralightWriteStateCardSearch); | ||||||
|  |     nfc_scene_mf_ultralight_write_setup_view(instance); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     FURI_LOG_D("WMFU", "Card searching..."); | ||||||
|  |     instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); | ||||||
|  |     nfc_poller_start(instance->poller, nfc_scene_mf_ultralight_write_worker_callback, instance); | ||||||
|  | 
 | ||||||
|  |     nfc_blink_emulate_start(instance); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_ultralight_write_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == NfcCustomEventCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 instance->scene_manager, | ||||||
|  |                 NfcSceneMfUltralightWrite, | ||||||
|  |                 NfcSceneMfUltralightWriteStateCardFound); | ||||||
|  |             nfc_scene_mf_ultralight_write_setup_view(instance); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcCustomEventWrongCard) { | ||||||
|  |             scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrongCard); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcCustomEventPollerSuccess) { | ||||||
|  |             scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteSuccess); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcCustomEventPollerFailure) { | ||||||
|  |             scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteFail); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_write_on_exit(void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  | 
 | ||||||
|  |     nfc_poller_stop(instance->poller); | ||||||
|  |     nfc_poller_free(instance->poller); | ||||||
|  | 
 | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         instance->scene_manager, | ||||||
|  |         NfcSceneMfUltralightWrite, | ||||||
|  |         NfcSceneMfUltralightWriteStateCardSearch); | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(instance->popup); | ||||||
|  | 
 | ||||||
|  |     nfc_blink_stop(instance); | ||||||
|  | } | ||||||
| @ -0,0 +1,67 @@ | |||||||
|  | #include "../nfc_app_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_write_fail_widget_callback( | ||||||
|  |     GuiButtonType result, | ||||||
|  |     InputType type, | ||||||
|  |     void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(instance->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_write_fail_on_enter(void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     Widget* widget = instance->widget; | ||||||
|  | 
 | ||||||
|  |     notification_message(instance->notifications, &sequence_error); | ||||||
|  | 
 | ||||||
|  |     widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         widget, | ||||||
|  |         7, | ||||||
|  |         17, | ||||||
|  |         AlignLeft, | ||||||
|  |         AlignTop, | ||||||
|  |         FontSecondary, | ||||||
|  |         "Card protected by\npassword, AUTH0\nor lock bits"); | ||||||
|  | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, | ||||||
|  |         GuiButtonTypeLeft, | ||||||
|  |         "Finish", | ||||||
|  |         nfc_scene_mf_ultralight_write_fail_widget_callback, | ||||||
|  |         instance); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool nfc_scene_mf_ultralight_write_fail_move_to_back_scene(const NfcApp* const instance) { | ||||||
|  |     bool was_saved = scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu); | ||||||
|  |     uint32_t scene_id = was_saved ? NfcSceneSavedMenu : NfcSceneReadMenu; | ||||||
|  | 
 | ||||||
|  |     return scene_manager_search_and_switch_to_previous_scene(instance->scene_manager, scene_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_ultralight_write_fail_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance); | ||||||
|  |         } | ||||||
|  |     } else if(event.type == SceneManagerEventTypeBack) { | ||||||
|  |         consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance); | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_write_fail_on_exit(void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  | 
 | ||||||
|  |     widget_reset(instance->widget); | ||||||
|  | } | ||||||
| @ -0,0 +1,43 @@ | |||||||
|  | #include "../nfc_app_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_write_success_popup_callback(void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_write_success_on_enter(void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     dolphin_deed(DolphinDeedNfcSave); | ||||||
|  | 
 | ||||||
|  |     notification_message(instance->notifications, &sequence_success); | ||||||
|  | 
 | ||||||
|  |     Popup* popup = instance->popup; | ||||||
|  |     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||||
|  |     popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); | ||||||
|  |     popup_set_timeout(popup, 1500); | ||||||
|  |     popup_set_context(popup, instance); | ||||||
|  |     popup_set_callback(popup, nfc_scene_mf_ultralight_write_success_popup_callback); | ||||||
|  |     popup_enable_timeout(popup); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == NfcCustomEventViewExit) { | ||||||
|  |             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                 instance->scene_manager, NfcSceneSavedMenu); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_write_success_on_exit(void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  | 
 | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(instance->popup); | ||||||
|  | } | ||||||
| @ -0,0 +1,58 @@ | |||||||
|  | #include "../nfc_app_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_wrong_card_widget_callback( | ||||||
|  |     GuiButtonType result, | ||||||
|  |     InputType type, | ||||||
|  |     void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(instance->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_wrong_card_on_enter(void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     Widget* widget = instance->widget; | ||||||
|  | 
 | ||||||
|  |     notification_message(instance->notifications, &sequence_error); | ||||||
|  | 
 | ||||||
|  |     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         widget, | ||||||
|  |         4, | ||||||
|  |         17, | ||||||
|  |         AlignLeft, | ||||||
|  |         AlignTop, | ||||||
|  |         FontSecondary, | ||||||
|  |         "Card of the same\ntype should be\n presented"); | ||||||
|  |     //"Data management\nis only possible\nwith card of same type");
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, | ||||||
|  |         GuiButtonTypeLeft, | ||||||
|  |         "Retry", | ||||||
|  |         nfc_scene_mf_ultralight_wrong_card_widget_callback, | ||||||
|  |         instance); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_ultralight_wrong_card_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_previous_scene(instance->scene_manager); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_wrong_card_on_exit(void* context) { | ||||||
|  |     NfcApp* instance = context; | ||||||
|  | 
 | ||||||
|  |     widget_reset(instance->widget); | ||||||
|  | } | ||||||
| @ -21,7 +21,6 @@ void nfc_scene_select_protocol_on_enter(void* context) { | |||||||
|     } else { |     } else { | ||||||
|         prefix = "Read as"; |         prefix = "Read as"; | ||||||
|         submenu_set_header(submenu, "Multi-protocol card"); |         submenu_set_header(submenu, "Multi-protocol card"); | ||||||
|         notification_message(instance->notifications, &sequence_single_vibro); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for(uint32_t i = 0; i < instance->protocols_detected_num; i++) { |     for(uint32_t i = 0; i < instance->protocols_detected_num; i++) { | ||||||
|  | |||||||
| @ -125,7 +125,6 @@ void dict_attack_reset(DictAttack* instance) { | |||||||
|         instance->view, |         instance->view, | ||||||
|         DictAttackViewModel * model, |         DictAttackViewModel * model, | ||||||
|         { |         { | ||||||
|             model->card_detected = false; |  | ||||||
|             model->sectors_total = 0; |             model->sectors_total = 0; | ||||||
|             model->sectors_read = 0; |             model->sectors_read = 0; | ||||||
|             model->current_sector = 0; |             model->current_sector = 0; | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ App( | |||||||
|     icon="A_U2F_14", |     icon="A_U2F_14", | ||||||
|     order=80, |     order=80, | ||||||
|     resources="resources", |     resources="resources", | ||||||
|     fap_libs=["assets"], |     fap_libs=["assets", "mbedtls"], | ||||||
|     fap_category="USB", |     fap_category="USB", | ||||||
|     fap_icon="icon.png", |     fap_icon="icon.png", | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -1,98 +0,0 @@ | |||||||
| /*
 |  | ||||||
|  * hmac.c - HMAC |  | ||||||
|  * |  | ||||||
|  * Copyright (C) 2017 Sergei Glushchenko |  | ||||||
|  * Author: Sergei Glushchenko <gl.sergei@gmail.com> |  | ||||||
|  * |  | ||||||
|  * This file is a part of U2F firmware for STM32 |  | ||||||
|  * |  | ||||||
|  * This program is free software: you can redistribute it and/or modify it |  | ||||||
|  * under the terms of the GNU General Public License as published by |  | ||||||
|  * the Free Software Foundation, either version 3 of the License, or |  | ||||||
|  * (at your option) any later version. |  | ||||||
|  * |  | ||||||
|  * This program is distributed in the hope that it will be useful, but |  | ||||||
|  * WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU |  | ||||||
|  * General Public License for more details. |  | ||||||
|  * |  | ||||||
|  * You should have received a copy of the GNU General Public License |  | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 |  | ||||||
|  * |  | ||||||
|  * As additional permission under GNU GPL version 3 section 7, you may |  | ||||||
|  * distribute non-source form of the Program without the copy of the |  | ||||||
|  * GNU GPL normally required by section 4, provided you inform the |  | ||||||
|  * recipients of GNU GPL by a written offer. |  | ||||||
|  * |  | ||||||
|  */ |  | ||||||
| #include <stdint.h> |  | ||||||
| 
 |  | ||||||
| #include "sha256.h" |  | ||||||
| #include "hmac_sha256.h" |  | ||||||
| 
 |  | ||||||
| static void _hmac_sha256_init(const hmac_context* ctx) { |  | ||||||
|     hmac_sha256_context* context = (hmac_sha256_context*)ctx; |  | ||||||
|     sha256_start(&context->sha_ctx); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
|     _hmac_sha256_update(const hmac_context* ctx, const uint8_t* message, unsigned message_size) { |  | ||||||
|     hmac_sha256_context* context = (hmac_sha256_context*)ctx; |  | ||||||
|     sha256_update(&context->sha_ctx, message, message_size); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void _hmac_sha256_finish(const hmac_context* ctx, uint8_t* hash_result) { |  | ||||||
|     hmac_sha256_context* context = (hmac_sha256_context*)ctx; |  | ||||||
|     sha256_finish(&context->sha_ctx, hash_result); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* Compute an HMAC using K as a key (as in RFC 6979). Note that K is always
 |  | ||||||
|    the same size as the hash result size. */ |  | ||||||
| static void hmac_init(const hmac_context* ctx, const uint8_t* K) { |  | ||||||
|     uint8_t* pad = ctx->tmp + 2 * ctx->result_size; |  | ||||||
|     unsigned i; |  | ||||||
|     for(i = 0; i < ctx->result_size; ++i) pad[i] = K[i] ^ 0x36; |  | ||||||
|     for(; i < ctx->block_size; ++i) pad[i] = 0x36; |  | ||||||
| 
 |  | ||||||
|     ctx->init_hash(ctx); |  | ||||||
|     ctx->update_hash(ctx, pad, ctx->block_size); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void hmac_update(const hmac_context* ctx, const uint8_t* message, unsigned message_size) { |  | ||||||
|     ctx->update_hash(ctx, message, message_size); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void hmac_finish(const hmac_context* ctx, const uint8_t* K, uint8_t* result) { |  | ||||||
|     uint8_t* pad = ctx->tmp + 2 * ctx->result_size; |  | ||||||
|     unsigned i; |  | ||||||
|     for(i = 0; i < ctx->result_size; ++i) pad[i] = K[i] ^ 0x5c; |  | ||||||
|     for(; i < ctx->block_size; ++i) pad[i] = 0x5c; |  | ||||||
| 
 |  | ||||||
|     ctx->finish_hash(ctx, result); |  | ||||||
| 
 |  | ||||||
|     ctx->init_hash(ctx); |  | ||||||
|     ctx->update_hash(ctx, pad, ctx->block_size); |  | ||||||
|     ctx->update_hash(ctx, result, ctx->result_size); |  | ||||||
|     ctx->finish_hash(ctx, result); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void hmac_sha256_init(hmac_sha256_context* ctx, const uint8_t* K) { |  | ||||||
|     ctx->hmac_ctx.init_hash = _hmac_sha256_init; |  | ||||||
|     ctx->hmac_ctx.update_hash = _hmac_sha256_update; |  | ||||||
|     ctx->hmac_ctx.finish_hash = _hmac_sha256_finish; |  | ||||||
|     ctx->hmac_ctx.block_size = 64; |  | ||||||
|     ctx->hmac_ctx.result_size = 32; |  | ||||||
|     ctx->hmac_ctx.tmp = ctx->tmp; |  | ||||||
|     hmac_init(&ctx->hmac_ctx, K); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void hmac_sha256_update( |  | ||||||
|     const hmac_sha256_context* ctx, |  | ||||||
|     const uint8_t* message, |  | ||||||
|     unsigned message_size) { |  | ||||||
|     hmac_update(&ctx->hmac_ctx, message, message_size); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void hmac_sha256_finish(const hmac_sha256_context* ctx, const uint8_t* K, uint8_t* hash_result) { |  | ||||||
|     hmac_finish(&ctx->hmac_ctx, K, hash_result); |  | ||||||
| } |  | ||||||
| @ -1,38 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include "sha256.h" |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| extern "C" { |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| typedef struct hmac_context { |  | ||||||
|     void (*init_hash)(const struct hmac_context* context); |  | ||||||
|     void (*update_hash)( |  | ||||||
|         const struct hmac_context* context, |  | ||||||
|         const uint8_t* message, |  | ||||||
|         unsigned message_size); |  | ||||||
|     void (*finish_hash)(const struct hmac_context* context, uint8_t* hash_result); |  | ||||||
|     unsigned block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */ |  | ||||||
|     unsigned result_size; /* Hash function result size in bytes, eg 32 for SHA-256. */ |  | ||||||
|     uint8_t* tmp; /* Must point to a buffer of at least (2 * result_size + block_size) bytes. */ |  | ||||||
| } hmac_context; |  | ||||||
| 
 |  | ||||||
| typedef struct hmac_sha256_context { |  | ||||||
|     hmac_context hmac_ctx; |  | ||||||
|     sha256_context sha_ctx; |  | ||||||
|     uint8_t tmp[32 * 2 + 64]; |  | ||||||
| } hmac_sha256_context; |  | ||||||
| 
 |  | ||||||
| void hmac_sha256_init(hmac_sha256_context* ctx, const uint8_t* K); |  | ||||||
| 
 |  | ||||||
| void hmac_sha256_update( |  | ||||||
|     const hmac_sha256_context* ctx, |  | ||||||
|     const uint8_t* message, |  | ||||||
|     unsigned message_size); |  | ||||||
| 
 |  | ||||||
| void hmac_sha256_finish(const hmac_sha256_context* ctx, const uint8_t* K, uint8_t* hash_result); |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| @ -1,18 +1,22 @@ | |||||||
| #include <furi.h> |  | ||||||
| #include "u2f.h" | #include "u2f.h" | ||||||
| #include "u2f_hid.h" | #include "u2f_hid.h" | ||||||
| #include "u2f_data.h" | #include "u2f_data.h" | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| #include <furi_hal_random.h> | #include <furi_hal_random.h> | ||||||
| #include <littlefs/lfs_util.h> // for lfs_tobe32 | #include <littlefs/lfs_util.h> // for lfs_tobe32 | ||||||
| 
 | 
 | ||||||
| #include "toolbox/sha256.h" | #include <mbedtls/sha256.h> | ||||||
| #include "hmac_sha256.h" | #include <mbedtls/md.h> | ||||||
| #include "micro-ecc/uECC.h" | #include <mbedtls/ecdsa.h> | ||||||
|  | #include <mbedtls/error.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "U2f" | #define TAG "U2f" | ||||||
| #define WORKER_TAG TAG "Worker" | #define WORKER_TAG TAG "Worker" | ||||||
| 
 | 
 | ||||||
|  | #define MCHECK(expr) furi_check((expr) == 0) | ||||||
|  | 
 | ||||||
| #define U2F_CMD_REGISTER 0x01 | #define U2F_CMD_REGISTER 0x01 | ||||||
| #define U2F_CMD_AUTHENTICATE 0x02 | #define U2F_CMD_AUTHENTICATE 0x02 | ||||||
| #define U2F_CMD_VERSION 0x03 | #define U2F_CMD_VERSION 0x03 | ||||||
| @ -25,16 +29,26 @@ typedef enum { | |||||||
|         0x08, // "dont-enforce-user-presence-and-sign" - send auth response even if user is missing
 |         0x08, // "dont-enforce-user-presence-and-sign" - send auth response even if user is missing
 | ||||||
| } U2fAuthMode; | } U2fAuthMode; | ||||||
| 
 | 
 | ||||||
|  | #define U2F_HASH_SIZE 32 | ||||||
|  | #define U2F_NONCE_SIZE 32 | ||||||
|  | #define U2F_CHALLENGE_SIZE 32 | ||||||
|  | #define U2F_APP_ID_SIZE 32 | ||||||
|  | 
 | ||||||
|  | #define U2F_EC_KEY_SIZE 32 | ||||||
|  | #define U2F_EC_BIGNUM_SIZE 32 | ||||||
|  | #define U2F_EC_POINT_SIZE 65 | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t format; |     uint8_t format; | ||||||
|     uint8_t xy[64]; |     uint8_t xy[64]; | ||||||
| } __attribute__((packed)) U2fPubKey; | } FURI_PACKED U2fPubKey; | ||||||
|  | _Static_assert(sizeof(U2fPubKey) == U2F_EC_POINT_SIZE, "U2fPubKey size mismatch"); | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t len; |     uint8_t len; | ||||||
|     uint8_t hash[32]; |     uint8_t hash[U2F_HASH_SIZE]; | ||||||
|     uint8_t nonce[32]; |     uint8_t nonce[U2F_NONCE_SIZE]; | ||||||
| } __attribute__((packed)) U2fKeyHandle; | } FURI_PACKED U2fKeyHandle; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t cla; |     uint8_t cla; | ||||||
| @ -42,16 +56,16 @@ typedef struct { | |||||||
|     uint8_t p1; |     uint8_t p1; | ||||||
|     uint8_t p2; |     uint8_t p2; | ||||||
|     uint8_t len[3]; |     uint8_t len[3]; | ||||||
|     uint8_t challenge[32]; |     uint8_t challenge[U2F_CHALLENGE_SIZE]; | ||||||
|     uint8_t app_id[32]; |     uint8_t app_id[U2F_APP_ID_SIZE]; | ||||||
| } __attribute__((packed)) U2fRegisterReq; | } FURI_PACKED U2fRegisterReq; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t reserved; |     uint8_t reserved; | ||||||
|     U2fPubKey pub_key; |     U2fPubKey pub_key; | ||||||
|     U2fKeyHandle key_handle; |     U2fKeyHandle key_handle; | ||||||
|     uint8_t cert[]; |     uint8_t cert[]; | ||||||
| } __attribute__((packed)) U2fRegisterResp; | } FURI_PACKED U2fRegisterResp; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t cla; |     uint8_t cla; | ||||||
| @ -59,16 +73,16 @@ typedef struct { | |||||||
|     uint8_t p1; |     uint8_t p1; | ||||||
|     uint8_t p2; |     uint8_t p2; | ||||||
|     uint8_t len[3]; |     uint8_t len[3]; | ||||||
|     uint8_t challenge[32]; |     uint8_t challenge[U2F_CHALLENGE_SIZE]; | ||||||
|     uint8_t app_id[32]; |     uint8_t app_id[U2F_APP_ID_SIZE]; | ||||||
|     U2fKeyHandle key_handle; |     U2fKeyHandle key_handle; | ||||||
| } __attribute__((packed)) U2fAuthReq; | } FURI_PACKED U2fAuthReq; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t user_present; |     uint8_t user_present; | ||||||
|     uint32_t counter; |     uint32_t counter; | ||||||
|     uint8_t signature[]; |     uint8_t signature[]; | ||||||
| } __attribute__((packed)) U2fAuthResp; | } FURI_PACKED U2fAuthResp; | ||||||
| 
 | 
 | ||||||
| static const uint8_t ver_str[] = {"U2F_V2"}; | static const uint8_t ver_str[] = {"U2F_V2"}; | ||||||
| 
 | 
 | ||||||
| @ -78,19 +92,20 @@ static const uint8_t state_user_missing[] = {0x69, 0x85}; | |||||||
| static const uint8_t state_wrong_data[] = {0x6A, 0x80}; | static const uint8_t state_wrong_data[] = {0x6A, 0x80}; | ||||||
| 
 | 
 | ||||||
| struct U2fData { | struct U2fData { | ||||||
|     uint8_t device_key[32]; |     uint8_t device_key[U2F_EC_KEY_SIZE]; | ||||||
|     uint8_t cert_key[32]; |     uint8_t cert_key[U2F_EC_KEY_SIZE]; | ||||||
|     uint32_t counter; |     uint32_t counter; | ||||||
|     const struct uECC_Curve_t* p_curve; |  | ||||||
|     bool ready; |     bool ready; | ||||||
|     bool user_present; |     bool user_present; | ||||||
|     U2fEvtCallback callback; |     U2fEvtCallback callback; | ||||||
|     void* context; |     void* context; | ||||||
|  |     mbedtls_ecp_group group; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static int u2f_uecc_random(uint8_t* dest, unsigned size) { | static int u2f_uecc_random_cb(void* context, uint8_t* dest, unsigned size) { | ||||||
|  |     UNUSED(context); | ||||||
|     furi_hal_random_fill_buf(dest, size); |     furi_hal_random_fill_buf(dest, size); | ||||||
|     return 1; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| U2fData* u2f_alloc() { | U2fData* u2f_alloc() { | ||||||
| @ -99,6 +114,7 @@ U2fData* u2f_alloc() { | |||||||
| 
 | 
 | ||||||
| void u2f_free(U2fData* U2F) { | void u2f_free(U2fData* U2F) { | ||||||
|     furi_assert(U2F); |     furi_assert(U2F); | ||||||
|  |     mbedtls_ecp_group_free(&U2F->group); | ||||||
|     free(U2F); |     free(U2F); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -129,8 +145,8 @@ bool u2f_init(U2fData* U2F) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     U2F->p_curve = uECC_secp256r1(); |     mbedtls_ecp_group_init(&U2F->group); | ||||||
|     uECC_set_rng(u2f_uecc_random); |     mbedtls_ecp_group_load(&U2F->group, MBEDTLS_ECP_DP_SECP256R1); | ||||||
| 
 | 
 | ||||||
|     U2F->ready = true; |     U2F->ready = true; | ||||||
|     return true; |     return true; | ||||||
| @ -171,21 +187,63 @@ static uint8_t u2f_der_encode_signature(uint8_t* der, uint8_t* sig) { | |||||||
|     der[0] = 0x30; |     der[0] = 0x30; | ||||||
| 
 | 
 | ||||||
|     uint8_t len = 2; |     uint8_t len = 2; | ||||||
|     len += u2f_der_encode_int(der + len, sig, 32); |     len += u2f_der_encode_int(der + len, sig, U2F_HASH_SIZE); | ||||||
|     len += u2f_der_encode_int(der + len, sig + 32, 32); |     len += u2f_der_encode_int(der + len, sig + U2F_HASH_SIZE, U2F_HASH_SIZE); | ||||||
| 
 | 
 | ||||||
|     der[1] = len - 2; |     der[1] = len - 2; | ||||||
|     return len; |     return len; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void | ||||||
|  |     u2f_ecc_sign(mbedtls_ecp_group* grp, const uint8_t* key, uint8_t* hash, uint8_t* signature) { | ||||||
|  |     mbedtls_mpi r, s, d; | ||||||
|  | 
 | ||||||
|  |     mbedtls_mpi_init(&r); | ||||||
|  |     mbedtls_mpi_init(&s); | ||||||
|  |     mbedtls_mpi_init(&d); | ||||||
|  | 
 | ||||||
|  |     MCHECK(mbedtls_mpi_read_binary(&d, key, U2F_EC_KEY_SIZE)); | ||||||
|  |     MCHECK(mbedtls_ecdsa_sign(grp, &r, &s, &d, hash, U2F_HASH_SIZE, u2f_uecc_random_cb, NULL)); | ||||||
|  |     MCHECK(mbedtls_mpi_write_binary(&r, signature, U2F_EC_BIGNUM_SIZE)); | ||||||
|  |     MCHECK(mbedtls_mpi_write_binary(&s, signature + U2F_EC_BIGNUM_SIZE, U2F_EC_BIGNUM_SIZE)); | ||||||
|  | 
 | ||||||
|  |     mbedtls_mpi_free(&r); | ||||||
|  |     mbedtls_mpi_free(&s); | ||||||
|  |     mbedtls_mpi_free(&d); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void u2f_ecc_compute_public_key( | ||||||
|  |     mbedtls_ecp_group* grp, | ||||||
|  |     const uint8_t* private_key, | ||||||
|  |     U2fPubKey* public_key) { | ||||||
|  |     mbedtls_ecp_point Q; | ||||||
|  |     mbedtls_mpi d; | ||||||
|  |     size_t olen; | ||||||
|  | 
 | ||||||
|  |     mbedtls_ecp_point_init(&Q); | ||||||
|  |     mbedtls_mpi_init(&d); | ||||||
|  | 
 | ||||||
|  |     MCHECK(mbedtls_mpi_read_binary(&d, private_key, U2F_EC_KEY_SIZE)); | ||||||
|  |     MCHECK(mbedtls_ecp_mul(grp, &Q, &d, &grp->G, u2f_uecc_random_cb, NULL)); | ||||||
|  |     MCHECK(mbedtls_ecp_check_privkey(grp, &d)); | ||||||
|  | 
 | ||||||
|  |     MCHECK(mbedtls_ecp_point_write_binary( | ||||||
|  |         grp, &Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, (unsigned char*)public_key, sizeof(U2fPubKey))); | ||||||
|  | 
 | ||||||
|  |     mbedtls_ecp_point_free(&Q); | ||||||
|  |     mbedtls_mpi_free(&d); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ///////////////////////////////////////////
 | ||||||
|  | 
 | ||||||
| static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) { | static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) { | ||||||
|     U2fRegisterReq* req = (U2fRegisterReq*)buf; |     U2fRegisterReq* req = (U2fRegisterReq*)buf; | ||||||
|     U2fRegisterResp* resp = (U2fRegisterResp*)buf; |     U2fRegisterResp* resp = (U2fRegisterResp*)buf; | ||||||
|     U2fKeyHandle handle; |     U2fKeyHandle handle; | ||||||
|     uint8_t private[32]; |     uint8_t private[U2F_EC_KEY_SIZE]; | ||||||
|     U2fPubKey pub_key; |     U2fPubKey pub_key; | ||||||
|     uint8_t hash[32]; |     uint8_t hash[U2F_HASH_SIZE]; | ||||||
|     uint8_t signature[64]; |     uint8_t signature[U2F_EC_BIGNUM_SIZE * 2]; | ||||||
| 
 | 
 | ||||||
|     if(u2f_data_check(false) == false) { |     if(u2f_data_check(false) == false) { | ||||||
|         U2F->ready = false; |         U2F->ready = false; | ||||||
| @ -201,40 +259,54 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) { | |||||||
|     } |     } | ||||||
|     U2F->user_present = false; |     U2F->user_present = false; | ||||||
| 
 | 
 | ||||||
|     hmac_sha256_context hmac_ctx; |     handle.len = U2F_HASH_SIZE * 2; | ||||||
|     sha256_context sha_ctx; |  | ||||||
| 
 | 
 | ||||||
|     handle.len = 32 * 2; |  | ||||||
|     // Generate random nonce
 |     // Generate random nonce
 | ||||||
|     furi_hal_random_fill_buf(handle.nonce, 32); |     furi_hal_random_fill_buf(handle.nonce, 32); | ||||||
| 
 | 
 | ||||||
|  |     { | ||||||
|  |         mbedtls_md_context_t hmac_ctx; | ||||||
|  |         mbedtls_md_init(&hmac_ctx); | ||||||
|  |         MCHECK(mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1)); | ||||||
|  |         MCHECK(mbedtls_md_hmac_starts(&hmac_ctx, U2F->device_key, sizeof(U2F->device_key))); | ||||||
|  | 
 | ||||||
|         // Generate private key
 |         // Generate private key
 | ||||||
|     hmac_sha256_init(&hmac_ctx, U2F->device_key); |         MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id))); | ||||||
|     hmac_sha256_update(&hmac_ctx, req->app_id, 32); |         MCHECK(mbedtls_md_hmac_update(&hmac_ctx, handle.nonce, sizeof(handle.nonce))); | ||||||
|     hmac_sha256_update(&hmac_ctx, handle.nonce, 32); |         MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, private)); | ||||||
|     hmac_sha256_finish(&hmac_ctx, U2F->device_key, private); | 
 | ||||||
|  |         MCHECK(mbedtls_md_hmac_reset(&hmac_ctx)); | ||||||
| 
 | 
 | ||||||
|         // Generate private key handle
 |         // Generate private key handle
 | ||||||
|     hmac_sha256_init(&hmac_ctx, U2F->device_key); |         MCHECK(mbedtls_md_hmac_update(&hmac_ctx, private, sizeof(private))); | ||||||
|     hmac_sha256_update(&hmac_ctx, private, 32); |         MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id))); | ||||||
|     hmac_sha256_update(&hmac_ctx, req->app_id, 32); |         MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, handle.hash)); | ||||||
|     hmac_sha256_finish(&hmac_ctx, U2F->device_key, handle.hash); |     } | ||||||
| 
 | 
 | ||||||
|     // Generate public key
 |     // Generate public key
 | ||||||
|     pub_key.format = 0x04; // Uncompressed point
 |     u2f_ecc_compute_public_key(&U2F->group, private, &pub_key); | ||||||
|     uECC_compute_public_key(private, pub_key.xy, U2F->p_curve); |  | ||||||
| 
 | 
 | ||||||
|     // Generate signature
 |     // Generate signature
 | ||||||
|  |     { | ||||||
|         uint8_t reserved_byte = 0; |         uint8_t reserved_byte = 0; | ||||||
|     sha256_start(&sha_ctx); |  | ||||||
|     sha256_update(&sha_ctx, &reserved_byte, 1); |  | ||||||
|     sha256_update(&sha_ctx, req->app_id, 32); |  | ||||||
|     sha256_update(&sha_ctx, req->challenge, 32); |  | ||||||
|     sha256_update(&sha_ctx, handle.hash, handle.len); |  | ||||||
|     sha256_update(&sha_ctx, (uint8_t*)&pub_key, 65); |  | ||||||
|     sha256_finish(&sha_ctx, hash); |  | ||||||
| 
 | 
 | ||||||
|     uECC_sign(U2F->cert_key, hash, 32, signature, U2F->p_curve); |         mbedtls_sha256_context sha_ctx; | ||||||
|  | 
 | ||||||
|  |         mbedtls_sha256_init(&sha_ctx); | ||||||
|  |         mbedtls_sha256_starts(&sha_ctx, 0); | ||||||
|  | 
 | ||||||
|  |         mbedtls_sha256_update(&sha_ctx, &reserved_byte, 1); | ||||||
|  |         mbedtls_sha256_update(&sha_ctx, req->app_id, sizeof(req->app_id)); | ||||||
|  |         mbedtls_sha256_update(&sha_ctx, req->challenge, sizeof(req->challenge)); | ||||||
|  |         mbedtls_sha256_update(&sha_ctx, handle.hash, handle.len); | ||||||
|  |         mbedtls_sha256_update(&sha_ctx, (uint8_t*)&pub_key, sizeof(U2fPubKey)); | ||||||
|  | 
 | ||||||
|  |         mbedtls_sha256_finish(&sha_ctx, hash); | ||||||
|  |         mbedtls_sha256_free(&sha_ctx); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Sign hash
 | ||||||
|  |     u2f_ecc_sign(&U2F->group, U2F->cert_key, hash, signature); | ||||||
| 
 | 
 | ||||||
|     // Encode response message
 |     // Encode response message
 | ||||||
|     resp->reserved = 0x05; |     resp->reserved = 0x05; | ||||||
| @ -250,13 +322,11 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) { | |||||||
| static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) { | static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) { | ||||||
|     U2fAuthReq* req = (U2fAuthReq*)buf; |     U2fAuthReq* req = (U2fAuthReq*)buf; | ||||||
|     U2fAuthResp* resp = (U2fAuthResp*)buf; |     U2fAuthResp* resp = (U2fAuthResp*)buf; | ||||||
|     uint8_t priv_key[32]; |     uint8_t priv_key[U2F_EC_KEY_SIZE]; | ||||||
|     uint8_t mac_control[32]; |     uint8_t mac_control[32]; | ||||||
|     hmac_sha256_context hmac_ctx; |  | ||||||
|     sha256_context sha_ctx; |  | ||||||
|     uint8_t flags = 0; |     uint8_t flags = 0; | ||||||
|     uint8_t hash[32]; |     uint8_t hash[U2F_HASH_SIZE]; | ||||||
|     uint8_t signature[64]; |     uint8_t signature[U2F_HASH_SIZE * 2]; | ||||||
|     uint32_t be_u2f_counter; |     uint32_t be_u2f_counter; | ||||||
| 
 | 
 | ||||||
|     if(u2f_data_check(false) == false) { |     if(u2f_data_check(false) == false) { | ||||||
| @ -281,26 +351,42 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) { | |||||||
|     be_u2f_counter = lfs_tobe32(U2F->counter + 1); |     be_u2f_counter = lfs_tobe32(U2F->counter + 1); | ||||||
| 
 | 
 | ||||||
|     // Generate hash
 |     // Generate hash
 | ||||||
|     sha256_start(&sha_ctx); |     { | ||||||
|     sha256_update(&sha_ctx, req->app_id, 32); |         mbedtls_sha256_context sha_ctx; | ||||||
|     sha256_update(&sha_ctx, &flags, 1); | 
 | ||||||
|     sha256_update(&sha_ctx, (uint8_t*)&(be_u2f_counter), 4); |         mbedtls_sha256_init(&sha_ctx); | ||||||
|     sha256_update(&sha_ctx, req->challenge, 32); |         mbedtls_sha256_starts(&sha_ctx, 0); | ||||||
|     sha256_finish(&sha_ctx, hash); | 
 | ||||||
|  |         mbedtls_sha256_update(&sha_ctx, req->app_id, sizeof(req->app_id)); | ||||||
|  |         mbedtls_sha256_update(&sha_ctx, &flags, 1); | ||||||
|  |         mbedtls_sha256_update(&sha_ctx, (uint8_t*)&(be_u2f_counter), sizeof(be_u2f_counter)); | ||||||
|  |         mbedtls_sha256_update(&sha_ctx, req->challenge, sizeof(req->challenge)); | ||||||
|  | 
 | ||||||
|  |         mbedtls_sha256_finish(&sha_ctx, hash); | ||||||
|  |         mbedtls_sha256_free(&sha_ctx); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         mbedtls_md_context_t hmac_ctx; | ||||||
|  |         mbedtls_md_init(&hmac_ctx); | ||||||
|  |         MCHECK(mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1)); | ||||||
|  |         MCHECK(mbedtls_md_hmac_starts(&hmac_ctx, U2F->device_key, sizeof(U2F->device_key))); | ||||||
| 
 | 
 | ||||||
|         // Recover private key
 |         // Recover private key
 | ||||||
|     hmac_sha256_init(&hmac_ctx, U2F->device_key); |         MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id))); | ||||||
|     hmac_sha256_update(&hmac_ctx, req->app_id, 32); |         MCHECK(mbedtls_md_hmac_update( | ||||||
|     hmac_sha256_update(&hmac_ctx, req->key_handle.nonce, 32); |             &hmac_ctx, req->key_handle.nonce, sizeof(req->key_handle.nonce))); | ||||||
|     hmac_sha256_finish(&hmac_ctx, U2F->device_key, priv_key); |         MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, priv_key)); | ||||||
|  | 
 | ||||||
|  |         MCHECK(mbedtls_md_hmac_reset(&hmac_ctx)); | ||||||
| 
 | 
 | ||||||
|         // Generate and verify private key handle
 |         // Generate and verify private key handle
 | ||||||
|     hmac_sha256_init(&hmac_ctx, U2F->device_key); |         MCHECK(mbedtls_md_hmac_update(&hmac_ctx, priv_key, sizeof(priv_key))); | ||||||
|     hmac_sha256_update(&hmac_ctx, priv_key, 32); |         MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id))); | ||||||
|     hmac_sha256_update(&hmac_ctx, req->app_id, 32); |         MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, mac_control)); | ||||||
|     hmac_sha256_finish(&hmac_ctx, U2F->device_key, mac_control); |     } | ||||||
| 
 | 
 | ||||||
|     if(memcmp(req->key_handle.hash, mac_control, 32) != 0) { |     if(memcmp(req->key_handle.hash, mac_control, sizeof(mac_control)) != 0) { | ||||||
|         FURI_LOG_W(TAG, "Wrong handle!"); |         FURI_LOG_W(TAG, "Wrong handle!"); | ||||||
|         memcpy(&buf[0], state_wrong_data, 2); |         memcpy(&buf[0], state_wrong_data, 2); | ||||||
|         return 2; |         return 2; | ||||||
| @ -311,7 +397,8 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) { | |||||||
|         return 2; |         return 2; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     uECC_sign(priv_key, hash, 32, signature, U2F->p_curve); |     // Sign hash
 | ||||||
|  |     u2f_ecc_sign(&U2F->group, priv_key, hash, signature); | ||||||
| 
 | 
 | ||||||
|     resp->user_present = flags; |     resp->user_present = flags; | ||||||
|     resp->counter = be_u2f_counter; |     resp->counter = be_u2f_counter; | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ typedef struct { | |||||||
|     uint32_t counter; |     uint32_t counter; | ||||||
|     uint8_t random_salt[24]; |     uint8_t random_salt[24]; | ||||||
|     uint32_t control; |     uint32_t control; | ||||||
| } __attribute__((packed)) U2fCounterData; | } FURI_PACKED U2fCounterData; | ||||||
| 
 | 
 | ||||||
| bool u2f_data_check(bool cert_only) { | bool u2f_data_check(bool cert_only) { | ||||||
|     bool state = false; |     bool state = false; | ||||||
|  | |||||||
| @ -202,7 +202,7 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str) { | |||||||
|     return u8g2_GetStrWidth(&canvas->fb, str); |     return u8g2_GetStrWidth(&canvas->fb, str); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| uint8_t canvas_glyph_width(Canvas* canvas, char symbol) { | uint8_t canvas_glyph_width(Canvas* canvas, uint16_t symbol) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
|     return u8g2_GetGlyphWidth(&canvas->fb, symbol); |     return u8g2_GetGlyphWidth(&canvas->fb, symbol); | ||||||
| } | } | ||||||
|  | |||||||
| @ -214,7 +214,7 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str); | |||||||
|  * |  * | ||||||
|  * @return     width in pixels |  * @return     width in pixels | ||||||
|  */ |  */ | ||||||
| uint8_t canvas_glyph_width(Canvas* canvas, char symbol); | uint8_t canvas_glyph_width(Canvas* canvas, uint16_t symbol); | ||||||
| 
 | 
 | ||||||
| /** Draw bitmap picture at position defined by x,y.
 | /** Draw bitmap picture at position defined by x,y.
 | ||||||
|  * |  * | ||||||
|  | |||||||
| @ -1,9 +1,10 @@ | |||||||
| #include "flipper.pb.h" |  | ||||||
| #include "rpc_i.h" | #include "rpc_i.h" | ||||||
| #include "gui.pb.h" |  | ||||||
| #include <gui/gui_i.h> | #include <gui/gui_i.h> | ||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| 
 | 
 | ||||||
|  | #include <flipper.pb.h> | ||||||
|  | #include <gui.pb.h> | ||||||
|  | 
 | ||||||
| #define TAG "RpcGui" | #define TAG "RpcGui" | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "rpc.h" | #include "rpc.h" | ||||||
| #include "storage/filesystem_api_defines.h" | #include <storage/filesystem_api_defines.h> | ||||||
| #include <pb.h> | #include <pb.h> | ||||||
| #include <pb_decode.h> | #include <pb_decode.h> | ||||||
| #include <pb_encode.h> | #include <pb_encode.h> | ||||||
|  | |||||||
| @ -1,18 +1,18 @@ | |||||||
| #include "flipper.pb.h" |  | ||||||
| #include <core/common_defines.h> | #include <core/common_defines.h> | ||||||
| #include <core/memmgr.h> | #include <core/memmgr.h> | ||||||
| #include <core/record.h> | #include <core/record.h> | ||||||
| #include "pb_decode.h" | #include <rpc/rpc.h> | ||||||
| #include "rpc/rpc.h" | #include <rpc/rpc_i.h> | ||||||
| #include "rpc_i.h" | #include <storage/filesystem_api_defines.h> | ||||||
| #include "storage.pb.h" | #include <storage/storage.h> | ||||||
| #include "storage/filesystem_api_defines.h" |  | ||||||
| #include "storage/storage.h" |  | ||||||
| #include <stdint.h> |  | ||||||
| #include <lib/toolbox/md5_calc.h> | #include <lib/toolbox/md5_calc.h> | ||||||
| #include <lib/toolbox/path.h> | #include <lib/toolbox/path.h> | ||||||
| #include <update_util/lfs_backup.h> | #include <update_util/lfs_backup.h> | ||||||
| 
 | 
 | ||||||
|  | #include <pb_decode.h> | ||||||
|  | #include <storage.pb.h> | ||||||
|  | #include <flipper.pb.h> | ||||||
|  | 
 | ||||||
| #define TAG "RpcStorage" | #define TAG "RpcStorage" | ||||||
| 
 | 
 | ||||||
| #define MAX_NAME_LENGTH 255 | #define MAX_NAME_LENGTH 255 | ||||||
|  | |||||||
| Before Width: | Height: | Size: 2.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Alt_17x10.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 550 B | 
| Before Width: | Height: | Size: 2.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Cmd_17x10.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 556 B | 
| Before Width: | Height: | Size: 2.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Ctrl_17x10.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 552 B | 
| Before Width: | Height: | Size: 2.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Del_17x10.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 551 B | 
| Before Width: | Height: | Size: 2.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Esc_17x10.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 550 B | 
| Before Width: | Height: | Size: 2.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Tab_17x10.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 549 B | 
| @ -14,8 +14,22 @@ enum HidDebugSubmenuIndex { | |||||||
|     HidSubmenuIndexMouse, |     HidSubmenuIndexMouse, | ||||||
|     HidSubmenuIndexMouseClicker, |     HidSubmenuIndexMouseClicker, | ||||||
|     HidSubmenuIndexMouseJiggler, |     HidSubmenuIndexMouseJiggler, | ||||||
|  |     HidSubmenuIndexRemovePairing, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | static void bt_hid_remove_pairing(Bt* bt) { | ||||||
|  |     bt_disconnect(bt); | ||||||
|  | 
 | ||||||
|  |     // Wait 2nd core to update nvm storage
 | ||||||
|  |     furi_delay_ms(200); | ||||||
|  | 
 | ||||||
|  |     furi_hal_bt_stop_advertising(); | ||||||
|  | 
 | ||||||
|  |     bt_forget_bonded_devices(bt); | ||||||
|  | 
 | ||||||
|  |     furi_hal_bt_start_advertising(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void hid_submenu_callback(void* context, uint32_t index) { | static void hid_submenu_callback(void* context, uint32_t index) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     Hid* app = context; |     Hid* app = context; | ||||||
| @ -45,6 +59,8 @@ static void hid_submenu_callback(void* context, uint32_t index) { | |||||||
|     } else if(index == HidSubmenuIndexMouseJiggler) { |     } else if(index == HidSubmenuIndexMouseJiggler) { | ||||||
|         app->view_id = HidViewMouseJiggler; |         app->view_id = HidViewMouseJiggler; | ||||||
|         view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); |         view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); | ||||||
|  |     } else if(index == HidSubmenuIndexRemovePairing) { | ||||||
|  |         bt_hid_remove_pairing(app->bt); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -143,6 +159,14 @@ Hid* hid_alloc(HidTransport transport) { | |||||||
|         HidSubmenuIndexMouseJiggler, |         HidSubmenuIndexMouseJiggler, | ||||||
|         hid_submenu_callback, |         hid_submenu_callback, | ||||||
|         app); |         app); | ||||||
|  |     if(transport == HidTransportBle) { | ||||||
|  |         submenu_add_item( | ||||||
|  |             app->device_type_submenu, | ||||||
|  |             "Remove Pairing", | ||||||
|  |             HidSubmenuIndexRemovePairing, | ||||||
|  |             hid_submenu_callback, | ||||||
|  |             app); | ||||||
|  |     } | ||||||
|     view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); |     view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); |         app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); | ||||||
|  | |||||||
| @ -49,7 +49,7 @@ typedef struct { | |||||||
| #define ROW_COUNT 7 | #define ROW_COUNT 7 | ||||||
| #define COLUMN_COUNT 12 | #define COLUMN_COUNT 12 | ||||||
| 
 | 
 | ||||||
| // 0 width items are not drawn, but there value is used
 | // 0 width items are not drawn, but their value is used
 | ||||||
| const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { | const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { | ||||||
|     { |     { | ||||||
|         {.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1}, |         {.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1}, | ||||||
| @ -140,17 +140,17 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { | |||||||
|         {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, |         {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         {.width = 2, .icon = &I_Ctrl_15x7, .value = HID_KEYBOARD_L_CTRL}, |         {.width = 2, .icon = &I_Ctrl_17x10, .value = HID_KEYBOARD_L_CTRL}, | ||||||
|         {.width = 0, .value = HID_KEYBOARD_L_CTRL}, |         {.width = 0, .value = HID_KEYBOARD_L_CTRL}, | ||||||
|         {.width = 2, .icon = &I_Alt_11x7, .value = HID_KEYBOARD_L_ALT}, |         {.width = 2, .icon = &I_Alt_17x10, .value = HID_KEYBOARD_L_ALT}, | ||||||
|         {.width = 0, .value = HID_KEYBOARD_L_ALT}, |         {.width = 0, .value = HID_KEYBOARD_L_ALT}, | ||||||
|         {.width = 2, .icon = &I_Cmd_15x7, .value = HID_KEYBOARD_L_GUI}, |         {.width = 2, .icon = &I_Cmd_17x10, .value = HID_KEYBOARD_L_GUI}, | ||||||
|         {.width = 0, .value = HID_KEYBOARD_L_GUI}, |         {.width = 0, .value = HID_KEYBOARD_L_GUI}, | ||||||
|         {.width = 2, .icon = &I_Tab_15x7, .value = HID_KEYBOARD_TAB}, |         {.width = 2, .icon = &I_Tab_17x10, .value = HID_KEYBOARD_TAB}, | ||||||
|         {.width = 0, .value = HID_KEYBOARD_TAB}, |         {.width = 0, .value = HID_KEYBOARD_TAB}, | ||||||
|         {.width = 2, .icon = &I_Esc_14x7, .value = HID_KEYBOARD_ESCAPE}, |         {.width = 2, .icon = &I_Esc_17x10, .value = HID_KEYBOARD_ESCAPE}, | ||||||
|         {.width = 0, .value = HID_KEYBOARD_ESCAPE}, |         {.width = 0, .value = HID_KEYBOARD_ESCAPE}, | ||||||
|         {.width = 2, .icon = &I_Del_12x7, .value = HID_KEYBOARD_DELETE_FORWARD}, |         {.width = 2, .icon = &I_Del_17x10, .value = HID_KEYBOARD_DELETE_FORWARD}, | ||||||
|         {.width = 0, .value = HID_KEYBOARD_DELETE_FORWARD}, |         {.width = 0, .value = HID_KEYBOARD_DELETE_FORWARD}, | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| #include "storage_move_to_sd.h" | #include "storage_move_to_sd.h" | ||||||
|  | 
 | ||||||
| #include <core/common_defines.h> | #include <core/common_defines.h> | ||||||
| #include <core/log.h> | #include <core/log.h> | ||||||
| #include "loader/loader.h" | #include <loader/loader.h> | ||||||
| #include <stdint.h> |  | ||||||
| #include <toolbox/dir_walk.h> | #include <toolbox/dir_walk.h> | ||||||
| #include <toolbox/path.h> | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_0.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_1.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_10.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_11.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_12.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_13.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_14.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_15.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_16.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_17.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_18.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_19.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_2.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_20.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_21.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_22.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_23.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_24.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_25.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_26.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_27.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_28.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_29.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_3.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_30.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_31.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_32.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_33.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_34.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_35.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_36.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_37.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_38.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_39.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_4.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_40.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_41.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_42.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_43.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_44.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_45.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_46.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_47.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_48.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_49.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_5.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_50.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_51.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_52.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_6.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L2_Secret_door_128x64/frame_7.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
 Aleksandr Kutuzov
						Aleksandr Kutuzov