├── .github ├── FUNDING.yml └── workflows │ └── build-fap.yml ├── .gitignore ├── vb_migrate_10px.png ├── icons ├── CommVB_32x20.png ├── Comm_32x20.png ├── Delete_32x20.png ├── CommWave_12x8.png ├── TextSaved_30x6.png ├── AppInteract_32x27.png ├── Background_128x64.png ├── CommReady_32x27.png ├── CommReturn_10x8.png ├── TextCleared_47x6.png ├── TextDeleted_46x6.png ├── TextTapApp_56x27.png ├── WrongDevice_32x27.png ├── CommComplete_32x20.png ├── LeftButtonBg_54x15.png ├── RightButtonBg_54x15.png ├── TextDeleteVb_49x13.png ├── TextRegister1_71x20.png ├── PulsemonLeftWait_15x15.png ├── PulsemonRightSad_15x15.png ├── PulsemonLeftStand_14x16.png ├── PulsemonRightHappy_14x16.png ├── TextClearCaptures_49x13.png ├── TextErrLoadCapture_65x13.png ├── TextErrReadFailed_71x20.png ├── TextErrSaveCapture_65x13.png ├── TextTransfersDone_51x13.png ├── PulsemonRightWaiting_15x16.png ├── TextErrLoadTemplate_69x13.png ├── TextRegisterErrNotVb_71x20.png ├── TextRegisterTapAgain_67x20.png ├── TextTransferCaptured_97x13.png ├── TextTransferDimCheck_70x20.png ├── TextTransferSecondDimCheck_71x20.png └── TextErrRegisterDifferentTag_47x20.png ├── .flipcorg └── gallery │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ └── 6.png ├── CHANGES.md ├── gui └── modules │ ├── file_select.h │ ├── variable_item_list_ex.h │ ├── variable_item_list_ex.c │ └── file_select.c ├── vb_migrate.h ├── README_catalog.md ├── application.fam ├── scenes ├── vb_migrate_scene_config.h ├── vb_migrate_scene.h ├── vb_migrate_scene.c ├── vb_migrate_scene_about.c ├── vb_migrate_scene_select.c ├── vb_migrate_scene_save_success.c ├── vb_migrate_scene_load.c ├── vb_migrate_scene_delete_captures_success.c ├── vb_migrate_scene_delete_success.c ├── vb_migrate_scene_main_menu.c ├── vb_migrate_scene_delete.c ├── vb_migrate_scene_info.c ├── vb_migrate_scene_delete_captures.c ├── vb_migrate_scene_register_save.c ├── vb_migrate_scene_dev_menu.c ├── vb_migrate_scene_to_app.c ├── vb_migrate_scene_from_app.c └── vb_migrate_scene_register.c ├── vb_tag.h ├── vb_migrate_i.h ├── .clang-format ├── vb_tag.c ├── README.md └── vb_migrate.c /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: caralynx 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/compile_commands.json 2 | dist/ 3 | -------------------------------------------------------------------------------- /vb_migrate_10px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/vb_migrate_10px.png -------------------------------------------------------------------------------- /icons/CommVB_32x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/CommVB_32x20.png -------------------------------------------------------------------------------- /icons/Comm_32x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/Comm_32x20.png -------------------------------------------------------------------------------- /icons/Delete_32x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/Delete_32x20.png -------------------------------------------------------------------------------- /.flipcorg/gallery/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/.flipcorg/gallery/1.png -------------------------------------------------------------------------------- /.flipcorg/gallery/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/.flipcorg/gallery/2.png -------------------------------------------------------------------------------- /.flipcorg/gallery/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/.flipcorg/gallery/3.png -------------------------------------------------------------------------------- /.flipcorg/gallery/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/.flipcorg/gallery/4.png -------------------------------------------------------------------------------- /.flipcorg/gallery/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/.flipcorg/gallery/5.png -------------------------------------------------------------------------------- /.flipcorg/gallery/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/.flipcorg/gallery/6.png -------------------------------------------------------------------------------- /icons/CommWave_12x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/CommWave_12x8.png -------------------------------------------------------------------------------- /icons/TextSaved_30x6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextSaved_30x6.png -------------------------------------------------------------------------------- /icons/AppInteract_32x27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/AppInteract_32x27.png -------------------------------------------------------------------------------- /icons/Background_128x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/Background_128x64.png -------------------------------------------------------------------------------- /icons/CommReady_32x27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/CommReady_32x27.png -------------------------------------------------------------------------------- /icons/CommReturn_10x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/CommReturn_10x8.png -------------------------------------------------------------------------------- /icons/TextCleared_47x6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextCleared_47x6.png -------------------------------------------------------------------------------- /icons/TextDeleted_46x6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextDeleted_46x6.png -------------------------------------------------------------------------------- /icons/TextTapApp_56x27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextTapApp_56x27.png -------------------------------------------------------------------------------- /icons/WrongDevice_32x27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/WrongDevice_32x27.png -------------------------------------------------------------------------------- /icons/CommComplete_32x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/CommComplete_32x20.png -------------------------------------------------------------------------------- /icons/LeftButtonBg_54x15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/LeftButtonBg_54x15.png -------------------------------------------------------------------------------- /icons/RightButtonBg_54x15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/RightButtonBg_54x15.png -------------------------------------------------------------------------------- /icons/TextDeleteVb_49x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextDeleteVb_49x13.png -------------------------------------------------------------------------------- /icons/TextRegister1_71x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextRegister1_71x20.png -------------------------------------------------------------------------------- /icons/PulsemonLeftWait_15x15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/PulsemonLeftWait_15x15.png -------------------------------------------------------------------------------- /icons/PulsemonRightSad_15x15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/PulsemonRightSad_15x15.png -------------------------------------------------------------------------------- /icons/PulsemonLeftStand_14x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/PulsemonLeftStand_14x16.png -------------------------------------------------------------------------------- /icons/PulsemonRightHappy_14x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/PulsemonRightHappy_14x16.png -------------------------------------------------------------------------------- /icons/TextClearCaptures_49x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextClearCaptures_49x13.png -------------------------------------------------------------------------------- /icons/TextErrLoadCapture_65x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextErrLoadCapture_65x13.png -------------------------------------------------------------------------------- /icons/TextErrReadFailed_71x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextErrReadFailed_71x20.png -------------------------------------------------------------------------------- /icons/TextErrSaveCapture_65x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextErrSaveCapture_65x13.png -------------------------------------------------------------------------------- /icons/TextTransfersDone_51x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextTransfersDone_51x13.png -------------------------------------------------------------------------------- /icons/PulsemonRightWaiting_15x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/PulsemonRightWaiting_15x16.png -------------------------------------------------------------------------------- /icons/TextErrLoadTemplate_69x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextErrLoadTemplate_69x13.png -------------------------------------------------------------------------------- /icons/TextRegisterErrNotVb_71x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextRegisterErrNotVb_71x20.png -------------------------------------------------------------------------------- /icons/TextRegisterTapAgain_67x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextRegisterTapAgain_67x20.png -------------------------------------------------------------------------------- /icons/TextTransferCaptured_97x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextTransferCaptured_97x13.png -------------------------------------------------------------------------------- /icons/TextTransferDimCheck_70x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextTransferDimCheck_70x20.png -------------------------------------------------------------------------------- /icons/TextTransferSecondDimCheck_71x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextTransferSecondDimCheck_71x20.png -------------------------------------------------------------------------------- /icons/TextErrRegisterDifferentTag_47x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/flipperzero-vb-migrate/HEAD/icons/TextErrRegisterDifferentTag_47x20.png -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # v1.2 2 | Updated for API 39.1 3 | 4 | # v1.1 5 | Updated data directory path and built for API 26.3 6 | 7 | # v1.0 Release to the Web 8 | Brand new UI, support for all VBs 9 | -------------------------------------------------------------------------------- /.github/workflows/build-fap.yml: -------------------------------------------------------------------------------- 1 | name: Build FAP 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | name: 'ufbt: Build for Dev branch' 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v3 11 | - name: Build with ufbt 12 | uses: flipperdevices/flipperzero-ufbt-action@v0.1.2 13 | id: build-app 14 | with: 15 | sdk-channel: release 16 | - name: Upload app artifacts 17 | uses: actions/upload-artifact@v3 18 | with: 19 | name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }} 20 | path: ${{ steps.build-app.outputs.fap-artifacts }} 21 | -------------------------------------------------------------------------------- /gui/modules/file_select.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file file_select.h 3 | * GUI: FileSelect view module API 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | typedef struct FileSelect FileSelect; 15 | 16 | typedef void (*FileSelectCallback)(bool result, void* context); 17 | 18 | FileSelect* file_select_alloc(); 19 | 20 | void file_select_free(FileSelect* file_select); 21 | View* file_select_get_view(FileSelect* file_select); 22 | 23 | void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context); 24 | void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension); 25 | void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size); 26 | bool file_select_init(FileSelect* file_select); 27 | void file_select_set_selected_file(FileSelect* file_select, const char* filename); 28 | 29 | #ifdef __cplusplus 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /vb_migrate.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #define VB_MIGRATE_VERSION "1.2" 24 | 25 | #define VB_MIGRATE_FOLDER STORAGE_APP_DATA_PATH_PREFIX 26 | 27 | typedef struct VbMigrate VbMigrate; 28 | 29 | VbMigrate* vb_migrate_alloc(); 30 | 31 | void vb_migrate_free(VbMigrate* inst); 32 | -------------------------------------------------------------------------------- /README_catalog.md: -------------------------------------------------------------------------------- 1 | # VB Lab Migration Assistant for Flipper Zero 2 | 3 | This app is designed to make transferring your characters from VB Lab and VBC 4 | Lab more convenient. 5 | 6 | Send me a Ko-fi if you found this useful: https://ko-fi.com/C0C81P4PX 7 | 8 | ## Background 9 | The Vital Bracelet Arena app is going to be the new companion app for the Vital 10 | Bracelet series of fitness bracelet toys, however the app does not support 11 | account linking from VB Lab and VBC Lab, and requires you to transfer characters 12 | one by one from those apps through your Vital Bracelet. Because the Vital 13 | Bracelet can only hold at most two characters at a time and requires inserting 14 | Dim/VBM to complete the transfer, transferring all of your characters may take a 15 | significant amount of time. The VB Lab Migration Assistant is designed to make 16 | this process faster by allowing you to store an unlimited amount of characters 17 | on the Flipper and to bypass the Dim loading process, therefore allowing you to 18 | transfer your characters without having to flip back and forth between the apps 19 | or wasting time loading data that will never be used. 20 | 21 | ## Usage 22 | See README.md in source repository: https://github.com/GMMan/flipperzero-vb-migrate/blob/master/README.md 23 | 24 | ## Credits 25 | Graphics by Aderek: https://twitter.com/AderekArt 26 | -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # VB Lab Migration Assistant for Flipper Zero 4 | # Copyright (C) 2022 cyanic 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | App( 20 | appid="vb_migrate", 21 | name="VB Migration Assistant", 22 | apptype=FlipperAppType.EXTERNAL, 23 | entry_point="vb_migrate_app", 24 | requires=["gui", "dialogs", "notification", "storage"], 25 | stack_size=2 * 1024, 26 | fap_version=(1,2), 27 | fap_libs=[], 28 | fap_icon="vb_migrate_10px.png", 29 | fap_category="NFC", 30 | fap_description="Makes transferring characters with VB Lab less cumbersome", 31 | fap_author="cyanic", 32 | fap_weburl="https://github.com/GMMan/flipperzero-vb-migrate", 33 | fap_icon_assets="icons", 34 | ) 35 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_config.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | ADD_SCENE(vb_migrate, main_menu, MainMenu) 20 | ADD_SCENE(vb_migrate, register, Register) 21 | ADD_SCENE(vb_migrate, register_save, RegisterSave) 22 | ADD_SCENE(vb_migrate, save_success, SaveSuccess) 23 | ADD_SCENE(vb_migrate, select, Select) 24 | ADD_SCENE(vb_migrate, load, Load) 25 | ADD_SCENE(vb_migrate, info, Info) 26 | ADD_SCENE(vb_migrate, dev_menu, DevMenu) 27 | ADD_SCENE(vb_migrate, delete, Delete) 28 | ADD_SCENE(vb_migrate, delete_success, DeleteSuccess) 29 | ADD_SCENE(vb_migrate, from_app, FromApp) 30 | ADD_SCENE(vb_migrate, to_app, ToApp) 31 | ADD_SCENE(vb_migrate, delete_captures, DeleteCaptures) 32 | ADD_SCENE(vb_migrate, delete_captures_success, DeleteCapturesSuccess) 33 | ADD_SCENE(vb_migrate, about, About) 34 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | // Generate scene id and total number 24 | #define ADD_SCENE(prefix, name, id) VbMigrateScene##id, 25 | typedef enum { 26 | #include "vb_migrate_scene_config.h" 27 | VbMigrateSceneNum, 28 | } VbMigrateScene; 29 | #undef ADD_SCENE 30 | 31 | extern const SceneManagerHandlers vb_migrate_scene_handlers; 32 | 33 | // Generate scene on_enter handlers declaration 34 | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); 35 | #include "vb_migrate_scene_config.h" 36 | #undef ADD_SCENE 37 | 38 | // Generate scene on_event handlers declaration 39 | #define ADD_SCENE(prefix, name, id) \ 40 | bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); 41 | #include "vb_migrate_scene_config.h" 42 | #undef ADD_SCENE 43 | 44 | // Generate scene on_exit handlers declaration 45 | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); 46 | #include "vb_migrate_scene_config.h" 47 | #undef ADD_SCENE 48 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "vb_migrate_scene.h" 20 | 21 | // Generate scene on_enter handlers array 22 | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, 23 | void (*const vb_migrate_on_enter_handlers[])(void*) = { 24 | #include "vb_migrate_scene_config.h" 25 | }; 26 | #undef ADD_SCENE 27 | 28 | // Generate scene on_event handlers array 29 | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, 30 | bool (*const vb_migrate_on_event_handlers[])(void* context, SceneManagerEvent event) = { 31 | #include "vb_migrate_scene_config.h" 32 | }; 33 | #undef ADD_SCENE 34 | 35 | // Generate scene on_exit handlers array 36 | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, 37 | void (*const vb_migrate_on_exit_handlers[])(void* context) = { 38 | #include "vb_migrate_scene_config.h" 39 | }; 40 | #undef ADD_SCENE 41 | 42 | // Initialize scene handlers configuration structure 43 | const SceneManagerHandlers vb_migrate_scene_handlers = { 44 | .on_enter_handlers = vb_migrate_on_enter_handlers, 45 | .on_event_handlers = vb_migrate_on_event_handlers, 46 | .on_exit_handlers = vb_migrate_on_exit_handlers, 47 | .scene_num = VbMigrateSceneNum, 48 | }; 49 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_about.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | 21 | void vb_migrate_scene_about_on_enter(void* context) { 22 | VbMigrate* inst = context; 23 | 24 | // Perform your setup here 25 | FuriString* temp_str = furi_string_alloc_printf( 26 | "\e#Information\n" 27 | "Version: %s\n" 28 | "Developed by: cyanic\n" 29 | "Graphics by: Aderek\n" 30 | "GitHub: https://github.com/GMMan/flipperzero-vb-migrate\n" 31 | "\n" 32 | "\e#Description\n" 33 | "Makes transferring\n" 34 | "characters with VB Lab/\n" 35 | "Arena less cumbersome", 36 | VB_MIGRATE_VERSION); 37 | 38 | Widget* widget = inst->widget; 39 | vb_migrate_add_bg(widget, VbMigrateBgTypeNone); 40 | widget_add_text_scroll_element(widget, 8, 16, 113, 33, furi_string_get_cstr(temp_str)); 41 | furi_string_free(temp_str); 42 | 43 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 44 | } 45 | 46 | bool vb_migrate_scene_about_on_event(void* context, SceneManagerEvent event) { 47 | UNUSED(context); 48 | UNUSED(event); 49 | return false; 50 | } 51 | 52 | void vb_migrate_scene_about_on_exit(void* context) { 53 | VbMigrate* inst = context; 54 | 55 | // Perform your cleanup here 56 | widget_reset(inst->widget); 57 | } 58 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_select.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | 21 | static void vb_migrate_scene_select_file_select_callback(bool result, void* context) { 22 | VbMigrate* inst = context; 23 | 24 | view_dispatcher_send_custom_event(inst->view_dispatcher, result); 25 | } 26 | 27 | void vb_migrate_scene_select_on_enter(void* context) { 28 | VbMigrate* inst = context; 29 | 30 | // Perform your setup here 31 | file_select_set_callback( 32 | inst->file_select, vb_migrate_scene_select_file_select_callback, inst); 33 | file_select_set_filter(inst->file_select, VB_MIGRATE_FOLDER, "*"); 34 | file_select_set_result_buffer(inst->file_select, inst->text_store, sizeof(inst->text_store)); 35 | file_select_init(inst->file_select); 36 | 37 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewFileSelect); 38 | } 39 | 40 | bool vb_migrate_scene_select_on_event(void* context, SceneManagerEvent event) { 41 | VbMigrate* inst = context; 42 | bool consumed = false; 43 | UNUSED(inst); 44 | 45 | if(event.type == SceneManagerEventTypeCustom) { 46 | if(event.event) { 47 | // File selected 48 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneLoad); 49 | consumed = true; 50 | } 51 | } 52 | return consumed; 53 | } 54 | 55 | void vb_migrate_scene_select_on_exit(void* context) { 56 | UNUSED(context); 57 | } 58 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_save_success.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | 21 | static void vb_migrate_scene_save_success_widget_callback( 22 | GuiButtonType result, 23 | InputType type, 24 | void* context) { 25 | VbMigrate* inst = context; 26 | 27 | if(type == InputTypeShort) { 28 | if(result == GuiButtonTypeRight) 29 | view_dispatcher_send_custom_event(inst->view_dispatcher, 0); 30 | } 31 | } 32 | 33 | void vb_migrate_scene_save_success_on_enter(void* context) { 34 | VbMigrate* inst = context; 35 | 36 | // Setup view 37 | Widget* widget = inst->widget; 38 | vb_migrate_add_bg(widget, VbMigrateBgTypeRightButton); 39 | widget_add_icon_element(widget, 11, 18, &I_CommComplete_32x20); 40 | widget_add_icon_element(widget, 48, 18, &I_TextSaved_30x6); 41 | widget_add_icon_element(widget, 9, 40, &I_PulsemonRightHappy_14x16); 42 | widget_add_button_element( 43 | widget, GuiButtonTypeRight, "OK", vb_migrate_scene_save_success_widget_callback, inst); 44 | 45 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 46 | } 47 | 48 | bool vb_migrate_scene_save_success_on_event(void* context, SceneManagerEvent event) { 49 | VbMigrate* inst = context; 50 | bool consumed = false; 51 | 52 | if(event.type == SceneManagerEventTypeCustom || event.type == SceneManagerEventTypeBack) { 53 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneInfo); 54 | consumed = true; 55 | } 56 | return consumed; 57 | } 58 | 59 | void vb_migrate_scene_save_success_on_exit(void* context) { 60 | VbMigrate* inst = context; 61 | 62 | // Clear view 63 | widget_reset(inst->widget); 64 | } 65 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_load.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022-2024 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | 21 | #define TAG "vb_migrate_scene_load" 22 | 23 | void vb_migrate_scene_load_on_enter(void* context) { 24 | VbMigrate* inst = context; 25 | 26 | // Perform your setup here 27 | vb_migrate_show_loading_popup(inst, true); 28 | view_dispatcher_send_custom_event(inst->view_dispatcher, 0); 29 | } 30 | 31 | bool vb_migrate_scene_load_on_event(void* context, SceneManagerEvent event) { 32 | VbMigrate* inst = context; 33 | bool consumed = false; 34 | 35 | if(event.type == SceneManagerEventTypeCustom) { 36 | if(vb_migrate_load_nfc(inst, inst->text_store, VB_MIGRATE_TEMPLATE_NAME)) { 37 | inst->num_captured = vb_migrate_count_captured_mons(inst, inst->text_store); 38 | 39 | const MfUltralightData* data = 40 | nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 41 | const BantBlock* bant = vb_tag_get_bant_block_const(data); 42 | const VbTagProduct* product = vb_tag_find_product(bant); 43 | inst->orig_product = product; 44 | if(product) { 45 | inst->orig_type = product->type; 46 | } else { 47 | inst->orig_type = VbTagTypeUnknown; 48 | } 49 | inst->override_type = inst->orig_type; 50 | 51 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneInfo); 52 | consumed = true; 53 | } else { 54 | consumed = scene_manager_previous_scene(inst->scene_manager); 55 | } 56 | vb_migrate_show_loading_popup(inst, false); 57 | } 58 | return consumed; 59 | } 60 | 61 | void vb_migrate_scene_load_on_exit(void* context) { 62 | UNUSED(context); 63 | } 64 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_delete_captures_success.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | 21 | static void vb_migrate_scene_delete_captures_success_widget_callback( 22 | GuiButtonType result, 23 | InputType type, 24 | void* context) { 25 | VbMigrate* inst = context; 26 | 27 | if(type == InputTypeShort) { 28 | if(result == GuiButtonTypeRight) 29 | view_dispatcher_send_custom_event(inst->view_dispatcher, 0); 30 | } 31 | } 32 | 33 | void vb_migrate_scene_delete_captures_success_on_enter(void* context) { 34 | VbMigrate* inst = context; 35 | 36 | // Perform your setup here 37 | Widget* widget = inst->widget; 38 | vb_migrate_add_bg(widget, VbMigrateBgTypeRightButton); 39 | widget_add_icon_element(widget, 11, 18, &I_Delete_32x20); 40 | widget_add_icon_element(widget, 48, 18, &I_TextCleared_47x6); 41 | widget_add_icon_element(widget, 9, 41, &I_PulsemonRightSad_15x15); 42 | widget_add_button_element( 43 | widget, 44 | GuiButtonTypeRight, 45 | "OK", 46 | vb_migrate_scene_delete_captures_success_widget_callback, 47 | inst); 48 | 49 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 50 | } 51 | 52 | bool vb_migrate_scene_delete_captures_success_on_event(void* context, SceneManagerEvent event) { 53 | VbMigrate* inst = context; 54 | bool consumed = false; 55 | 56 | if(event.type == SceneManagerEventTypeCustom || event.type == SceneManagerEventTypeBack) { 57 | consumed = scene_manager_search_and_switch_to_previous_scene( 58 | inst->scene_manager, VbMigrateSceneDevMenu); 59 | } 60 | return consumed; 61 | } 62 | 63 | void vb_migrate_scene_delete_captures_success_on_exit(void* context) { 64 | VbMigrate* inst = context; 65 | 66 | // Perform your cleanup here 67 | widget_reset(inst->widget); 68 | } 69 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_delete_success.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | 21 | static void vb_migrate_scene_delete_success_widget_callback( 22 | GuiButtonType result, 23 | InputType type, 24 | void* context) { 25 | VbMigrate* inst = context; 26 | if(type == InputTypeShort) { 27 | if(result == GuiButtonTypeRight) 28 | 29 | view_dispatcher_send_custom_event(inst->view_dispatcher, 0); 30 | } 31 | } 32 | 33 | void vb_migrate_scene_delete_success_on_enter(void* context) { 34 | VbMigrate* inst = context; 35 | 36 | // Perform your setup here 37 | Widget* widget = inst->widget; 38 | vb_migrate_add_bg(widget, VbMigrateBgTypeRightButton); 39 | widget_add_icon_element(widget, 11, 18, &I_Delete_32x20); 40 | widget_add_icon_element(widget, 48, 18, &I_TextDeleted_46x6); 41 | widget_add_icon_element(widget, 9, 41, &I_PulsemonRightSad_15x15); 42 | widget_add_button_element( 43 | widget, GuiButtonTypeRight, "OK", vb_migrate_scene_delete_success_widget_callback, inst); 44 | 45 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 46 | } 47 | 48 | bool vb_migrate_scene_delete_success_on_event(void* context, SceneManagerEvent event) { 49 | VbMigrate* inst = context; 50 | bool consumed = false; 51 | 52 | if(event.type == SceneManagerEventTypeCustom || event.type == SceneManagerEventTypeBack) { 53 | uint32_t back_scenes[] = {VbMigrateSceneSelect, VbMigrateSceneMainMenu}; 54 | consumed = scene_manager_search_and_switch_to_previous_scene_one_of( 55 | inst->scene_manager, back_scenes, COUNT_OF(back_scenes)); 56 | } 57 | return consumed; 58 | } 59 | 60 | void vb_migrate_scene_delete_success_on_exit(void* context) { 61 | VbMigrate* inst = context; 62 | 63 | // Perform your cleanup here 64 | widget_reset(inst->widget); 65 | } 66 | -------------------------------------------------------------------------------- /vb_tag.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022-2024 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | 24 | #define BANT_MAGIC (0x544E4142) 25 | 26 | typedef struct BantBlock BantBlock; 27 | 28 | typedef enum { 29 | VbTagTypeUnknown, 30 | VbTagTypeVBDM, 31 | VbTagTypeVBV, 32 | VbTagTypeVBC, 33 | VbTagTypeVH, 34 | VbTagTypeVBBE, 35 | VbTagTypeMax 36 | } VbTagType; 37 | 38 | typedef struct { 39 | uint16_t item_id; 40 | uint16_t item_no; 41 | const char* name; 42 | const char* short_name; 43 | VbTagType type; 44 | } VbTagProduct; 45 | 46 | typedef enum { 47 | VbTagStatusReady = 1 << 0, 48 | VbTagStatusDimReady = 1 << 1, 49 | VbTagStatusActiveIsAvatar = 1 << 2, 50 | VbTagStatusNewSpot = 1 << 3, 51 | } VbTagStatus; 52 | 53 | typedef enum { 54 | VbTagOperationIdle, 55 | VbTagOperationReady, 56 | VbTagOperationTransferToApp, 57 | VbTagOperationCheckDim, 58 | VbTagOperationReturnFromApp, 59 | VbTagOperationSpotInit, 60 | VbTagOperationSpotCommit, 61 | } VbTagOperation; 62 | 63 | BantBlock* vb_tag_get_bant_block(MfUltralightData* data); 64 | const BantBlock* vb_tag_get_bant_block_const(const MfUltralightData* data); 65 | const VbTagProduct* vb_tag_find_product(const BantBlock* bant); 66 | bool vb_tag_validate_product(const MfUltralightData* data); 67 | VbTagStatus vb_tag_get_status(const BantBlock* bant); 68 | void vb_tag_set_status(BantBlock* bant, VbTagStatus status); 69 | VbTagOperation vb_tag_get_operation(const BantBlock* bant); 70 | void vb_tag_set_operation(BantBlock* bant, VbTagOperation operation); 71 | const VbTagProduct* vb_tag_get_default_product(VbTagType type); 72 | void vb_tag_set_item_id_no(BantBlock* bant, const VbTagProduct* product); 73 | const char* vb_tag_get_tag_type_name(VbTagType type); 74 | bool vb_tag_get_app_flag(const BantBlock* bant); 75 | void vb_tag_set_app_flag(BantBlock* bant, bool value); 76 | bool vb_tag_is_vbbe(const BantBlock* bant); 77 | uint32_t vb_tag_get_nonce(const BantBlock* bant); 78 | void vb_tag_set_nonce(BantBlock* bant, uint32_t value); 79 | void vb_tag_set_random_nonce(BantBlock* bant); 80 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_main_menu.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | 21 | typedef enum { 22 | SubmenuMainIndexRegister, 23 | SubmenuMainIndexSelect, 24 | SubmenuMainIndexAbout, 25 | } SubmenuMainIndex; 26 | 27 | static void vb_migrate_scene_main_menu_submenu_callback(void* context, uint32_t index) { 28 | VbMigrate* inst = context; 29 | 30 | view_dispatcher_send_custom_event(inst->view_dispatcher, index); 31 | } 32 | 33 | void vb_migrate_scene_main_menu_on_enter(void* context) { 34 | VbMigrate* inst = context; 35 | Submenu* submenu = inst->submenu; 36 | 37 | submenu_add_item( 38 | submenu, 39 | "Register Vital Bracelet", 40 | SubmenuMainIndexRegister, 41 | vb_migrate_scene_main_menu_submenu_callback, 42 | inst); 43 | submenu_add_item( 44 | submenu, 45 | "Select Vital Bracelet", 46 | SubmenuMainIndexSelect, 47 | vb_migrate_scene_main_menu_submenu_callback, 48 | inst); 49 | submenu_add_item( 50 | submenu, "About", SubmenuMainIndexAbout, vb_migrate_scene_main_menu_submenu_callback, inst); 51 | 52 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewMenu); 53 | } 54 | 55 | bool vb_migrate_scene_main_menu_on_event(void* context, SceneManagerEvent event) { 56 | VbMigrate* inst = context; 57 | bool consumed = false; 58 | 59 | if(event.type == SceneManagerEventTypeCustom) { 60 | if(event.event == SubmenuMainIndexRegister) { 61 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneRegister); 62 | consumed = true; 63 | } else if(event.event == SubmenuMainIndexSelect) { 64 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneSelect); 65 | consumed = true; 66 | } else if(event.event == SubmenuMainIndexAbout) { 67 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneAbout); 68 | consumed = true; 69 | } 70 | } 71 | return consumed; 72 | } 73 | 74 | void vb_migrate_scene_main_menu_on_exit(void* context) { 75 | VbMigrate* inst = context; 76 | 77 | submenu_reset(inst->submenu); 78 | } 79 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_delete.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | #include "../vb_tag.h" 21 | 22 | static void 23 | vb_migrate_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) { 24 | VbMigrate* inst = context; 25 | 26 | if(type == InputTypeShort) { 27 | view_dispatcher_send_custom_event(inst->view_dispatcher, result); 28 | } 29 | } 30 | 31 | void vb_migrate_scene_delete_on_enter(void* context) { 32 | VbMigrate* inst = context; 33 | 34 | // Perform your setup here 35 | Widget* widget = inst->widget; 36 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton | VbMigrateBgTypeRightButton); 37 | widget_add_icon_element(widget, 11, 18, &I_Delete_32x20); 38 | widget_add_icon_element(widget, 48, 18, &I_TextDeleteVb_49x13); 39 | 40 | FuriString* temp_str = furi_string_alloc_printf("%d", inst->num_captured); 41 | widget_add_string_element( 42 | widget, 99, 24, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); 43 | furi_string_free(temp_str); 44 | 45 | widget_add_button_element( 46 | inst->widget, GuiButtonTypeLeft, "Cancel", vb_migrate_scene_delete_widget_callback, inst); 47 | widget_add_button_element( 48 | inst->widget, GuiButtonTypeRight, "Delete", vb_migrate_scene_delete_widget_callback, inst); 49 | 50 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 51 | } 52 | 53 | bool vb_migrate_scene_delete_on_event(void* context, SceneManagerEvent event) { 54 | VbMigrate* inst = context; 55 | bool consumed = false; 56 | 57 | if(event.type == SceneManagerEventTypeCustom) { 58 | if(event.event == GuiButtonTypeLeft) { 59 | consumed = scene_manager_previous_scene(inst->scene_manager); 60 | } else if(event.event == GuiButtonTypeRight) { 61 | if(vb_migrate_delete(inst, inst->text_store, true)) { 62 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneDeleteSuccess); 63 | consumed = true; 64 | } else { 65 | consumed = scene_manager_search_and_switch_to_previous_scene( 66 | inst->scene_manager, VbMigrateSceneSelect); 67 | } 68 | } 69 | } 70 | return consumed; 71 | } 72 | 73 | void vb_migrate_scene_delete_on_exit(void* context) { 74 | VbMigrate* inst = context; 75 | 76 | // Perform your cleanup here 77 | widget_reset(inst->widget); 78 | } 79 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_info.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | #include "../vb_tag.h" 21 | 22 | static void 23 | vb_migrate_scene_info_button_callback(GuiButtonType result, InputType type, void* context) { 24 | VbMigrate* inst = context; 25 | 26 | if(type == InputTypeShort) { 27 | if(result == GuiButtonTypeRight) { 28 | view_dispatcher_send_custom_event(inst->view_dispatcher, result); 29 | } 30 | } 31 | } 32 | 33 | void vb_migrate_scene_info_on_enter(void* context) { 34 | VbMigrate* inst = context; 35 | FuriString* temp_str = furi_string_alloc(); 36 | 37 | // Build info scroll 38 | // Name 39 | furi_string_cat_printf(temp_str, "\ec%s\n", inst->text_store); 40 | 41 | // Type 42 | if(inst->orig_product == NULL) 43 | furi_string_cat(temp_str, "Unknown product\n"); 44 | else 45 | furi_string_cat_printf(temp_str, "\e#%s\n", inst->orig_product->name); 46 | 47 | // Number of mons loaded 48 | furi_string_cat_printf(temp_str, "Charas. captured: %d", inst->num_captured); 49 | 50 | vb_migrate_add_bg(inst->widget, VbMigrateBgTypeRightButton); 51 | 52 | widget_add_text_scroll_element(inst->widget, 8, 16, 113, 33, furi_string_get_cstr(temp_str)); 53 | furi_string_free(temp_str); 54 | 55 | widget_add_button_element( 56 | inst->widget, GuiButtonTypeRight, "More", vb_migrate_scene_info_button_callback, inst); 57 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 58 | } 59 | 60 | bool vb_migrate_scene_info_on_event(void* context, SceneManagerEvent event) { 61 | VbMigrate* inst = context; 62 | bool consumed = false; 63 | UNUSED(inst); 64 | 65 | if(event.type == SceneManagerEventTypeCustom) { 66 | if(event.event == GuiButtonTypeRight) { 67 | // Reset menu selection index 68 | scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneDevMenu, 0); 69 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneDevMenu); 70 | consumed = true; 71 | } 72 | } else if(event.type == SceneManagerEventTypeBack) { 73 | uint32_t back_scenes[] = {VbMigrateSceneSelect, VbMigrateSceneMainMenu}; 74 | consumed = scene_manager_search_and_switch_to_previous_scene_one_of( 75 | inst->scene_manager, back_scenes, COUNT_OF(back_scenes)); 76 | } 77 | return consumed; 78 | } 79 | 80 | void vb_migrate_scene_info_on_exit(void* context) { 81 | VbMigrate* inst = context; 82 | 83 | // Perform your cleanup here 84 | widget_reset(inst->widget); 85 | } 86 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_delete_captures.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | 21 | static void vb_migrate_scene_delete_captures_widget_callback( 22 | GuiButtonType result, 23 | InputType type, 24 | void* context) { 25 | VbMigrate* inst = context; 26 | 27 | if(type == InputTypeShort) { 28 | view_dispatcher_send_custom_event(inst->view_dispatcher, result); 29 | } 30 | } 31 | 32 | void vb_migrate_scene_delete_captures_on_enter(void* context) { 33 | VbMigrate* inst = context; 34 | 35 | // Perform your setup here 36 | Widget* widget = inst->widget; 37 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton | VbMigrateBgTypeRightButton); 38 | widget_add_icon_element(widget, 11, 18, &I_Delete_32x20); 39 | widget_add_icon_element(widget, 48, 18, &I_TextClearCaptures_49x13); 40 | 41 | FuriString* temp_str = furi_string_alloc_printf("%d", inst->num_captured); 42 | widget_add_string_element( 43 | widget, 99, 24, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); 44 | furi_string_free(temp_str); 45 | 46 | widget_add_button_element( 47 | inst->widget, 48 | GuiButtonTypeLeft, 49 | "Cancel", 50 | vb_migrate_scene_delete_captures_widget_callback, 51 | inst); 52 | widget_add_button_element( 53 | inst->widget, 54 | GuiButtonTypeRight, 55 | "Delete", 56 | vb_migrate_scene_delete_captures_widget_callback, 57 | inst); 58 | 59 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 60 | } 61 | 62 | bool vb_migrate_scene_delete_captures_on_event(void* context, SceneManagerEvent event) { 63 | VbMigrate* inst = context; 64 | bool consumed = false; 65 | 66 | if(event.type == SceneManagerEventTypeCustom) { 67 | if(event.event == GuiButtonTypeLeft) { 68 | consumed = scene_manager_previous_scene(inst->scene_manager); 69 | } else if(event.event == GuiButtonTypeRight) { 70 | if(vb_migrate_delete(inst, inst->text_store, false)) { 71 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneDeleteCapturesSuccess); 72 | consumed = true; 73 | } else { 74 | consumed = scene_manager_previous_scene(inst->scene_manager); 75 | } 76 | inst->num_captured = vb_migrate_count_captured_mons(inst, inst->text_store); 77 | } 78 | } 79 | return consumed; 80 | } 81 | 82 | void vb_migrate_scene_delete_captures_on_exit(void* context) { 83 | VbMigrate* inst = context; 84 | 85 | // Perform your cleanup here 86 | widget_reset(inst->widget); 87 | } 88 | -------------------------------------------------------------------------------- /gui/modules/variable_item_list_ex.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file variable_item_list.h 3 | * GUI: VariableItemList view module API 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | typedef struct VariableItemListEx VariableItemListEx; 15 | typedef struct VariableItemEx VariableItemEx; 16 | typedef void (*VariableItemExChangeCallback)(VariableItemEx* item); 17 | typedef void (*VariableItemListExEnterCallback)(void* context, uint32_t index); 18 | 19 | /** Allocate and initialize VariableItemList 20 | * 21 | * @return VariableItemList* 22 | */ 23 | VariableItemListEx* variable_item_list_ex_alloc(); 24 | 25 | /** Deinitialize and free VariableItemList 26 | * 27 | * @param variable_item_list VariableItemList instance 28 | */ 29 | void variable_item_list_ex_free(VariableItemListEx* variable_item_list); 30 | 31 | /** Clear all elements from list 32 | * 33 | * @param variable_item_list VariableItemList instance 34 | */ 35 | void variable_item_list_ex_reset(VariableItemListEx* variable_item_list); 36 | 37 | /** Get VariableItemList View instance 38 | * 39 | * @param variable_item_list VariableItemList instance 40 | * 41 | * @return View instance 42 | */ 43 | View* variable_item_list_ex_get_view(VariableItemListEx* variable_item_list); 44 | 45 | /** Add item to VariableItemList 46 | * 47 | * @param variable_item_list VariableItemList instance 48 | * @param label item name 49 | * @param values_count item values count 50 | * @param change_callback called on value change in gui 51 | * @param context item context 52 | * 53 | * @return VariableItemEx* item instance 54 | */ 55 | VariableItemEx* variable_item_list_ex_add( 56 | VariableItemListEx* variable_item_list, 57 | const char* label, 58 | uint8_t values_count, 59 | VariableItemExChangeCallback change_callback, 60 | void* context, 61 | int32_t callback_index); 62 | 63 | /** Set enter callback 64 | * 65 | * @param variable_item_list VariableItemList instance 66 | * @param callback VariableItemListEnterCallback instance 67 | * @param context pointer to context 68 | */ 69 | void variable_item_list_ex_set_enter_callback( 70 | VariableItemListEx* variable_item_list, 71 | VariableItemListExEnterCallback callback, 72 | void* context); 73 | 74 | void variable_item_list_ex_set_selected_item(VariableItemListEx* variable_item_list, uint8_t index); 75 | 76 | uint8_t variable_item_list_ex_get_selected_item_index(VariableItemListEx* variable_item_list); 77 | 78 | /** Set item current selected index 79 | * 80 | * @param item VariableItemEx* instance 81 | * @param current_value_index The current value index 82 | */ 83 | void variable_item_ex_set_current_value_index(VariableItemEx* item, uint8_t current_value_index); 84 | 85 | /** Set number of values for item 86 | * 87 | * @param item VariableItemEx* instance 88 | * @param values_count The new values count 89 | */ 90 | void variable_item_ex_set_values_count(VariableItemEx* item, uint8_t values_count); 91 | 92 | /** Set item current selected text 93 | * 94 | * @param item VariableItemEx* instance 95 | * @param current_value_text The current value text 96 | */ 97 | void variable_item_ex_set_current_value_text(VariableItemEx* item, const char* current_value_text); 98 | 99 | /** Get item current selected index 100 | * 101 | * @param item VariableItemEx* instance 102 | * 103 | * @return uint8_t current selected index 104 | */ 105 | uint8_t variable_item_ex_get_current_value_index(VariableItemEx* item); 106 | 107 | /** Get item context 108 | * 109 | * @param item VariableItemEx* instance 110 | * 111 | * @return void* item context 112 | */ 113 | void* variable_item_ex_get_context(VariableItemEx* item); 114 | 115 | #ifdef __cplusplus 116 | } 117 | #endif 118 | -------------------------------------------------------------------------------- /vb_migrate_i.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022-2024 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | // #include 30 | #include "gui/modules/file_select.h" 31 | #include 32 | #include "gui/modules/variable_item_list_ex.h" 33 | 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "vb_migrate.h" 43 | #include "scenes/vb_migrate_scene.h" 44 | #include "vb_tag.h" 45 | 46 | #include "vb_migrate_icons.h" 47 | 48 | #define NFC_APP_EXTENSION ".nfc" 49 | #define VB_MIGRATE_TEMPLATE_NAME "template" NFC_APP_EXTENSION 50 | #define VB_MIGRATE_CAPTURE_FORMAT "%03d%s" 51 | 52 | #define VB_MIGRATE_MAX_DEV_NAME_LENGTH (30) 53 | 54 | struct VbMigrate { 55 | Gui* gui; 56 | Storage* storage; 57 | DialogsApp* dialogs; 58 | NotificationApp* notifications; 59 | ViewDispatcher* view_dispatcher; 60 | SceneManager* scene_manager; 61 | Submenu* submenu; 62 | Popup* popup; 63 | Widget* widget; 64 | // DialogEx* dialog_ex; 65 | FileSelect* file_select; 66 | TextInput* text_input; 67 | Loading* loading; 68 | VariableItemListEx* variable_list; 69 | Nfc* nfc; 70 | NfcListener* listener; 71 | NfcPoller* poller; 72 | NfcDevice* nfc_dev; 73 | MfUltralightData* data_work; 74 | char text_store[128]; 75 | uint8_t captured_pwd[MF_ULTRALIGHT_AUTH_PASSWORD_SIZE]; 76 | uint8_t captured_uid[7]; 77 | bool clear_account_id; 78 | int num_captured; 79 | int next_id; 80 | int num_sent; 81 | VbTagType orig_type; 82 | VbTagType override_type; 83 | const VbTagProduct* orig_product; 84 | }; 85 | 86 | typedef enum { 87 | VbMigrateViewMenu, 88 | VbMigrateViewPopup, 89 | VbMigrateViewWidget, 90 | VbMigrateViewTextInput, 91 | // VbMigrateViewDialogEx, 92 | VbMigrateViewFileSelect, 93 | VbMigrateViewLoading, 94 | VbMigrateViewVariableItemList, 95 | } VbMigrateView; 96 | 97 | typedef enum { 98 | VbMigrateBgTypeNone, 99 | VbMigrateBgTypeLeftButton = 1 << 0, 100 | VbMigrateBgTypeRightButton = 1 << 1, 101 | } VbMigrateBgType; 102 | 103 | void vb_migrate_blink_read(VbMigrate* inst); 104 | void vb_migrate_blink_emulate(VbMigrate* inst); 105 | void vb_migrate_blink_stop(VbMigrate* inst); 106 | void vb_migrate_text_store_set(VbMigrate* inst, const char* text, ...); 107 | void vb_migrate_text_store_clear(VbMigrate* inst); 108 | bool vb_migrate_save_nfc(VbMigrate* inst, const char* dev_name, const char* file_name); 109 | bool vb_migrate_load_nfc(VbMigrate* inst, const char* dev_name, const char* file_name); 110 | int vb_migrate_count_captured_mons(VbMigrate* inst, const char* dev_name); 111 | bool vb_migrate_delete(VbMigrate* inst, const char* dev_name, bool whole_vb); 112 | int vb_migrate_get_next_id(VbMigrate* inst, const char* dev_name, int i, bool is_load); 113 | void vb_migrate_show_loading_popup(VbMigrate* inst, bool show); 114 | void vb_migrate_add_bg(Widget* widget, VbMigrateBgType type); 115 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_register_save.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022-2024 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include 20 | 21 | #include "../vb_migrate_i.h" 22 | #include "../vb_tag.h" 23 | 24 | // This thing doesn't know what a FuriString is 25 | #include 26 | 27 | typedef enum { 28 | RegisterSaveEventTextInput, 29 | } RegisterSaveEvent; 30 | 31 | static void vb_migrate_scene_register_save_text_input_callback(void* context) { 32 | VbMigrate* inst = context; 33 | 34 | view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterSaveEventTextInput); 35 | } 36 | 37 | void vb_migrate_scene_register_save_on_enter(void* context) { 38 | VbMigrate* inst = context; 39 | 40 | // Setup view 41 | TextInput* text_input = inst->text_input; 42 | text_input_set_header_text(text_input, "Name this Vital Bracelet"); 43 | text_input_set_result_callback( 44 | text_input, 45 | vb_migrate_scene_register_save_text_input_callback, 46 | inst, 47 | inst->text_store, 48 | VB_MIGRATE_MAX_DEV_NAME_LENGTH, 49 | true); 50 | 51 | // Set default name 52 | FuriString* temp_str; 53 | const MfUltralightData* data = nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 54 | const BantBlock* bant = vb_tag_get_bant_block_const(data); 55 | const VbTagProduct* prod = vb_tag_find_product(bant); 56 | temp_str = furi_string_alloc_printf("%s_", prod->short_name); 57 | for(size_t i = 0; i < data->iso14443_3a_data->uid_len; ++i) { 58 | furi_string_cat_printf(temp_str, "%02x", data->iso14443_3a_data->uid[i]); 59 | } 60 | vb_migrate_text_store_set(inst, furi_string_get_cstr(temp_str)); 61 | furi_string_free(temp_str); 62 | 63 | // We're validating whether folder exists 64 | ValidatorIsFile* validator_is_file = 65 | validator_is_file_alloc_init(VB_MIGRATE_FOLDER, "/" VB_MIGRATE_TEMPLATE_NAME, NULL); 66 | text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); 67 | 68 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewTextInput); 69 | notification_message(inst->notifications, &sequence_set_green_255); 70 | } 71 | 72 | bool vb_migrate_scene_register_save_on_event(void* context, SceneManagerEvent event) { 73 | VbMigrate* inst = context; 74 | bool consumed = false; 75 | 76 | if(event.type == SceneManagerEventTypeCustom) { 77 | if(event.event == RegisterSaveEventTextInput) { 78 | if(strlen(inst->text_store) != 0) { 79 | vb_migrate_show_loading_popup(inst, true); 80 | if(vb_migrate_save_nfc(inst, inst->text_store, VB_MIGRATE_TEMPLATE_NAME)) { 81 | inst->num_captured = 0; 82 | 83 | const MfUltralightData* data = 84 | nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 85 | const BantBlock* bant = vb_tag_get_bant_block_const(data); 86 | const VbTagProduct* product = vb_tag_find_product(bant); 87 | inst->orig_product = product; 88 | if(product) { 89 | inst->orig_type = product->type; 90 | } else { 91 | inst->orig_type = VbTagTypeUnknown; 92 | } 93 | inst->override_type = inst->orig_type; 94 | 95 | // Go to success 96 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneSaveSuccess); 97 | } else { 98 | // Otherwise just stay here 99 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewTextInput); 100 | } 101 | vb_migrate_show_loading_popup(inst, false); 102 | 103 | consumed = true; 104 | } 105 | } 106 | } 107 | return consumed; 108 | } 109 | 110 | void vb_migrate_scene_register_save_on_exit(void* context) { 111 | VbMigrate* inst = context; 112 | 113 | // Clear view 114 | TextInput* text_input = inst->text_input; 115 | ValidatorIsFile* validator = text_input_get_validator_callback_context(text_input); 116 | text_input_set_validator(text_input, NULL, NULL); 117 | validator_is_file_free(validator); 118 | 119 | text_input_reset(text_input); 120 | notification_message_block(inst->notifications, &sequence_reset_green); 121 | } 122 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: AlwaysBreak 5 | AlignArrayOfStructures: None 6 | AlignConsecutiveMacros: None 7 | AlignConsecutiveAssignments: None 8 | AlignConsecutiveBitFields: None 9 | AlignConsecutiveDeclarations: None 10 | AlignEscapedNewlines: Left 11 | AlignOperands: Align 12 | AlignTrailingComments: false 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: false 15 | AllowShortEnumsOnASingleLine: true 16 | AllowShortBlocksOnASingleLine: Never 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: None 19 | AllowShortLambdasOnASingleLine: All 20 | AllowShortIfStatementsOnASingleLine: WithoutElse 21 | AllowShortLoopsOnASingleLine: true 22 | AlwaysBreakAfterDefinitionReturnType: None 23 | AlwaysBreakAfterReturnType: None 24 | AlwaysBreakBeforeMultilineStrings: false 25 | AlwaysBreakTemplateDeclarations: Yes 26 | AttributeMacros: 27 | - __capability 28 | BinPackArguments: false 29 | BinPackParameters: false 30 | BraceWrapping: 31 | AfterCaseLabel: false 32 | AfterClass: false 33 | AfterControlStatement: Never 34 | AfterEnum: false 35 | AfterFunction: false 36 | AfterNamespace: false 37 | AfterObjCDeclaration: false 38 | AfterStruct: false 39 | AfterUnion: false 40 | AfterExternBlock: false 41 | BeforeCatch: false 42 | BeforeElse: false 43 | BeforeLambdaBody: false 44 | BeforeWhile: false 45 | IndentBraces: false 46 | SplitEmptyFunction: true 47 | SplitEmptyRecord: true 48 | SplitEmptyNamespace: true 49 | BreakBeforeBinaryOperators: None 50 | BreakBeforeConceptDeclarations: true 51 | BreakBeforeBraces: Attach 52 | BreakBeforeInheritanceComma: false 53 | BreakInheritanceList: BeforeColon 54 | BreakBeforeTernaryOperators: false 55 | BreakConstructorInitializersBeforeComma: false 56 | BreakConstructorInitializers: BeforeComma 57 | BreakAfterJavaFieldAnnotations: false 58 | BreakStringLiterals: false 59 | ColumnLimit: 99 60 | CommentPragmas: '^ IWYU pragma:' 61 | QualifierAlignment: Leave 62 | CompactNamespaces: false 63 | ConstructorInitializerIndentWidth: 4 64 | ContinuationIndentWidth: 4 65 | Cpp11BracedListStyle: true 66 | DeriveLineEnding: true 67 | DerivePointerAlignment: false 68 | DisableFormat: false 69 | EmptyLineAfterAccessModifier: Never 70 | EmptyLineBeforeAccessModifier: LogicalBlock 71 | ExperimentalAutoDetectBinPacking: false 72 | PackConstructorInitializers: BinPack 73 | BasedOnStyle: '' 74 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 75 | AllowAllConstructorInitializersOnNextLine: true 76 | FixNamespaceComments: false 77 | ForEachMacros: 78 | - foreach 79 | - Q_FOREACH 80 | - BOOST_FOREACH 81 | IfMacros: 82 | - KJ_IF_MAYBE 83 | IncludeBlocks: Preserve 84 | IncludeCategories: 85 | - Regex: '.*' 86 | Priority: 1 87 | SortPriority: 0 88 | CaseSensitive: false 89 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 90 | Priority: 3 91 | SortPriority: 0 92 | CaseSensitive: false 93 | - Regex: '.*' 94 | Priority: 1 95 | SortPriority: 0 96 | CaseSensitive: false 97 | IncludeIsMainRegex: '(Test)?$' 98 | IncludeIsMainSourceRegex: '' 99 | IndentAccessModifiers: false 100 | IndentCaseLabels: false 101 | IndentCaseBlocks: false 102 | IndentGotoLabels: true 103 | IndentPPDirectives: None 104 | IndentExternBlock: AfterExternBlock 105 | IndentRequires: false 106 | IndentWidth: 4 107 | IndentWrappedFunctionNames: true 108 | InsertTrailingCommas: None 109 | JavaScriptQuotes: Leave 110 | JavaScriptWrapImports: true 111 | KeepEmptyLinesAtTheStartOfBlocks: false 112 | LambdaBodyIndentation: Signature 113 | MacroBlockBegin: '' 114 | MacroBlockEnd: '' 115 | MaxEmptyLinesToKeep: 1 116 | NamespaceIndentation: None 117 | ObjCBinPackProtocolList: Auto 118 | ObjCBlockIndentWidth: 4 119 | ObjCBreakBeforeNestedBlockParam: true 120 | ObjCSpaceAfterProperty: true 121 | ObjCSpaceBeforeProtocolList: true 122 | PenaltyBreakAssignment: 10 123 | PenaltyBreakBeforeFirstCallParameter: 30 124 | PenaltyBreakComment: 10 125 | PenaltyBreakFirstLessLess: 0 126 | PenaltyBreakOpenParenthesis: 0 127 | PenaltyBreakString: 10 128 | PenaltyBreakTemplateDeclaration: 10 129 | PenaltyExcessCharacter: 100 130 | PenaltyReturnTypeOnItsOwnLine: 60 131 | PenaltyIndentedWhitespace: 0 132 | PointerAlignment: Left 133 | PPIndentWidth: -1 134 | ReferenceAlignment: Pointer 135 | ReflowComments: false 136 | RemoveBracesLLVM: false 137 | SeparateDefinitionBlocks: Leave 138 | ShortNamespaceLines: 1 139 | SortIncludes: Never 140 | SortJavaStaticImport: Before 141 | SortUsingDeclarations: false 142 | SpaceAfterCStyleCast: false 143 | SpaceAfterLogicalNot: false 144 | SpaceAfterTemplateKeyword: true 145 | SpaceBeforeAssignmentOperators: true 146 | SpaceBeforeCaseColon: false 147 | SpaceBeforeCpp11BracedList: false 148 | SpaceBeforeCtorInitializerColon: true 149 | SpaceBeforeInheritanceColon: true 150 | SpaceBeforeParens: Never 151 | SpaceBeforeParensOptions: 152 | AfterControlStatements: false 153 | AfterForeachMacros: false 154 | AfterFunctionDefinitionName: false 155 | AfterFunctionDeclarationName: false 156 | AfterIfMacros: false 157 | AfterOverloadedOperator: false 158 | BeforeNonEmptyParentheses: false 159 | SpaceAroundPointerQualifiers: Default 160 | SpaceBeforeRangeBasedForLoopColon: true 161 | SpaceInEmptyBlock: false 162 | SpaceInEmptyParentheses: false 163 | SpacesBeforeTrailingComments: 1 164 | SpacesInAngles: Never 165 | SpacesInConditionalStatement: false 166 | SpacesInContainerLiterals: false 167 | SpacesInCStyleCastParentheses: false 168 | SpacesInLineCommentPrefix: 169 | Minimum: 1 170 | Maximum: -1 171 | SpacesInParentheses: false 172 | SpacesInSquareBrackets: false 173 | SpaceBeforeSquareBrackets: false 174 | BitFieldColonSpacing: Both 175 | Standard: c++03 176 | StatementAttributeLikeMacros: 177 | - Q_EMIT 178 | StatementMacros: 179 | - Q_UNUSED 180 | - QT_REQUIRE_VERSION 181 | TabWidth: 4 182 | UseCRLF: false 183 | UseTab: Never 184 | WhitespaceSensitiveMacros: 185 | - STRINGIZE 186 | - PP_STRINGIZE 187 | - BOOST_PP_STRINGIZE 188 | - NS_SWIFT_NAME 189 | - CF_SWIFT_NAME 190 | ... 191 | 192 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_dev_menu.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include "../vb_migrate_i.h" 20 | 21 | typedef enum { 22 | SubmenuDevMenuIndexTransferFromApp, 23 | SubmenuDevMenuIndexTransferToApp, 24 | SubmenuDevMenuClearAccountId, 25 | SubmenuDevMenuIndexSpoof, 26 | SubmenuDevMenuIndexClearCaptures, 27 | SubmenuDevMenuIndexDeleteVb, 28 | // ----- 29 | SubmenuDevMenuClearAccountIdOff, 30 | SubmenuDevMenuClearAccountIdOn, 31 | // ----- 32 | SubmenuDevMenuIndexSpoofSelection, // Always keep this last because we add tag type to it 33 | } SubmenuDevMenuIndex; 34 | 35 | static void vb_migrate_scene_dev_menu_var_list_enter_callback(void* context, uint32_t index) { 36 | VbMigrate* inst = context; 37 | 38 | view_dispatcher_send_custom_event(inst->view_dispatcher, index); 39 | } 40 | 41 | static VbTagType 42 | vb_migrate_scene_dev_menu_spoof_set_item_by_index(VariableItemEx* item, uint8_t index) { 43 | VbTagType type; 44 | if(index >= 2) { // VBC 45 | type = (VbTagType)(index + 2); 46 | } else { 47 | type = (VbTagType)(index + 1); 48 | } 49 | 50 | variable_item_ex_set_current_value_index(item, index); 51 | variable_item_ex_set_current_value_text(item, vb_tag_get_tag_type_name(type)); 52 | return type; 53 | } 54 | 55 | static uint8_t 56 | vb_migrate_scene_dev_menu_spoof_set_item_by_type(VariableItemEx* item, VbTagType type) { 57 | uint8_t index; 58 | if(type >= VbTagTypeVH) { 59 | index = (uint8_t)type - 2; 60 | } else { 61 | index = (uint8_t)type - 1; 62 | } 63 | 64 | variable_item_ex_set_current_value_index(item, index); 65 | variable_item_ex_set_current_value_text(item, vb_tag_get_tag_type_name(type)); 66 | return index; 67 | } 68 | 69 | static void vb_migrate_scene_dev_menu_spoof_change_callback(VariableItemEx* item) { 70 | VbMigrate* inst = variable_item_ex_get_context(item); 71 | uint8_t index = variable_item_ex_get_current_value_index(item); 72 | 73 | VbTagType tag_type = vb_migrate_scene_dev_menu_spoof_set_item_by_index(item, index); 74 | view_dispatcher_send_custom_event( 75 | inst->view_dispatcher, SubmenuDevMenuIndexSpoofSelection + tag_type); 76 | } 77 | 78 | static void vb_migrate_scene_dev_menu_clear_account_id_change_callback(VariableItemEx* item) { 79 | VbMigrate* inst = variable_item_ex_get_context(item); 80 | uint8_t index = variable_item_ex_get_current_value_index(item); 81 | 82 | variable_item_ex_set_current_value_text(item, index ? "On" : "Off"); 83 | view_dispatcher_send_custom_event( 84 | inst->view_dispatcher, 85 | index ? SubmenuDevMenuClearAccountIdOn : SubmenuDevMenuClearAccountIdOff); 86 | } 87 | 88 | void vb_migrate_scene_dev_menu_on_enter(void* context) { 89 | VbMigrate* inst = context; 90 | VariableItemListEx* variable_list = inst->variable_list; 91 | VariableItemEx* item; 92 | 93 | variable_item_list_ex_set_enter_callback( 94 | variable_list, vb_migrate_scene_dev_menu_var_list_enter_callback, inst); 95 | variable_item_list_ex_add( 96 | variable_list, "Transfer App > Flipper", 0, NULL, NULL, SubmenuDevMenuIndexTransferFromApp); 97 | 98 | if(inst->num_captured != 0) { 99 | variable_item_list_ex_add( 100 | variable_list, 101 | "Transfer Flipper > App", 102 | 0, 103 | NULL, 104 | NULL, 105 | SubmenuDevMenuIndexTransferToApp); 106 | } 107 | 108 | item = variable_item_list_ex_add( 109 | variable_list, 110 | "Unlink Account", 111 | 2, 112 | vb_migrate_scene_dev_menu_clear_account_id_change_callback, 113 | inst, 114 | SubmenuDevMenuClearAccountId); 115 | variable_item_ex_set_current_value_index(item, inst->clear_account_id ? 1 : 0); 116 | variable_item_ex_set_current_value_text(item, inst->clear_account_id ? "On" : "Off"); 117 | 118 | if(inst->orig_type == VbTagTypeVBDM || inst->orig_type == VbTagTypeVBV || 119 | inst->orig_type == VbTagTypeVH) { 120 | item = variable_item_list_ex_add( 121 | variable_list, 122 | "Spoof Version", 123 | VbTagTypeMax - 1 - 2, // Removing VBC from list, skip VBBE 124 | vb_migrate_scene_dev_menu_spoof_change_callback, 125 | inst, 126 | SubmenuDevMenuIndexSpoof); 127 | vb_migrate_scene_dev_menu_spoof_set_item_by_type(item, inst->override_type); 128 | } 129 | 130 | if(inst->num_captured != 0) { 131 | variable_item_list_ex_add( 132 | variable_list, "Clear Captures", 0, NULL, NULL, SubmenuDevMenuIndexClearCaptures); 133 | } 134 | 135 | variable_item_list_ex_add( 136 | variable_list, "Delete Vital Bracelet", 0, NULL, NULL, SubmenuDevMenuIndexDeleteVb); 137 | 138 | // variable_item_list_ex_set_selected_item( 139 | // variable_list, scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneDevMenu)); 140 | 141 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewVariableItemList); 142 | } 143 | 144 | bool vb_migrate_scene_dev_menu_on_event(void* context, SceneManagerEvent event) { 145 | VbMigrate* inst = context; 146 | bool consumed = false; 147 | 148 | if(event.type == SceneManagerEventTypeCustom) { 149 | // if(event.event < SubmenuDevMenuIndexSpoofSelection) { 150 | // scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneDevMenu, event.event); 151 | // } 152 | 153 | if(event.event == SubmenuDevMenuIndexTransferFromApp) { 154 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneFromApp); 155 | consumed = true; 156 | } else if(event.event == SubmenuDevMenuIndexTransferToApp) { 157 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneToApp); 158 | consumed = true; 159 | } else if(event.event == SubmenuDevMenuIndexClearCaptures) { 160 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneDeleteCaptures); 161 | consumed = true; 162 | } else if(event.event == SubmenuDevMenuIndexDeleteVb) { 163 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneDelete); 164 | consumed = true; 165 | } else if(event.event == SubmenuDevMenuClearAccountIdOff) { 166 | inst->clear_account_id = false; 167 | consumed = true; 168 | } else if(event.event == SubmenuDevMenuClearAccountIdOn) { 169 | inst->clear_account_id = true; 170 | consumed = true; 171 | } else if(event.event >= SubmenuDevMenuIndexSpoofSelection) { 172 | // Note: skipping SubmenuDevMenuIndexSpoof because there's nothing to do on enter 173 | inst->override_type = event.event - SubmenuDevMenuIndexSpoofSelection; 174 | consumed = true; 175 | } 176 | } 177 | return consumed; 178 | } 179 | 180 | void vb_migrate_scene_dev_menu_on_exit(void* context) { 181 | VbMigrate* inst = context; 182 | 183 | variable_item_list_ex_reset(inst->variable_list); 184 | } 185 | -------------------------------------------------------------------------------- /vb_tag.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022-2024 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include 20 | #include "vb_tag.h" 21 | 22 | #define VB_NAME_VBDM "VB Digital Monster" 23 | #define VB_NAME_VBV "VB Digivice -V-" 24 | #define VB_NAME_VBC "VB Characters" 25 | #define VB_NAME_VH "Vital Hero" 26 | #define VB_NAME_VBBE "Vital Bracelet BE" 27 | 28 | #define VB_NAME_VBDM_SHORT "VBDM" 29 | #define VB_NAME_VBV_SHORT "VBV" 30 | #define VB_NAME_VBC_SHORT "VBC" 31 | #define VB_NAME_VH_SHORT "VH" 32 | #define VB_NAME_VBBE_SHORT "VBBE" 33 | 34 | struct BantBlockCommon { 35 | uint32_t magic; 36 | // Note: this should be big endian, but for convenience, we'll treat them as little endian 37 | uint16_t item_id; 38 | uint16_t item_no; 39 | uint8_t status; 40 | } __attribute__((packed)); 41 | 42 | struct BantBlockVb { 43 | uint8_t dim_no; 44 | uint8_t operation; 45 | uint8_t reserved; 46 | uint8_t app_flag; 47 | uint8_t padding[3]; 48 | } __attribute__((packed)); 49 | 50 | struct BantBlockBe { 51 | uint8_t operation; 52 | uint16_t dim_no; 53 | uint8_t app_flag; 54 | uint8_t nonce[3]; 55 | } __attribute__((packed)); 56 | 57 | struct BantBlock { 58 | struct BantBlockCommon common; 59 | union { 60 | struct BantBlockVb vb; 61 | struct BantBlockBe be; 62 | }; 63 | } __attribute__((packed)); 64 | 65 | static const VbTagProduct vb_tag_valid_products[] = { 66 | {.item_id = 0x0200, 67 | .item_no = 0x0100, 68 | .name = VB_NAME_VBDM, 69 | .short_name = VB_NAME_VBDM_SHORT, 70 | .type = VbTagTypeVBDM}, 71 | {.item_id = 0x0200, 72 | .item_no = 0x0200, 73 | .name = VB_NAME_VBDM, 74 | .short_name = VB_NAME_VBDM_SHORT, 75 | .type = VbTagTypeVBDM}, 76 | {.item_id = 0x0200, 77 | .item_no = 0x0300, 78 | .name = VB_NAME_VBDM, 79 | .short_name = VB_NAME_VBDM_SHORT, 80 | .type = VbTagTypeVBDM}, 81 | {.item_id = 0x0200, 82 | .item_no = 0x0400, 83 | .name = VB_NAME_VBV, 84 | .short_name = VB_NAME_VBV_SHORT, 85 | .type = VbTagTypeVBV}, 86 | {.item_id = 0x0200, 87 | .item_no = 0x0500, 88 | .name = VB_NAME_VBV, 89 | .short_name = VB_NAME_VBV_SHORT, 90 | .type = VbTagTypeVBV}, 91 | {.item_id = 0x0200, 92 | .item_no = 0x0600, 93 | .name = VB_NAME_VH, 94 | .short_name = VB_NAME_VH_SHORT, 95 | .type = VbTagTypeVH}, 96 | {.item_id = 0x0300, 97 | .item_no = 0x0100, 98 | .name = VB_NAME_VBC, 99 | .short_name = VB_NAME_VBC_SHORT, 100 | .type = VbTagTypeVBC}, 101 | {.item_id = 0x0400, 102 | .item_no = 0x0100, 103 | .name = VB_NAME_VBBE, 104 | .short_name = VB_NAME_VBBE_SHORT, 105 | .type = VbTagTypeVBBE}, 106 | }; 107 | 108 | static const char* vb_tag_type_names[] = { 109 | "Unknown", 110 | VB_NAME_VBDM_SHORT, 111 | VB_NAME_VBV_SHORT, 112 | VB_NAME_VBC_SHORT, 113 | VB_NAME_VH_SHORT, 114 | VB_NAME_VBBE_SHORT, 115 | }; 116 | 117 | BantBlock* vb_tag_get_bant_block(MfUltralightData* data) { 118 | return (BantBlock*)&data->page[4].data; 119 | } 120 | 121 | const BantBlock* vb_tag_get_bant_block_const(const MfUltralightData* data) { 122 | return (BantBlock*)&data->page[4].data; 123 | } 124 | 125 | const VbTagProduct* vb_tag_find_product(const BantBlock* bant) { 126 | for(size_t i = 0; i < COUNT_OF(vb_tag_valid_products); ++i) { 127 | const VbTagProduct* product = &vb_tag_valid_products[i]; 128 | if(bant->common.item_id == product->item_id && bant->common.item_no == product->item_no) 129 | return product; 130 | } 131 | 132 | return NULL; 133 | } 134 | 135 | bool vb_tag_validate_product(const MfUltralightData* data) { 136 | // Must be NTAG I2C Plus 1k 137 | if(data->type != MfUltralightTypeNTAGI2CPlus1K) return false; 138 | // Must match one of the known product IDs 139 | const BantBlock* bant = vb_tag_get_bant_block_const(data); 140 | if(bant->common.magic != BANT_MAGIC) return false; 141 | return vb_tag_find_product(bant) != NULL; 142 | } 143 | 144 | VbTagStatus vb_tag_get_status(const BantBlock* bant) { 145 | return bant->common.status; 146 | } 147 | 148 | void vb_tag_set_status(BantBlock* bant, VbTagStatus status) { 149 | bant->common.status = status; 150 | } 151 | 152 | VbTagOperation vb_tag_get_operation(const BantBlock* bant) { 153 | return vb_tag_is_vbbe(bant) ? bant->be.operation : bant->vb.operation; 154 | } 155 | 156 | void vb_tag_set_operation(BantBlock* bant, VbTagOperation operation) { 157 | uint8_t* p_op = vb_tag_is_vbbe(bant) ? &bant->be.operation : &bant->vb.operation; 158 | *p_op = operation; 159 | } 160 | 161 | const VbTagProduct* vb_tag_get_default_product(VbTagType type) { 162 | // IMPORTANT: Update when vb_tag_valid_products changes 163 | switch(type) { 164 | case VbTagTypeVBDM: 165 | return &vb_tag_valid_products[2]; 166 | case VbTagTypeVBV: 167 | return &vb_tag_valid_products[4]; 168 | case VbTagTypeVBC: 169 | return &vb_tag_valid_products[6]; 170 | case VbTagTypeVH: 171 | return &vb_tag_valid_products[5]; 172 | case VbTagTypeVBBE: 173 | return &vb_tag_valid_products[7]; 174 | 175 | default: 176 | return NULL; 177 | } 178 | } 179 | 180 | void vb_tag_set_item_id_no(BantBlock* bant, const VbTagProduct* product) { 181 | bant->common.item_id = product->item_id; 182 | bant->common.item_no = product->item_no; 183 | } 184 | 185 | const char* vb_tag_get_tag_type_name(VbTagType type) { 186 | if(type < VbTagTypeMax) { 187 | return vb_tag_type_names[type]; 188 | } else { 189 | return NULL; 190 | } 191 | } 192 | 193 | bool vb_tag_get_app_flag(const BantBlock* bant) { 194 | uint8_t app_flag = vb_tag_is_vbbe(bant) ? bant->be.app_flag : bant->vb.app_flag; 195 | return app_flag == 1; 196 | } 197 | 198 | void vb_tag_set_app_flag(BantBlock* bant, bool value) { 199 | uint8_t* app_flag = vb_tag_is_vbbe(bant) ? &bant->be.app_flag : &bant->vb.app_flag; 200 | *app_flag = value ? 1 : 0xff; 201 | } 202 | 203 | // Lookup is expensive, let's check tag ID directly 204 | bool vb_tag_is_vbbe(const BantBlock* bant) { 205 | return bant->common.item_id == 0x0400; 206 | } 207 | 208 | uint32_t vb_tag_get_nonce(const BantBlock* bant) { 209 | if(vb_tag_is_vbbe(bant)) { 210 | return (bant->be.nonce[0] << 16) | (bant->be.nonce[1] << 8) | bant->be.nonce[2]; 211 | } else { 212 | return 0; 213 | } 214 | } 215 | 216 | void vb_tag_set_nonce(BantBlock* bant, uint32_t value) { 217 | if(vb_tag_is_vbbe(bant)) { 218 | uint8_t* nonce = bant->be.nonce; 219 | nonce[0] = value >> 16; 220 | nonce[1] = value >> 8; 221 | nonce[2] = value; 222 | } 223 | } 224 | 225 | void vb_tag_set_random_nonce(BantBlock* bant) { 226 | if(vb_tag_is_vbbe(bant)) { 227 | uint32_t orig_nonce = vb_tag_get_nonce(bant); 228 | uint32_t new_nonce; 229 | do { 230 | new_nonce = rand() & 0xffffff; 231 | // Original nonce is generated as ((rand() % 0xffff) << 8) | (rand() % 0xff), 232 | // so don't inclue 0xffff** and 0x****ff as valid values 233 | if((new_nonce & 0xff) == 0xff) --new_nonce; 234 | if(new_nonce >= 0xffff00) new_nonce -= 0x100; 235 | } while(new_nonce == 0 || new_nonce == orig_nonce); 236 | vb_tag_set_nonce(bant, new_nonce); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VB Lab Migration Assistant for Flipper Zero 2 | =========================================== 3 | 4 | This app is designed to make transferring your characters from VB Lab and VBC 5 | Lab more convenient. 6 | 7 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/C0C81P4PX) 8 | 9 | Background 10 | ---------- 11 | The Vital Bracelet Arena app is going to be the new companion app for the Vital 12 | Bracelet series of fitness bracelet toys, however the app does not support 13 | account linking from VB Lab and VBC Lab, and requires you to transfer characters 14 | one by one from those apps through your Vital Bracelet. Because the Vital 15 | Bracelet can only hold at most two characters at a time and requires inserting 16 | Dim/VBM to complete the transfer, transferring all of your characters may take a 17 | significant amount of time. The VB Lab Migration Assistant is designed to make 18 | this process faster by allowing you to store an unlimited amount of characters 19 | on the Flipper and to bypass the Dim loading process, therefore allowing you to 20 | transfer your characters without having to flip back and forth between the apps 21 | or wasting time loading data that will never be used. 22 | 23 | Usage 24 | ----- 25 | 26 | ### Update notes 27 | 28 | As of Flipper Zero firmware version 0.79.1, application data is stored in a 29 | dedicated folder. As of app version 1.1, application data is stored in 30 | `app_data/vb_migrate` under the SD card. If you have used a previous version of 31 | this app, please move the `vb_migrate` folder on your card into the `app_data` 32 | folder to resume access to previously registered VBs. 33 | 34 | ### 1. Register Vital Bracelet 35 | 36 | Registering your Vital Bracelet to the Flipper creates template data used for 37 | emulating the VB using the Flipper. 38 | 39 | - Select "Register Vital Bracelet" from Migration Assistant's main menu. 40 | - If you have an active character on your Vital Bracelet, **transfer it to VB 41 | Lab/Arena**. The active character slot on the VB has to be empty to avoid 42 | issues when transferring character from VB Lab/Arena to the Flipper. Press the 43 | right key on Flipper. 44 | 45 | If using VB Lab: 46 | - If there is an active character in VB Lab, put it in storage. 47 | - On VB Lab's "Scan" page, tap "Vital Bracelet -> App". Press the right key on 48 | Flipper. 49 | 50 | If using VB Arena: 51 | - Select the device type matching your VB and any franchise. 52 | - Tap the "Send" button. Press the right key on Flipper. 53 | 54 | - Tap the Flipper to your Vital Bracelet. You do not have to put the VB in scan 55 | mode. When Flipper finishes reading, it will beep. 56 | - On VB Lab, tap "Send", or on VB Arena, tap "Transfer Data", and tap the 57 | Flipper to your phone. VB Lab/Arena will give you an error message. This is 58 | normal, and you can dismiss it. The Flipper will beep. Wait for it to switch 59 | to the next screen. 60 | - Tap the Flipper to the original Vital Bracelet again. The Flipper will beep 61 | when it's finished reading. 62 | - Name the Vital Bracelet and select "Save". 63 | 64 | The Vital Bracelet info will show after saving. 65 | 66 | ### 2. Select Vital Bracelet, Vital Bracelet Info 67 | 68 | Use the "Select Vital Bracelet" menu to load a previously registered VB. After 69 | loading, you will see information about this Vital Bracelet: 70 | 71 | - Registered name 72 | - Vital Bracelet type 73 | - Number of characters captured 74 | 75 | Press the right key to see a menu of options you can use with the registered VB. 76 | 77 | ### Spoof Version 78 | 79 | Vital Bracelet Arena splits Vital Bracelet and Vital Hero Digimon into 80 | mutually-exclusive categories, and currently transferring VBM characters from 81 | VBDM has not been tested, so if you want to transfer between categories, you can 82 | select the Vital Bracelet version to spoof to change the VB type and force the 83 | app to perform transfers between categories. 84 | 85 | Note that spoofing a Vital Bracelet Characters from a non-VBC registration and 86 | vice versa will not work because different encryption keys are used. 87 | 88 | ### Unlink Account 89 | 90 | This option unsets the account flag in the NFC data so the character is not 91 | bound to any particular account, and you can transfer it to a different Vital 92 | Bracelet that is using a different account when using VB Arena. 93 | 94 | If enabled when transferring from app, the flag will be unset on the capture and 95 | will still be unset when transferring to app. If enabled when transferring to 96 | app, the flag will be unset on all transfers, regardless whether the flag was 97 | set when captured. 98 | 99 | Note that VB Lab may still complain because it also uses character ID embedded 100 | in character data to check whether the character belongs to the current account. 101 | 102 | ### 3. Transfer App > Flipper 103 | 104 | Use this mode to transfer characters from VB Lab to Flipper. 105 | 106 | - An instructions screen is displayed. Press right key to continue. 107 | 108 | If using VB Lab: 109 | - On your VB Lab app, wake up the character you want to send (if you have not 110 | already). 111 | - Go to the "Scan" screen, and tap "App -> Vital Bracelet". Select "Check Dim 112 | Card". If prompted to send Special Missions or items, tap "No". 113 | 114 | If using VB Arena: 115 | - Select the device and franchise for the character you want to transfer. 116 | - Tap the "Assign" button. 117 | - Press and hold on the character you want to transfer. 118 | - Tap the "Send" button. 119 | - Tap "OK". 120 | - Tap "Check Card". 121 | 122 | - Tap the Flipper to your phone. Release when VB Lab indicates to do so, and 123 | wait for Flipper to beep. 124 | - Tap "Check Dim/Card Installation" on VB Lab/Arena, and tap Flipper to your 125 | phone. Flipper will beep. 126 | - Tap "Send" on VB Lab, or "Transfer Data" on VB Arena, and tap Flipper to your 127 | phone. Hold it there until VB Lab/Arena shows the transfer animation. Flipper 128 | will beep after it has received the character. 129 | - Tap "OK" on VB Lab/Arena. Flipper will have automatically reloaded the 130 | template so you can repeat the above again to transfer more characters. 131 | - When you are done transferring characters, press the left key on Flipper to 132 | exit the transfer mode. 133 | 134 | Note if you have previously transferred characters to Flipper, the new 135 | characters you transfer will be put at the end of the sequence of characters 136 | when transferring from Flipper to app. 137 | 138 | ### 4. Transfer Flipper > App 139 | 140 | Use this mode to transfer characters from Flipper to VB Lab. 141 | 142 | - An instructions screen is displayed. Press right key to continue. 143 | 144 | If using VB Lab: 145 | - If there is an active character in VB Lab, put it in storage. 146 | - On VB Lab's "Scan" page, tap "Vital Bracelet -> App". 147 | - Tap "Send". 148 | 149 | If using VB Arena: 150 | - Select the device type matching your VB and any franchise. 151 | - Tap the "Send" button. 152 | - Tap the "Transfer Data" button. 153 | 154 | - Tap your Flipper to your phone, and hold until VB Lab/Arena shows the 155 | - transfer animation. Flipper will beep when it detected the transfer and will 156 | - load the next character. 157 | - Repeat the above until all of your characters have been transferred. 158 | - If you want to skip a certain character, press the right key to skip and load 159 | - the next character. 160 | - You will see the "Transfers complete" screen on Flipper when all characters 161 | - have been transferred. Press the right key to return to the menu. 162 | 163 | ### 5. Clear Captures 164 | 165 | If you are doing transfers in batches, you can clear the currently captured 166 | characters so you do not have to skip them when transferring to app. Check the 167 | details, and press the right key to confirm deletion. 168 | 169 | ### 6. Delete Vital Bracelet 170 | 171 | If you want to unregister the Vital Bracelet, you can use this option to delete 172 | its captures and remove it from the "Select Vital Bracelet" list. Check the 173 | details, and press the right key to confirm deletion. 174 | 175 | Credits 176 | ------- 177 | 178 | Graphics by [Aderek](https://twitter.com/AderekArt) 179 | -------------------------------------------------------------------------------- /vb_migrate.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022-2024 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include 20 | 21 | #include "vb_migrate_i.h" 22 | 23 | #define TAG "vb_migrate" 24 | 25 | bool vb_migrate_custom_event_callback(void* context, uint32_t event) { 26 | furi_assert(context); 27 | VbMigrate* inst = context; 28 | return scene_manager_handle_custom_event(inst->scene_manager, event); 29 | } 30 | 31 | bool vb_migrate_back_event_callback(void* context) { 32 | furi_assert(context); 33 | VbMigrate* inst = context; 34 | return scene_manager_handle_back_event(inst->scene_manager); 35 | } 36 | 37 | void vb_migrate_blink_read(VbMigrate* inst) { 38 | notification_message(inst->notifications, &sequence_blink_start_cyan); 39 | } 40 | 41 | void vb_migrate_blink_emulate(VbMigrate* inst) { 42 | notification_message(inst->notifications, &sequence_blink_start_magenta); 43 | } 44 | 45 | void vb_migrate_blink_stop(VbMigrate* inst) { 46 | notification_message_block(inst->notifications, &sequence_blink_stop); 47 | } 48 | 49 | void vb_migrate_text_store_set(VbMigrate* inst, const char* text, ...) { 50 | va_list args; 51 | va_start(args, text); 52 | 53 | vsnprintf(inst->text_store, sizeof(inst->text_store), text, args); 54 | 55 | va_end(args); 56 | } 57 | 58 | void vb_migrate_text_store_clear(VbMigrate* inst) { 59 | memset(inst->text_store, 0, sizeof(inst->text_store)); 60 | } 61 | 62 | bool vb_migrate_save_nfc(VbMigrate* inst, const char* dev_name, const char* file_name) { 63 | bool saved = false; 64 | FuriString* temp_str = furi_string_alloc(); 65 | 66 | do { 67 | furi_string_printf(temp_str, "%s/%s", VB_MIGRATE_FOLDER, dev_name); 68 | if(!storage_simply_mkdir(inst->storage, furi_string_get_cstr(temp_str))) { 69 | dialog_message_show_storage_error(inst->dialogs, "Can not create\ndata folder"); 70 | break; 71 | } 72 | furi_string_cat_printf(temp_str, "/%s", file_name); 73 | saved = nfc_device_save(inst->nfc_dev, furi_string_get_cstr(temp_str)); 74 | } while(false); 75 | 76 | furi_string_free(temp_str); 77 | return saved; 78 | } 79 | 80 | bool vb_migrate_load_nfc(VbMigrate* inst, const char* dev_name, const char* file_name) { 81 | bool loaded = false; 82 | FuriString* temp_str = 83 | furi_string_alloc_printf("%s/%s/%s", VB_MIGRATE_FOLDER, dev_name, file_name); 84 | loaded = nfc_device_load(inst->nfc_dev, furi_string_get_cstr(temp_str)); 85 | 86 | furi_string_free(temp_str); 87 | return loaded; 88 | } 89 | 90 | bool vb_migrate_delete(VbMigrate* inst, const char* dev_name, bool whole_vb) { 91 | bool deleted = false; 92 | FuriString* dir_path = furi_string_alloc_printf("%s/%s", VB_MIGRATE_FOLDER, dev_name); 93 | 94 | if(whole_vb) { 95 | deleted = storage_simply_remove_recursive(inst->storage, furi_string_get_cstr(dir_path)); 96 | } else { 97 | File* dir_handle = storage_file_alloc(inst->storage); 98 | if(storage_dir_open(dir_handle, furi_string_get_cstr(dir_path))) { 99 | FileInfo file_info; 100 | char name[256]; 101 | FuriString* file_path = furi_string_alloc(); 102 | while(storage_dir_read(dir_handle, &file_info, name, sizeof(name))) { 103 | // Files that is .nfc, but is not template 104 | if(!(file_info.flags & FSF_DIRECTORY) && strstr(name, NFC_APP_EXTENSION) && 105 | !strstr(name, VB_MIGRATE_TEMPLATE_NAME)) { 106 | furi_string_printf(file_path, "%s/%s", furi_string_get_cstr(dir_path), name); 107 | deleted = 108 | storage_simply_remove(inst->storage, furi_string_get_cstr(file_path)); 109 | if(!deleted) break; 110 | } 111 | } 112 | 113 | furi_string_free(file_path); 114 | storage_dir_close(dir_handle); 115 | } 116 | storage_file_free(dir_handle); 117 | } 118 | 119 | furi_string_free(dir_path); 120 | return deleted; 121 | } 122 | 123 | int vb_migrate_count_captured_mons(VbMigrate* inst, const char* dev_name) { 124 | int count = 0; 125 | 126 | FuriString* dir_path = furi_string_alloc_printf("%s/%s", VB_MIGRATE_FOLDER, dev_name); 127 | File* dir_handle = storage_file_alloc(inst->storage); 128 | if(storage_dir_open(dir_handle, furi_string_get_cstr(dir_path))) { 129 | FileInfo file_info; 130 | char name[256]; 131 | while(storage_dir_read(dir_handle, &file_info, name, sizeof(name))) { 132 | // Files that is .nfc, but is not template 133 | if(!(file_info.flags & FSF_DIRECTORY) && strstr(name, NFC_APP_EXTENSION) && 134 | !strstr(name, VB_MIGRATE_TEMPLATE_NAME)) 135 | ++count; 136 | } 137 | 138 | storage_dir_close(dir_handle); 139 | } 140 | storage_file_free(dir_handle); 141 | furi_string_free(dir_path); 142 | 143 | return count; 144 | } 145 | 146 | int vb_migrate_get_next_id(VbMigrate* inst, const char* dev_name, int i, bool is_load) { 147 | FuriString* dir_path = furi_string_alloc_printf("%s/%s", VB_MIGRATE_FOLDER, dev_name); 148 | FuriString* file_path = furi_string_alloc(); 149 | while(true) { 150 | furi_string_printf( 151 | file_path, 152 | "%s/" VB_MIGRATE_CAPTURE_FORMAT, 153 | furi_string_get_cstr(dir_path), 154 | i, 155 | NFC_APP_EXTENSION); 156 | bool exit_cond = 157 | storage_common_stat(inst->storage, furi_string_get_cstr(file_path), NULL) == 158 | FSE_NOT_EXIST; 159 | if(is_load) exit_cond = !exit_cond; 160 | if(exit_cond) break; 161 | ++i; 162 | } 163 | 164 | furi_string_free(file_path); 165 | furi_string_free(dir_path); 166 | return i; 167 | } 168 | 169 | void vb_migrate_show_loading_popup(VbMigrate* inst, bool show) { 170 | if(show) { 171 | // Raise timer priority so that animations can play 172 | furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); 173 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewLoading); 174 | } else { 175 | // Restore default timer priority 176 | furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); 177 | } 178 | } 179 | 180 | VbMigrate* vb_migrate_alloc() { 181 | VbMigrate* inst = malloc(sizeof(VbMigrate)); 182 | 183 | inst->view_dispatcher = view_dispatcher_alloc(); 184 | inst->scene_manager = scene_manager_alloc(&vb_migrate_scene_handlers, inst); 185 | view_dispatcher_enable_queue(inst->view_dispatcher); 186 | view_dispatcher_set_event_callback_context(inst->view_dispatcher, inst); 187 | view_dispatcher_set_custom_event_callback( 188 | inst->view_dispatcher, vb_migrate_custom_event_callback); 189 | view_dispatcher_set_navigation_event_callback( 190 | inst->view_dispatcher, vb_migrate_back_event_callback); 191 | 192 | inst->clear_account_id = false; 193 | 194 | // GUI 195 | inst->gui = furi_record_open(RECORD_GUI); 196 | 197 | // Storage 198 | inst->storage = furi_record_open(RECORD_STORAGE); 199 | 200 | // Notifications service 201 | inst->notifications = furi_record_open(RECORD_NOTIFICATION); 202 | 203 | // Dialogs 204 | inst->dialogs = furi_record_open(RECORD_DIALOGS); 205 | 206 | // NFC 207 | inst->nfc = nfc_alloc(); 208 | inst->nfc_dev = nfc_device_alloc(); 209 | inst->data_work = mf_ultralight_alloc(); 210 | 211 | // Submenu 212 | inst->submenu = submenu_alloc(); 213 | view_dispatcher_add_view( 214 | inst->view_dispatcher, VbMigrateViewMenu, submenu_get_view(inst->submenu)); 215 | 216 | // Popup 217 | inst->popup = popup_alloc(); 218 | view_dispatcher_add_view( 219 | inst->view_dispatcher, VbMigrateViewPopup, popup_get_view(inst->popup)); 220 | 221 | // Widget 222 | inst->widget = widget_alloc(); 223 | view_dispatcher_add_view( 224 | inst->view_dispatcher, VbMigrateViewWidget, widget_get_view(inst->widget)); 225 | 226 | // File select 227 | inst->file_select = file_select_alloc(); 228 | view_dispatcher_add_view( 229 | inst->view_dispatcher, VbMigrateViewFileSelect, file_select_get_view(inst->file_select)); 230 | 231 | // Text input 232 | inst->text_input = text_input_alloc(); 233 | view_dispatcher_add_view( 234 | inst->view_dispatcher, VbMigrateViewTextInput, text_input_get_view(inst->text_input)); 235 | 236 | // Loading 237 | inst->loading = loading_alloc(); 238 | view_dispatcher_add_view( 239 | inst->view_dispatcher, VbMigrateViewLoading, loading_get_view(inst->loading)); 240 | 241 | // Variable item list 242 | inst->variable_list = variable_item_list_ex_alloc(); 243 | view_dispatcher_add_view( 244 | inst->view_dispatcher, 245 | VbMigrateViewVariableItemList, 246 | variable_item_list_ex_get_view(inst->variable_list)); 247 | 248 | // Dialog ex 249 | // inst->dialog_ex = dialog_ex_alloc(); 250 | // view_dispatcher_add_view( 251 | // inst->view_dispatcher, VbMigrateViewDialogEx, dialog_ex_get_view(inst->dialog_ex)); 252 | 253 | return inst; 254 | } 255 | 256 | void vb_migrate_free(VbMigrate* inst) { 257 | // Dialog ex 258 | // view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewDialogEx); 259 | // dialog_ex_free(inst->dialog_ex); 260 | 261 | // Variable item list 262 | view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewVariableItemList); 263 | variable_item_list_ex_free(inst->variable_list); 264 | 265 | // Loading 266 | view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewLoading); 267 | loading_free(inst->loading); 268 | 269 | // Text input 270 | view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewTextInput); 271 | text_input_free(inst->text_input); 272 | 273 | // File select 274 | view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewFileSelect); 275 | file_select_free(inst->file_select); 276 | 277 | // Widget 278 | view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewWidget); 279 | widget_free(inst->widget); 280 | 281 | // Popup 282 | view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewPopup); 283 | popup_free(inst->popup); 284 | 285 | // Submenu 286 | view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewMenu); 287 | submenu_free(inst->submenu); 288 | 289 | // NFC 290 | mf_ultralight_free(inst->data_work); 291 | nfc_device_free(inst->nfc_dev); 292 | nfc_free(inst->nfc); 293 | 294 | furi_record_close(RECORD_DIALOGS); 295 | furi_record_close(RECORD_NOTIFICATION); 296 | furi_record_close(RECORD_STORAGE); 297 | furi_record_close(RECORD_GUI); 298 | 299 | view_dispatcher_free(inst->view_dispatcher); 300 | scene_manager_free(inst->scene_manager); 301 | 302 | free(inst); 303 | } 304 | 305 | int32_t vb_migrate_app(void* p) { 306 | UNUSED(p); 307 | 308 | VbMigrate* inst = vb_migrate_alloc(); 309 | view_dispatcher_attach_to_gui(inst->view_dispatcher, inst->gui, ViewDispatcherTypeFullscreen); 310 | 311 | if(storage_simply_mkdir(inst->storage, VB_MIGRATE_FOLDER)) { 312 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneMainMenu); 313 | view_dispatcher_run(inst->view_dispatcher); 314 | } else { 315 | dialog_message_show_storage_error(inst->dialogs, "Can not create\napp folder"); 316 | } 317 | 318 | vb_migrate_free(inst); 319 | return 0; 320 | } 321 | 322 | void vb_migrate_add_bg(Widget* widget, VbMigrateBgType type) { 323 | widget_add_icon_element(widget, 0, 0, &I_Background_128x64); 324 | if(type & VbMigrateBgTypeLeftButton) { 325 | widget_add_icon_element(widget, 0, 49, &I_LeftButtonBg_54x15); 326 | } 327 | if(type & VbMigrateBgTypeRightButton) { 328 | widget_add_icon_element(widget, 74, 49, &I_RightButtonBg_54x15); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_to_app.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022-2024 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include 20 | 21 | #include "../vb_migrate_i.h" 22 | #include "../vb_tag.h" 23 | 24 | #define TAG "vb_migrate_scene_to_app" 25 | 26 | typedef enum { 27 | ToAppStateInitial, 28 | ToAppStateInstructions, 29 | ToAppStateEmulateReady, 30 | ToAppStateEmulateTransferToApp, 31 | ToAppStateLoadError, 32 | ToAppStateComplete, 33 | } ToAppState; 34 | 35 | typedef enum { 36 | ToAppEventTypeWidgetLeft, 37 | ToAppEventTypeWidgetRight, 38 | ToAppEventTypeEmulateStart, 39 | ToAppEventTypeCaptureLoadError, 40 | ToAppEventTypeTagWrite, 41 | } ToAppEventType; 42 | 43 | static void 44 | vb_migrate_scene_to_app_widget_callback(GuiButtonType result, InputType type, void* context) { 45 | VbMigrate* inst = context; 46 | 47 | if(type == InputTypeShort) { 48 | if(result == GuiButtonTypeLeft) 49 | view_dispatcher_send_custom_event(inst->view_dispatcher, ToAppEventTypeWidgetLeft); 50 | else if(result == GuiButtonTypeRight) 51 | view_dispatcher_send_custom_event(inst->view_dispatcher, ToAppEventTypeWidgetRight); 52 | } 53 | } 54 | 55 | static NfcCommand vb_migrate_scene_to_app_worker_callback(NfcGenericEvent event, void* context) { 56 | VbMigrate* inst = context; 57 | NfcCommand result = NfcCommandContinue; 58 | 59 | if(event.protocol == NfcProtocolInvalid) { 60 | NfcEvent* nfc_event = event.event_data; 61 | if(nfc_event->type == NfcEventTypeRxEnd) { 62 | const MfUltralightData* data = 63 | nfc_listener_get_data(inst->listener, NfcProtocolMfUltralight); 64 | const BantBlock* bant = vb_tag_get_bant_block_const(data); 65 | if(vb_tag_get_operation(bant) == VbTagOperationTransferToApp) { 66 | nfc_device_set_data(inst->nfc_dev, NfcProtocolMfUltralight, data); 67 | view_dispatcher_send_custom_event(inst->view_dispatcher, ToAppEventTypeTagWrite); 68 | } 69 | } 70 | } 71 | 72 | return result; 73 | } 74 | 75 | static void vb_migrate_scene_to_app_set_nfc_state(VbMigrate* inst, ToAppState state) { 76 | nfc_device_copy_data(inst->nfc_dev, NfcProtocolMfUltralight, inst->data_work); 77 | BantBlock* bant = vb_tag_get_bant_block(inst->data_work); 78 | if(state == ToAppStateEmulateReady) { 79 | vb_tag_set_random_nonce(bant); 80 | vb_tag_set_status(bant, VbTagStatusReady); 81 | vb_tag_set_operation(bant, VbTagOperationReady); 82 | } 83 | 84 | // Override tag type 85 | if(inst->override_type != inst->orig_type && inst->override_type != VbTagTypeUnknown) { 86 | vb_tag_set_item_id_no(bant, vb_tag_get_default_product(inst->override_type)); 87 | } 88 | 89 | if(inst->clear_account_id) { 90 | vb_tag_set_app_flag(bant, false); 91 | } 92 | nfc_device_set_data(inst->nfc_dev, NfcProtocolMfUltralight, inst->data_work); 93 | } 94 | 95 | static bool vb_migrate_scene_to_app_is_state_changed(VbMigrate* inst, ToAppState state) { 96 | const MfUltralightData* data = nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 97 | const BantBlock* bant = vb_tag_get_bant_block_const(data); 98 | VbTagOperation operation = vb_tag_get_operation(bant); 99 | 100 | if(state == ToAppStateEmulateReady) { 101 | return operation == VbTagOperationTransferToApp; 102 | } 103 | 104 | return false; 105 | } 106 | 107 | static void vb_migrate_scene_to_app_set_state(VbMigrate* inst, ToAppState state) { 108 | uint32_t curr_state = scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneToApp); 109 | if(state != curr_state) { 110 | Widget* widget = inst->widget; 111 | 112 | if(state == ToAppStateInstructions) { 113 | widget_reset(widget); 114 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton | VbMigrateBgTypeRightButton); 115 | widget_add_text_scroll_element( 116 | widget, 117 | 8, 118 | 16, 119 | 113, 120 | 33, 121 | "\e#To transfer\n" 122 | "\e#characters from\n" 123 | "\e#Flipper:\n" 124 | "0. If on VB Arena, select\n" 125 | "the device type that\n" 126 | "matches your current\n" 127 | "settings and the correct\n" 128 | "franchise for the\n" 129 | "characters you're\n" 130 | "transferring\n" 131 | "1. If on VB Lab, put the\n" 132 | "current character into\n" 133 | "storage\n" 134 | "2. Sync character from\n" 135 | "Flipper as if it was a Vital\n" 136 | "Bracelet. Flipper will beep\n" 137 | "and automatically\n" 138 | "advance to the next\n" 139 | "captured character when\n" 140 | "VB Lab/Arena has\n" 141 | "transferred the current\n" 142 | "character\n" 143 | "\n" 144 | "-> You can press the right\n" 145 | "key to skip sending the\n" 146 | "current character\n" 147 | "3. Repeat the above until\n" 148 | "you have transferred all\n" 149 | "the characters you want\n" 150 | "\n" 151 | "You can cancel at any\n" 152 | "time to finish transferring."); 153 | widget_add_button_element( 154 | widget, GuiButtonTypeLeft, "Cancel", vb_migrate_scene_to_app_widget_callback, inst); 155 | widget_add_button_element( 156 | widget, GuiButtonTypeRight, "Next", vb_migrate_scene_to_app_widget_callback, inst); 157 | 158 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 159 | } else if(state == ToAppStateEmulateReady) { 160 | view_dispatcher_send_custom_event(inst->view_dispatcher, ToAppEventTypeEmulateStart); 161 | } else if(state == ToAppStateLoadError) { 162 | widget_reset(widget); 163 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 164 | widget_add_icon_element(widget, 11, 18, &I_WrongDevice_32x27); 165 | widget_add_icon_element(widget, 48, 18, &I_TextErrLoadCapture_65x13); 166 | widget_add_icon_element(widget, 104, 41, &I_PulsemonLeftWait_15x15); 167 | FuriString* temp_str = furi_string_alloc_printf("%03d", inst->next_id); 168 | widget_add_string_multiline_element( 169 | widget, 48, 32, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); 170 | widget_add_button_element( 171 | widget, GuiButtonTypeLeft, "Cancel", vb_migrate_scene_to_app_widget_callback, inst); 172 | 173 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 174 | furi_string_free(temp_str); 175 | notification_message(inst->notifications, &sequence_error); 176 | notification_message(inst->notifications, &sequence_set_red_255); 177 | } else if(state == ToAppStateComplete) { 178 | widget_reset(widget); 179 | vb_migrate_add_bg(widget, VbMigrateBgTypeRightButton); 180 | widget_add_icon_element(widget, 11, 18, &I_CommComplete_32x20); 181 | widget_add_icon_element(widget, 48, 18, &I_TextTransfersDone_51x13); 182 | widget_add_icon_element(widget, 9, 40, &I_PulsemonRightHappy_14x16); 183 | widget_add_button_element( 184 | widget, GuiButtonTypeRight, "OK", vb_migrate_scene_to_app_widget_callback, inst); 185 | 186 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 187 | } else { 188 | furi_crash("Unknown new state in vb_migrate_scene_to_app_set_state"); 189 | } 190 | 191 | scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneToApp, state); 192 | } 193 | } 194 | 195 | static void vb_migrate_scene_to_app_load_capture(VbMigrate* inst, bool go_next) { 196 | if(go_next) { 197 | nfc_listener_stop(inst->listener); 198 | nfc_listener_free(inst->listener); 199 | inst->listener = NULL; 200 | vb_migrate_blink_stop(inst); 201 | ++inst->next_id; 202 | ++inst->num_sent; 203 | } 204 | 205 | if(inst->num_sent == inst->num_captured) { 206 | vb_migrate_scene_to_app_set_state(inst, ToAppStateComplete); 207 | } else { 208 | uint32_t state = scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneToApp); 209 | inst->next_id = vb_migrate_get_next_id(inst, inst->text_store, inst->next_id, true); 210 | FuriString* temp_str = 211 | furi_string_alloc_printf(VB_MIGRATE_CAPTURE_FORMAT, inst->next_id, NFC_APP_EXTENSION); 212 | 213 | vb_migrate_show_loading_popup(inst, true); 214 | if(vb_migrate_load_nfc(inst, inst->text_store, furi_string_get_cstr(temp_str))) { 215 | Widget* widget = inst->widget; 216 | 217 | widget_reset(widget); 218 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton | VbMigrateBgTypeRightButton); 219 | widget_add_icon_element(widget, 11, 18, &I_AppInteract_32x27); 220 | widget_add_icon_element(widget, 48, 18, &I_TextTapApp_56x27); 221 | furi_string_printf(temp_str, "%d/%d", inst->num_sent + 1, inst->num_captured); 222 | widget_add_string_element( 223 | widget, 224 | 120, 225 | 48, 226 | AlignRight, 227 | AlignBottom, 228 | FontSecondary, 229 | furi_string_get_cstr(temp_str)); 230 | widget_add_button_element( 231 | widget, GuiButtonTypeLeft, "Cancel", vb_migrate_scene_to_app_widget_callback, inst); 232 | widget_add_button_element( 233 | widget, GuiButtonTypeRight, "Skip", vb_migrate_scene_to_app_widget_callback, inst); 234 | 235 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 236 | 237 | vb_migrate_scene_to_app_set_nfc_state(inst, state); 238 | 239 | const NfcDeviceData* data = 240 | nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 241 | inst->listener = nfc_listener_alloc(inst->nfc, NfcProtocolMfUltralight, data); 242 | nfc_listener_start(inst->listener, vb_migrate_scene_to_app_worker_callback, inst); 243 | vb_migrate_blink_emulate(inst); 244 | } else { 245 | view_dispatcher_send_custom_event( 246 | inst->view_dispatcher, ToAppEventTypeCaptureLoadError); 247 | } 248 | vb_migrate_show_loading_popup(inst, false); 249 | furi_string_free(temp_str); 250 | } 251 | } 252 | 253 | void vb_migrate_scene_to_app_on_enter(void* context) { 254 | VbMigrate* inst = context; 255 | 256 | // Perform your setup here 257 | inst->next_id = 0; 258 | inst->num_sent = 0; 259 | scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneToApp, ToAppStateInitial); 260 | vb_migrate_scene_to_app_set_state(inst, ToAppStateInstructions); 261 | } 262 | 263 | bool vb_migrate_scene_to_app_on_event(void* context, SceneManagerEvent event) { 264 | VbMigrate* inst = context; 265 | uint32_t state = scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneToApp); 266 | bool consumed = false; 267 | 268 | if(event.type == SceneManagerEventTypeCustom) { 269 | if(event.event == ToAppEventTypeWidgetLeft) { 270 | consumed = scene_manager_previous_scene(inst->scene_manager); 271 | } else if(event.event == ToAppEventTypeWidgetRight) { 272 | if(state == ToAppStateInstructions) { 273 | vb_migrate_scene_to_app_set_state(inst, ToAppStateEmulateReady); 274 | consumed = true; 275 | } else if(state == ToAppStateEmulateReady) { 276 | vb_migrate_scene_to_app_load_capture(inst, true); 277 | consumed = true; 278 | } else if(state == ToAppStateComplete) { 279 | consumed = scene_manager_previous_scene(inst->scene_manager); 280 | } 281 | } else if(event.event == ToAppEventTypeEmulateStart) { 282 | vb_migrate_scene_to_app_load_capture(inst, false); 283 | } else if(event.event == ToAppEventTypeTagWrite) { 284 | if(vb_migrate_scene_to_app_is_state_changed(inst, state)) { 285 | if(state == ToAppStateEmulateReady) { 286 | notification_message(inst->notifications, &sequence_success); 287 | vb_migrate_scene_to_app_load_capture(inst, true); 288 | consumed = true; 289 | } 290 | } 291 | } else if(event.event == ToAppEventTypeCaptureLoadError) { 292 | vb_migrate_scene_to_app_set_state(inst, ToAppStateLoadError); 293 | consumed = true; 294 | } else { 295 | furi_crash("Unknown event in vb_migrate_scene_to_app_on_event"); 296 | } 297 | } 298 | return consumed; 299 | } 300 | 301 | void vb_migrate_scene_to_app_on_exit(void* context) { 302 | VbMigrate* inst = context; 303 | 304 | // Perform your cleanup here 305 | widget_reset(inst->widget); 306 | if(inst->listener) { 307 | nfc_listener_stop(inst->listener); 308 | nfc_listener_free(inst->listener); 309 | inst->listener = NULL; 310 | } 311 | vb_migrate_blink_stop(inst); 312 | notification_message_block(inst->notifications, &sequence_reset_red); 313 | } 314 | -------------------------------------------------------------------------------- /gui/modules/variable_item_list_ex.c: -------------------------------------------------------------------------------- 1 | #include "variable_item_list_ex.h" 2 | #include "gui/canvas.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct VariableItemEx { 9 | const char* label; 10 | uint8_t current_value_index; 11 | FuriString* current_value_text; 12 | uint8_t values_count; 13 | VariableItemExChangeCallback change_callback; 14 | void* context; 15 | int32_t callback_index; 16 | }; 17 | 18 | ARRAY_DEF(VariableItemExArray, VariableItemEx, M_POD_OPLIST); 19 | 20 | struct VariableItemListEx { 21 | View* view; 22 | VariableItemListExEnterCallback callback; 23 | void* context; 24 | }; 25 | 26 | typedef struct { 27 | VariableItemExArray_t items; 28 | uint8_t position; 29 | uint8_t window_position; 30 | } VariableItemListExModel; 31 | 32 | static void variable_item_list_ex_process_up(VariableItemListEx* variable_item_list); 33 | static void variable_item_list_ex_process_down(VariableItemListEx* variable_item_list); 34 | static void variable_item_list_ex_process_left(VariableItemListEx* variable_item_list); 35 | static void variable_item_list_ex_process_right(VariableItemListEx* variable_item_list); 36 | static void variable_item_list_ex_process_ok(VariableItemListEx* variable_item_list); 37 | 38 | static void variable_item_list_ex_draw_callback(Canvas* canvas, void* _model) { 39 | VariableItemListExModel* model = _model; 40 | 41 | const uint8_t item_height = 16; 42 | const uint8_t item_width = 123; 43 | 44 | canvas_clear(canvas); 45 | 46 | uint8_t position = 0; 47 | VariableItemExArray_it_t it; 48 | 49 | canvas_set_font(canvas, FontSecondary); 50 | for(VariableItemExArray_it(it, model->items); !VariableItemExArray_end_p(it); 51 | VariableItemExArray_next(it)) { 52 | uint8_t item_position = position - model->window_position; 53 | uint8_t items_on_screen = 4; 54 | uint8_t y_offset = 0; 55 | 56 | if(item_position < items_on_screen) { 57 | const VariableItemEx* item = VariableItemExArray_cref(it); 58 | uint8_t item_y = y_offset + (item_position * item_height); 59 | uint8_t item_text_y = item_y + item_height - 4; 60 | 61 | if(position == model->position) { 62 | canvas_set_color(canvas, ColorBlack); 63 | elements_slightly_rounded_box(canvas, 0, item_y + 1, item_width, item_height - 2); 64 | canvas_set_color(canvas, ColorWhite); 65 | } else { 66 | canvas_set_color(canvas, ColorBlack); 67 | } 68 | 69 | canvas_draw_str(canvas, 6, item_text_y, item->label); 70 | 71 | if(item->current_value_index > 0) { 72 | canvas_draw_str(canvas, 73, item_text_y, "<"); 73 | } 74 | 75 | canvas_draw_str_aligned( 76 | canvas, 77 | (115 + 73) / 2 + 1, 78 | item_text_y, 79 | AlignCenter, 80 | AlignBottom, 81 | furi_string_get_cstr(item->current_value_text)); 82 | 83 | if(item->current_value_index < (item->values_count - 1)) { 84 | canvas_draw_str(canvas, 115, item_text_y, ">"); 85 | } 86 | } 87 | 88 | position++; 89 | } 90 | 91 | elements_scrollbar(canvas, model->position, VariableItemExArray_size(model->items)); 92 | } 93 | 94 | void variable_item_list_ex_set_selected_item(VariableItemListEx* variable_item_list, uint8_t index) { 95 | with_view_model( 96 | variable_item_list->view, 97 | VariableItemListExModel * model, 98 | { 99 | uint8_t position = index; 100 | if(position >= VariableItemExArray_size(model->items)) { 101 | position = 0; 102 | } 103 | 104 | model->position = position; 105 | model->window_position = position; 106 | 107 | if(model->window_position > 0) { 108 | model->window_position -= 1; 109 | } 110 | 111 | if(VariableItemExArray_size(model->items) <= 4) { 112 | model->window_position = 0; 113 | } else { 114 | if(model->window_position >= (VariableItemExArray_size(model->items) - 4)) { 115 | model->window_position = (VariableItemExArray_size(model->items) - 4); 116 | } 117 | } 118 | }, 119 | true); 120 | } 121 | 122 | uint8_t variable_item_list_ex_get_selected_item_index(VariableItemListEx* variable_item_list) { 123 | VariableItemListExModel* model = view_get_model(variable_item_list->view); 124 | uint8_t idx = model->position; 125 | view_commit_model(variable_item_list->view, false); 126 | return idx; 127 | } 128 | 129 | static bool variable_item_list_ex_input_callback(InputEvent* event, void* context) { 130 | VariableItemListEx* variable_item_list = context; 131 | furi_assert(variable_item_list); 132 | bool consumed = false; 133 | 134 | if(event->type == InputTypeShort) { 135 | switch(event->key) { 136 | case InputKeyUp: 137 | consumed = true; 138 | variable_item_list_ex_process_up(variable_item_list); 139 | break; 140 | case InputKeyDown: 141 | consumed = true; 142 | variable_item_list_ex_process_down(variable_item_list); 143 | break; 144 | case InputKeyLeft: 145 | consumed = true; 146 | variable_item_list_ex_process_left(variable_item_list); 147 | break; 148 | case InputKeyRight: 149 | consumed = true; 150 | variable_item_list_ex_process_right(variable_item_list); 151 | break; 152 | case InputKeyOk: 153 | variable_item_list_ex_process_ok(variable_item_list); 154 | break; 155 | default: 156 | break; 157 | } 158 | } else if(event->type == InputTypeRepeat) { 159 | switch(event->key) { 160 | case InputKeyUp: 161 | consumed = true; 162 | variable_item_list_ex_process_up(variable_item_list); 163 | break; 164 | case InputKeyDown: 165 | consumed = true; 166 | variable_item_list_ex_process_down(variable_item_list); 167 | break; 168 | case InputKeyLeft: 169 | consumed = true; 170 | variable_item_list_ex_process_left(variable_item_list); 171 | break; 172 | case InputKeyRight: 173 | consumed = true; 174 | variable_item_list_ex_process_right(variable_item_list); 175 | break; 176 | default: 177 | break; 178 | } 179 | } 180 | 181 | return consumed; 182 | } 183 | 184 | void variable_item_list_ex_process_up(VariableItemListEx* variable_item_list) { 185 | with_view_model( 186 | variable_item_list->view, 187 | VariableItemListExModel * model, 188 | { 189 | uint8_t items_on_screen = 4; 190 | if(model->position > 0) { 191 | model->position--; 192 | if(((model->position - model->window_position) < 1) && 193 | model->window_position > 0) { 194 | model->window_position--; 195 | } 196 | } else { 197 | model->position = VariableItemExArray_size(model->items) - 1; 198 | if(model->position > (items_on_screen - 1)) { 199 | model->window_position = model->position - (items_on_screen - 1); 200 | } 201 | } 202 | }, 203 | true); 204 | } 205 | 206 | void variable_item_list_ex_process_down(VariableItemListEx* variable_item_list) { 207 | with_view_model( 208 | variable_item_list->view, 209 | VariableItemListExModel * model, 210 | { 211 | uint8_t items_on_screen = 4; 212 | if(model->position < (VariableItemExArray_size(model->items) - 1)) { 213 | model->position++; 214 | if((model->position - model->window_position) > (items_on_screen - 2) && 215 | model->window_position < 216 | (VariableItemExArray_size(model->items) - items_on_screen)) { 217 | model->window_position++; 218 | } 219 | } else { 220 | model->position = 0; 221 | model->window_position = 0; 222 | } 223 | }, 224 | true); 225 | } 226 | 227 | VariableItemEx* variable_item_list_ex_get_selected_item(VariableItemListExModel* model) { 228 | VariableItemEx* item = NULL; 229 | 230 | VariableItemExArray_it_t it; 231 | uint8_t position = 0; 232 | for(VariableItemExArray_it(it, model->items); !VariableItemExArray_end_p(it); 233 | VariableItemExArray_next(it)) { 234 | if(position == model->position) { 235 | break; 236 | } 237 | position++; 238 | } 239 | 240 | item = VariableItemExArray_ref(it); 241 | 242 | furi_assert(item); 243 | return item; 244 | } 245 | 246 | void variable_item_list_ex_process_left(VariableItemListEx* variable_item_list) { 247 | with_view_model( 248 | variable_item_list->view, 249 | VariableItemListExModel * model, 250 | { 251 | VariableItemEx* item = variable_item_list_ex_get_selected_item(model); 252 | if(item->current_value_index > 0) { 253 | item->current_value_index--; 254 | if(item->change_callback) { 255 | item->change_callback(item); 256 | } 257 | } 258 | }, 259 | true); 260 | } 261 | 262 | void variable_item_list_ex_process_right(VariableItemListEx* variable_item_list) { 263 | with_view_model( 264 | variable_item_list->view, 265 | VariableItemListExModel * model, 266 | { 267 | VariableItemEx* item = variable_item_list_ex_get_selected_item(model); 268 | if(item->current_value_index < (item->values_count - 1)) { 269 | item->current_value_index++; 270 | if(item->change_callback) { 271 | item->change_callback(item); 272 | } 273 | } 274 | }, 275 | true); 276 | } 277 | 278 | void variable_item_list_ex_process_ok(VariableItemListEx* variable_item_list) { 279 | with_view_model( 280 | variable_item_list->view, 281 | VariableItemListExModel * model, 282 | { 283 | if(variable_item_list->callback) { 284 | const VariableItemEx* variable_item = 285 | VariableItemExArray_cget(model->items, model->position); 286 | variable_item_list->callback( 287 | variable_item_list->context, variable_item->callback_index); 288 | } 289 | }, 290 | false); 291 | } 292 | 293 | VariableItemListEx* variable_item_list_ex_alloc() { 294 | VariableItemListEx* variable_item_list = malloc(sizeof(VariableItemListEx)); 295 | variable_item_list->view = view_alloc(); 296 | view_set_context(variable_item_list->view, variable_item_list); 297 | view_allocate_model( 298 | variable_item_list->view, ViewModelTypeLocking, sizeof(VariableItemListExModel)); 299 | view_set_draw_callback(variable_item_list->view, variable_item_list_ex_draw_callback); 300 | view_set_input_callback(variable_item_list->view, variable_item_list_ex_input_callback); 301 | 302 | with_view_model( 303 | variable_item_list->view, 304 | VariableItemListExModel * model, 305 | { 306 | VariableItemExArray_init(model->items); 307 | model->position = 0; 308 | model->window_position = 0; 309 | }, 310 | true); 311 | 312 | return variable_item_list; 313 | } 314 | 315 | void variable_item_list_ex_free(VariableItemListEx* variable_item_list) { 316 | furi_assert(variable_item_list); 317 | 318 | with_view_model( 319 | variable_item_list->view, 320 | VariableItemListExModel * model, 321 | { 322 | VariableItemExArray_it_t it; 323 | for(VariableItemExArray_it(it, model->items); !VariableItemExArray_end_p(it); 324 | VariableItemExArray_next(it)) { 325 | furi_string_free(VariableItemExArray_ref(it)->current_value_text); 326 | } 327 | VariableItemExArray_clear(model->items); 328 | }, 329 | false); 330 | view_free(variable_item_list->view); 331 | free(variable_item_list); 332 | } 333 | 334 | void variable_item_list_ex_reset(VariableItemListEx* variable_item_list) { 335 | furi_assert(variable_item_list); 336 | 337 | with_view_model( 338 | variable_item_list->view, 339 | VariableItemListExModel * model, 340 | { 341 | VariableItemExArray_it_t it; 342 | for(VariableItemExArray_it(it, model->items); !VariableItemExArray_end_p(it); 343 | VariableItemExArray_next(it)) { 344 | furi_string_free(VariableItemExArray_ref(it)->current_value_text); 345 | } 346 | VariableItemExArray_reset(model->items); 347 | }, 348 | false); 349 | } 350 | 351 | View* variable_item_list_ex_get_view(VariableItemListEx* variable_item_list) { 352 | furi_assert(variable_item_list); 353 | return variable_item_list->view; 354 | } 355 | 356 | VariableItemEx* variable_item_list_ex_add( 357 | VariableItemListEx* variable_item_list, 358 | const char* label, 359 | uint8_t values_count, 360 | VariableItemExChangeCallback change_callback, 361 | void* context, 362 | int32_t callback_index) { 363 | VariableItemEx* item = NULL; 364 | furi_assert(label); 365 | furi_assert(variable_item_list); 366 | 367 | with_view_model( 368 | variable_item_list->view, 369 | VariableItemListExModel * model, 370 | { 371 | item = VariableItemExArray_push_new(model->items); 372 | item->label = label; 373 | item->values_count = values_count; 374 | item->change_callback = change_callback; 375 | item->context = context; 376 | item->current_value_index = 0; 377 | item->current_value_text = furi_string_alloc(); 378 | item->callback_index = callback_index; 379 | }, 380 | true); 381 | 382 | return item; 383 | } 384 | 385 | void variable_item_list_ex_set_enter_callback( 386 | VariableItemListEx* variable_item_list, 387 | VariableItemListExEnterCallback callback, 388 | void* context) { 389 | furi_assert(callback); 390 | with_view_model( 391 | variable_item_list->view, 392 | VariableItemListExModel * model, 393 | { 394 | UNUSED(model); 395 | variable_item_list->callback = callback; 396 | variable_item_list->context = context; 397 | }, 398 | false); 399 | } 400 | 401 | void variable_item_ex_set_current_value_index(VariableItemEx* item, uint8_t current_value_index) { 402 | item->current_value_index = current_value_index; 403 | } 404 | 405 | void variable_item_ex_set_values_count(VariableItemEx* item, uint8_t values_count) { 406 | item->values_count = values_count; 407 | } 408 | 409 | void variable_item_ex_set_current_value_text(VariableItemEx* item, const char* current_value_text) { 410 | furi_string_set(item->current_value_text, current_value_text); 411 | } 412 | 413 | uint8_t variable_item_ex_get_current_value_index(VariableItemEx* item) { 414 | return item->current_value_index; 415 | } 416 | 417 | void* variable_item_ex_get_context(VariableItemEx* item) { 418 | return item->context; 419 | } 420 | -------------------------------------------------------------------------------- /gui/modules/file_select.c: -------------------------------------------------------------------------------- 1 | #include "file_select.h" 2 | #include 3 | #include 4 | 5 | #define FILENAME_COUNT 4 6 | 7 | struct FileSelect { 8 | // public 9 | View* view; 10 | Storage* fs_api; 11 | const char* path; 12 | const char* extension; 13 | 14 | bool init_completed; 15 | 16 | FileSelectCallback callback; 17 | void* context; 18 | 19 | char* buffer; 20 | uint8_t buffer_size; 21 | }; 22 | 23 | typedef struct { 24 | FuriString* filename[FILENAME_COUNT]; 25 | uint8_t position; 26 | 27 | uint16_t first_file_index; 28 | uint16_t file_count; 29 | 30 | } FileSelectModel; 31 | 32 | bool file_select_fill_strings(FileSelect* file_select); 33 | bool file_select_fill_count(FileSelect* file_select); 34 | static bool file_select_init_inner(FileSelect* file_select); 35 | 36 | static void file_select_draw_callback(Canvas* canvas, void* _model) { 37 | FileSelectModel* model = _model; 38 | 39 | FuriString* string_buff; 40 | const uint8_t item_height = 16; 41 | const uint8_t item_width = 123; 42 | const uint8_t text_max_width = 115; 43 | 44 | canvas_clear(canvas); 45 | canvas_set_font(canvas, FontSecondary); 46 | 47 | if(model->file_count) { 48 | for(uint8_t i = 0; i < MIN(FILENAME_COUNT, model->file_count); i++) { 49 | if(i == model->position) { 50 | canvas_set_color(canvas, ColorBlack); 51 | canvas_draw_box(canvas, 0, (i * item_height) + 1, item_width, item_height - 2); 52 | 53 | canvas_set_color(canvas, ColorWhite); 54 | canvas_draw_dot(canvas, 0, (i * item_height) + 1); 55 | canvas_draw_dot(canvas, 0, (i * item_height) + item_height - 2); 56 | canvas_draw_dot(canvas, item_width - 1, (i * item_height) + 1); 57 | canvas_draw_dot(canvas, item_width - 1, (i * item_height) + item_height - 2); 58 | } else { 59 | canvas_set_color(canvas, ColorBlack); 60 | } 61 | 62 | string_buff = furi_string_alloc_set(model->filename[i]); 63 | elements_string_fit_width(canvas, string_buff, text_max_width); 64 | canvas_draw_str( 65 | canvas, 6, (i * item_height) + item_height - 4, furi_string_get_cstr(string_buff)); 66 | 67 | furi_string_free(string_buff); 68 | } 69 | } else { 70 | canvas_draw_str(canvas, 6, item_height, "Empty folder"); 71 | } 72 | elements_scrollbar(canvas, model->first_file_index + model->position, model->file_count); 73 | } 74 | 75 | static bool file_select_input_callback(InputEvent* event, void* context) { 76 | FileSelect* file_select = (FileSelect*)context; 77 | bool consumed = false; 78 | 79 | if((event->type == InputTypeShort) | (event->type == InputTypeRepeat)) { 80 | if(!file_select->init_completed) { 81 | if(!file_select_init_inner(file_select)) { 82 | file_select->callback(false, file_select->context); 83 | } 84 | } else if(event->key == InputKeyUp) { 85 | with_view_model( 86 | file_select->view, 87 | FileSelectModel * model, 88 | { 89 | if(model->position == 0) { 90 | if(model->first_file_index == 0) { 91 | // wrap 92 | int16_t max_first_file_index = model->file_count - FILENAME_COUNT; 93 | model->position = MIN(FILENAME_COUNT - 1, model->file_count - 1); 94 | model->first_file_index = 95 | max_first_file_index < 0 ? 0 : max_first_file_index; 96 | } else { 97 | model->first_file_index--; 98 | } 99 | } else if(model->position == 1) { 100 | if(model->first_file_index == 0) { 101 | model->position--; 102 | } else { 103 | model->first_file_index--; 104 | } 105 | } else { 106 | model->position--; 107 | } 108 | }, 109 | true); 110 | consumed = true; 111 | if(!file_select_fill_strings(file_select)) { 112 | file_select->callback(false, file_select->context); 113 | } 114 | } else if(event->key == InputKeyDown) { 115 | with_view_model( 116 | file_select->view, 117 | FileSelectModel * model, 118 | { 119 | uint16_t max_first_file_index = model->file_count > FILENAME_COUNT ? 120 | model->file_count - FILENAME_COUNT : 121 | 0; 122 | 123 | if(model->position >= MIN(FILENAME_COUNT - 1, model->file_count - 1)) { 124 | if(model->first_file_index >= max_first_file_index) { 125 | // wrap 126 | model->position = 0; 127 | model->first_file_index = 0; 128 | } else { 129 | model->first_file_index++; 130 | } 131 | } else if(model->position >= (FILENAME_COUNT - 2)) { 132 | if(model->first_file_index >= max_first_file_index) { 133 | model->position++; 134 | } else { 135 | model->first_file_index++; 136 | } 137 | } else { 138 | model->position++; 139 | } 140 | }, 141 | true); 142 | consumed = true; 143 | if(!file_select_fill_strings(file_select)) { 144 | file_select->callback(false, file_select->context); 145 | } 146 | } else if(event->key == InputKeyOk) { 147 | if(file_select->callback != NULL) { 148 | size_t files = 0; 149 | if(file_select->buffer) { 150 | with_view_model( 151 | file_select->view, 152 | FileSelectModel * model, 153 | { 154 | files = model->file_count; 155 | strlcpy( 156 | file_select->buffer, 157 | furi_string_get_cstr(model->filename[model->position]), 158 | file_select->buffer_size); 159 | }, 160 | false); 161 | }; 162 | if(files > 0) { 163 | file_select->callback(true, file_select->context); 164 | } 165 | } 166 | consumed = true; 167 | } 168 | } 169 | 170 | return consumed; 171 | } 172 | 173 | static bool file_select_init_inner(FileSelect* file_select) { 174 | bool result = false; 175 | if(file_select->path && file_select->extension && file_select->fs_api) { 176 | if(file_select_fill_count(file_select)) { 177 | if(file_select_fill_strings(file_select)) { 178 | file_select->init_completed = true; 179 | result = true; 180 | } 181 | } 182 | } 183 | 184 | return result; 185 | } 186 | 187 | FileSelect* file_select_alloc() { 188 | FileSelect* file_select = malloc(sizeof(FileSelect)); 189 | file_select->view = view_alloc(); 190 | file_select->fs_api = furi_record_open("storage"); 191 | 192 | view_set_context(file_select->view, file_select); 193 | view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel)); 194 | view_set_draw_callback(file_select->view, file_select_draw_callback); 195 | view_set_input_callback(file_select->view, file_select_input_callback); 196 | 197 | with_view_model( 198 | file_select->view, 199 | FileSelectModel * model, 200 | { 201 | for(uint8_t i = 0; i < FILENAME_COUNT; i++) { 202 | model->filename[i] = furi_string_alloc(); 203 | } 204 | 205 | model->first_file_index = 0; 206 | model->file_count = 0; 207 | }, 208 | false); 209 | 210 | return file_select; 211 | } 212 | 213 | void file_select_free(FileSelect* file_select) { 214 | furi_assert(file_select); 215 | with_view_model( 216 | file_select->view, 217 | FileSelectModel * model, 218 | { 219 | for(uint8_t i = 0; i < FILENAME_COUNT; i++) { 220 | furi_string_free(model->filename[i]); 221 | } 222 | }, 223 | false); 224 | view_free(file_select->view); 225 | free(file_select); 226 | furi_record_close("storage"); 227 | } 228 | 229 | View* file_select_get_view(FileSelect* file_select) { 230 | furi_assert(file_select); 231 | return file_select->view; 232 | } 233 | 234 | void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) { 235 | file_select->context = context; 236 | file_select->callback = callback; 237 | } 238 | 239 | void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension) { 240 | furi_assert(file_select); 241 | file_select->path = path; 242 | file_select->extension = extension; 243 | } 244 | 245 | void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size) { 246 | file_select->buffer = buffer; 247 | file_select->buffer_size = buffer_size; 248 | 249 | if(file_select->buffer) { 250 | strlcpy(file_select->buffer, "", file_select->buffer_size); 251 | } 252 | } 253 | 254 | bool file_select_init(FileSelect* file_select) { 255 | if(!file_select_init_inner(file_select)) { 256 | file_select->callback(false, file_select->context); 257 | return false; 258 | } else { 259 | return true; 260 | } 261 | } 262 | 263 | static bool filter_file(FileSelect* file_select, FileInfo* file_info, char* name) { 264 | bool result = false; 265 | 266 | if((file_info->flags & FSF_DIRECTORY)) { 267 | if(strcmp(file_select->extension, "*") == 0) { 268 | result = true; 269 | } else if(strstr(name, file_select->extension) != NULL) { 270 | result = true; 271 | } 272 | } 273 | 274 | return result; 275 | } 276 | 277 | bool file_select_fill_strings(FileSelect* file_select) { 278 | furi_assert(file_select); 279 | furi_assert(file_select->fs_api); 280 | furi_assert(file_select->path); 281 | furi_assert(file_select->extension); 282 | 283 | FileInfo file_info; 284 | File* directory = storage_file_alloc(file_select->fs_api); 285 | 286 | uint8_t string_counter = 0; 287 | uint16_t file_counter = 0; 288 | const uint8_t name_length = 100; 289 | char* name = malloc(name_length); 290 | uint16_t first_file_index = 0; 291 | 292 | with_view_model( 293 | file_select->view, 294 | FileSelectModel * model, 295 | { first_file_index = model->first_file_index; }, 296 | false); 297 | 298 | if(!storage_dir_open(directory, file_select->path)) { 299 | storage_dir_close(directory); 300 | storage_file_free(directory); 301 | free(name); 302 | return true; 303 | } 304 | 305 | while(1) { 306 | if(!storage_dir_read(directory, &file_info, name, name_length)) { 307 | break; 308 | } 309 | 310 | if(storage_file_get_error(directory) == FSE_OK) { 311 | if(filter_file(file_select, &file_info, name)) { 312 | if(file_counter >= first_file_index) { 313 | with_view_model( 314 | file_select->view, 315 | FileSelectModel * model, 316 | { 317 | furi_string_set(model->filename[string_counter], name); 318 | 319 | if(strcmp(file_select->extension, "*") != 0) { 320 | furi_string_replace_all_str( 321 | model->filename[string_counter], file_select->extension, ""); 322 | } 323 | }, 324 | true); 325 | string_counter++; 326 | 327 | if(string_counter >= FILENAME_COUNT) { 328 | break; 329 | } 330 | } 331 | file_counter++; 332 | } 333 | } else { 334 | storage_dir_close(directory); 335 | storage_file_free(directory); 336 | free(name); 337 | return false; 338 | } 339 | } 340 | 341 | storage_dir_close(directory); 342 | storage_file_free(directory); 343 | free(name); 344 | return true; 345 | } 346 | 347 | bool file_select_fill_count(FileSelect* file_select) { 348 | furi_assert(file_select); 349 | furi_assert(file_select->fs_api); 350 | furi_assert(file_select->path); 351 | furi_assert(file_select->extension); 352 | 353 | FileInfo file_info; 354 | File* directory = storage_file_alloc(file_select->fs_api); 355 | 356 | uint16_t file_counter = 0; 357 | const uint8_t name_length = 100; 358 | char* name = malloc(name_length); 359 | 360 | if(!storage_dir_open(directory, file_select->path)) { 361 | storage_dir_close(directory); 362 | storage_file_free(directory); 363 | free(name); 364 | return true; 365 | } 366 | 367 | while(1) { 368 | if(!storage_dir_read(directory, &file_info, name, name_length)) { 369 | break; 370 | } 371 | 372 | if(storage_file_get_error(directory) == FSE_OK) { 373 | if(filter_file(file_select, &file_info, name)) { 374 | file_counter++; 375 | } 376 | } else { 377 | storage_dir_close(directory); 378 | storage_file_free(directory); 379 | free(name); 380 | return false; 381 | } 382 | } 383 | 384 | with_view_model( 385 | file_select->view, FileSelectModel * model, { model->file_count = file_counter; }, false); 386 | 387 | storage_dir_close(directory); 388 | storage_file_free(directory); 389 | free(name); 390 | return true; 391 | } 392 | 393 | void file_select_set_selected_file_internal(FileSelect* file_select, const char* filename) { 394 | furi_assert(file_select); 395 | furi_assert(filename); 396 | furi_assert(file_select->fs_api); 397 | furi_assert(file_select->path); 398 | furi_assert(file_select->extension); 399 | 400 | if(strlen(filename) == 0) return; 401 | 402 | FileInfo file_info; 403 | File* directory = storage_file_alloc(file_select->fs_api); 404 | 405 | const uint8_t name_length = 100; 406 | char* name = malloc(name_length); 407 | uint16_t file_position = 0; 408 | bool file_found = false; 409 | 410 | FuriString* filename_str = furi_string_alloc_set_str(filename); 411 | if(strcmp(file_select->extension, "*") != 0) { 412 | furi_string_cat_str(filename_str, file_select->extension); 413 | } 414 | 415 | if(!storage_dir_open(directory, file_select->path)) { 416 | furi_string_free(filename_str); 417 | storage_dir_close(directory); 418 | storage_file_free(directory); 419 | free(name); 420 | return; 421 | } 422 | 423 | while(1) { 424 | if(!storage_dir_read(directory, &file_info, name, name_length)) { 425 | break; 426 | } 427 | 428 | if(storage_file_get_error(directory) == FSE_OK) { 429 | if(filter_file(file_select, &file_info, name)) { 430 | if(strcmp(furi_string_get_cstr(filename_str), name) == 0) { 431 | file_found = true; 432 | break; 433 | } 434 | 435 | file_position++; 436 | } 437 | } else { 438 | furi_string_free(filename_str); 439 | storage_dir_close(directory); 440 | storage_file_free(directory); 441 | free(name); 442 | return; 443 | } 444 | } 445 | 446 | if(file_found) { 447 | with_view_model( 448 | file_select->view, 449 | FileSelectModel * model, 450 | { 451 | uint16_t max_first_file_index = 452 | model->file_count > FILENAME_COUNT ? model->file_count - FILENAME_COUNT : 0; 453 | 454 | model->first_file_index = file_position; 455 | 456 | if(model->first_file_index > 0) { 457 | model->first_file_index -= 1; 458 | } 459 | 460 | if(model->first_file_index >= max_first_file_index) { 461 | model->first_file_index = max_first_file_index; 462 | } 463 | 464 | model->position = file_position - model->first_file_index; 465 | }, 466 | true); 467 | } 468 | 469 | furi_string_free(filename_str); 470 | storage_dir_close(directory); 471 | storage_file_free(directory); 472 | free(name); 473 | } 474 | 475 | void file_select_set_selected_file(FileSelect* file_select, const char* filename) { 476 | file_select_set_selected_file_internal(file_select, filename); 477 | 478 | if(!file_select_fill_strings(file_select)) { 479 | file_select->callback(false, file_select->context); 480 | } 481 | } 482 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_from_app.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022-2024 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include 20 | 21 | #include "../vb_migrate_i.h" 22 | #include "../vb_tag.h" 23 | 24 | #define TAG "vb_migrate_scene_from_app" 25 | 26 | typedef enum { 27 | FromAppStateInitial, 28 | FromAppStateInstructions, 29 | FromAppStateEmulateReady, 30 | FromAppStateEmulateCheckDim, 31 | FromAppStateEmulateTransferFromApp, 32 | FromAppStateTemplateError, 33 | FromAppStateSaveError, 34 | } FromAppState; 35 | 36 | typedef enum { 37 | FromAppEventTypeWidgetLeft, 38 | FromAppEventTypeWidgetRight, 39 | FromAppEventTypeTemplateLoadError, 40 | FromAppEventTypeTagWrite, 41 | FromAppEventTypeCaptureSaveError, 42 | FromAppEventTypeCaptureSaveSuccess, 43 | } FromAppEventType; 44 | 45 | static void 46 | vb_migrate_scene_from_app_widget_callback(GuiButtonType result, InputType type, void* context) { 47 | VbMigrate* inst = context; 48 | 49 | if(type == InputTypeShort) { 50 | if(result == GuiButtonTypeLeft) 51 | view_dispatcher_send_custom_event(inst->view_dispatcher, FromAppEventTypeWidgetLeft); 52 | else if(result == GuiButtonTypeRight) 53 | view_dispatcher_send_custom_event(inst->view_dispatcher, FromAppEventTypeWidgetRight); 54 | } 55 | } 56 | 57 | static NfcCommand vb_migrate_scene_from_app_worker_callback(NfcGenericEvent event, void* context) { 58 | VbMigrate* inst = context; 59 | NfcCommand result = NfcCommandContinue; 60 | 61 | if(event.protocol == NfcProtocolInvalid) { 62 | NfcEvent* nfc_event = event.event_data; 63 | if(nfc_event->type == NfcEventTypeRxEnd) { 64 | const MfUltralightData* data = 65 | nfc_listener_get_data(inst->listener, NfcProtocolMfUltralight); 66 | const BantBlock* bant = vb_tag_get_bant_block_const(data); 67 | switch(vb_tag_get_operation(bant)) { 68 | case VbTagOperationCheckDim: 69 | case VbTagOperationReturnFromApp: 70 | nfc_device_set_data(inst->nfc_dev, NfcProtocolMfUltralight, data); 71 | view_dispatcher_send_custom_event(inst->view_dispatcher, FromAppEventTypeTagWrite); 72 | break; 73 | default: 74 | break; 75 | } 76 | } 77 | } 78 | 79 | return result; 80 | } 81 | 82 | static void vb_migrate_scene_from_app_set_nfc_state(VbMigrate* inst, FromAppState state) { 83 | nfc_device_copy_data(inst->nfc_dev, NfcProtocolMfUltralight, inst->data_work); 84 | BantBlock* bant = vb_tag_get_bant_block(inst->data_work); 85 | if(state == FromAppStateEmulateReady) { 86 | vb_tag_set_random_nonce(bant); 87 | vb_tag_set_status(bant, VbTagStatusReady); 88 | vb_tag_set_operation(bant, VbTagOperationReady); 89 | } else if(state == FromAppStateEmulateCheckDim) { 90 | vb_tag_set_status(bant, VbTagStatusReady | VbTagStatusDimReady); 91 | vb_tag_set_operation(bant, VbTagOperationReady); 92 | } else if(state == FromAppStateEmulateTransferFromApp) { 93 | vb_tag_set_status(bant, 0); 94 | vb_tag_set_operation(bant, VbTagOperationIdle); 95 | } 96 | 97 | // Override tag type 98 | if(inst->override_type != inst->orig_type && inst->override_type != VbTagTypeUnknown) { 99 | vb_tag_set_item_id_no(bant, vb_tag_get_default_product(inst->override_type)); 100 | } 101 | 102 | if(inst->clear_account_id) { 103 | vb_tag_set_app_flag(bant, false); 104 | } 105 | nfc_device_set_data(inst->nfc_dev, NfcProtocolMfUltralight, inst->data_work); 106 | } 107 | 108 | static bool vb_migrate_scene_from_app_is_state_changed(VbMigrate* inst, FromAppState state) { 109 | const MfUltralightData* data = nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 110 | const BantBlock* bant = vb_tag_get_bant_block_const(data); 111 | VbTagOperation operation = vb_tag_get_operation(bant); 112 | 113 | if(state == FromAppStateEmulateReady) { 114 | return operation == VbTagOperationCheckDim; 115 | } else if(state == FromAppStateEmulateCheckDim) { 116 | return operation == VbTagOperationCheckDim || operation == VbTagOperationReturnFromApp; 117 | } 118 | 119 | return false; 120 | } 121 | 122 | static void vb_migrate_scene_from_app_set_state(VbMigrate* inst, FromAppState state) { 123 | uint32_t curr_state = 124 | scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneFromApp); 125 | if(state != curr_state) { 126 | Widget* widget = inst->widget; 127 | 128 | if(state == FromAppStateInstructions) { 129 | widget_reset(widget); 130 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton | VbMigrateBgTypeRightButton); 131 | widget_add_text_scroll_element( 132 | widget, 133 | 8, 134 | 16, 135 | 113, 136 | 33, 137 | "\e#To transfer\n" 138 | "\e#characters to Flipper:\n" 139 | "0. If on VB Arena, select\n" 140 | "the device type that\n" 141 | "matches your current\n" 142 | "settings\n" 143 | "1. If on VB Lab, wake up\n" 144 | "character from storage\n" 145 | "2. Sync character to\n" 146 | "Flipper as if it was a\n" 147 | "Vital Bracelet. Flipper\n" 148 | "will beep when it is\n" 149 | "ready for the next step\n" 150 | "3. The character is\n" 151 | "automatically saved when\n" 152 | "the transfer is complete,\n" 153 | "and will be ready for\n" 154 | "another transfer\n" 155 | "4. Repeat the above until\n" 156 | "you have transferred all\n" 157 | "the characters you want\n" 158 | "\n" 159 | "You can cancel at any\n" 160 | "time to finish transferring."); 161 | widget_add_button_element( 162 | widget, 163 | GuiButtonTypeLeft, 164 | "Cancel", 165 | vb_migrate_scene_from_app_widget_callback, 166 | inst); 167 | widget_add_button_element( 168 | widget, 169 | GuiButtonTypeRight, 170 | "Next", 171 | vb_migrate_scene_from_app_widget_callback, 172 | inst); 173 | 174 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 175 | } else if(state == FromAppStateEmulateReady) { 176 | vb_migrate_show_loading_popup(inst, true); 177 | if(vb_migrate_load_nfc(inst, inst->text_store, VB_MIGRATE_TEMPLATE_NAME)) { 178 | widget_reset(widget); 179 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 180 | widget_add_icon_element(widget, 11, 18, &I_CommReady_32x27); 181 | widget_add_icon_element(widget, 48, 18, &I_TextTransferDimCheck_70x20); 182 | FuriString* temp_str = furi_string_alloc_printf("%d", inst->num_captured); 183 | widget_add_string_element( 184 | widget, 185 | 100, 186 | 31, 187 | AlignLeft, 188 | AlignTop, 189 | FontSecondary, 190 | furi_string_get_cstr(temp_str)); 191 | widget_add_icon_element(widget, 106, 40, &I_PulsemonRightWaiting_15x16); 192 | widget_add_button_element( 193 | widget, 194 | GuiButtonTypeLeft, 195 | "Cancel", 196 | vb_migrate_scene_from_app_widget_callback, 197 | inst); 198 | 199 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 200 | furi_string_free(temp_str); 201 | 202 | vb_migrate_scene_from_app_set_nfc_state(inst, state); 203 | const NfcDeviceData* data = 204 | nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 205 | inst->listener = nfc_listener_alloc(inst->nfc, NfcProtocolMfUltralight, data); 206 | nfc_listener_start( 207 | inst->listener, vb_migrate_scene_from_app_worker_callback, inst); 208 | vb_migrate_blink_emulate(inst); 209 | } else { 210 | view_dispatcher_send_custom_event( 211 | inst->view_dispatcher, FromAppEventTypeTemplateLoadError); 212 | } 213 | vb_migrate_show_loading_popup(inst, false); 214 | } else if(state == FromAppStateEmulateCheckDim) { 215 | widget_reset(widget); 216 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 217 | widget_add_icon_element(widget, 11, 18, &I_CommReady_32x27); 218 | widget_add_icon_element(widget, 48, 18, &I_TextTransferSecondDimCheck_71x20); 219 | widget_add_icon_element(widget, 106, 40, &I_PulsemonRightWaiting_15x16); 220 | widget_add_button_element( 221 | widget, 222 | GuiButtonTypeLeft, 223 | "Cancel", 224 | vb_migrate_scene_from_app_widget_callback, 225 | inst); 226 | 227 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 228 | notification_message(inst->notifications, &sequence_success); 229 | vb_migrate_scene_from_app_set_nfc_state(inst, state); 230 | furi_assert(!inst->listener); 231 | const NfcDeviceData* data = 232 | nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 233 | inst->listener = nfc_listener_alloc(inst->nfc, NfcProtocolMfUltralight, data); 234 | nfc_listener_start(inst->listener, vb_migrate_scene_from_app_worker_callback, inst); 235 | vb_migrate_blink_emulate(inst); 236 | } else if(state == FromAppStateEmulateTransferFromApp) { 237 | widget_reset(widget); 238 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 239 | widget_add_icon_element(widget, 15, 18, &I_TextTransferCaptured_97x13); 240 | widget_add_icon_element(widget, 106, 40, &I_PulsemonRightWaiting_15x16); 241 | widget_add_button_element( 242 | widget, 243 | GuiButtonTypeLeft, 244 | "Cancel", 245 | vb_migrate_scene_from_app_widget_callback, 246 | inst); 247 | 248 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 249 | nfc_listener_stop(inst->listener); 250 | nfc_listener_free(inst->listener); 251 | inst->listener = NULL; 252 | vb_migrate_blink_stop(inst); 253 | vb_migrate_scene_from_app_set_nfc_state(inst, state); 254 | notification_message(inst->notifications, &sequence_success); 255 | 256 | // Restore original tag type if necessary 257 | if(inst->override_type != inst->orig_type && inst->override_type != VbTagTypeUnknown) { 258 | nfc_device_copy_data(inst->nfc_dev, NfcProtocolMfUltralight, inst->data_work); 259 | BantBlock* bant = vb_tag_get_bant_block(inst->data_work); 260 | vb_tag_set_item_id_no(bant, inst->orig_product); 261 | nfc_device_set_data(inst->nfc_dev, NfcProtocolMfUltralight, inst->data_work); 262 | } 263 | 264 | // Save the tag 265 | inst->next_id = vb_migrate_get_next_id(inst, inst->text_store, inst->next_id, false); 266 | FuriString* save_path = furi_string_alloc_printf( 267 | VB_MIGRATE_CAPTURE_FORMAT, inst->next_id, NFC_APP_EXTENSION); 268 | if(vb_migrate_save_nfc(inst, inst->text_store, furi_string_get_cstr(save_path))) { 269 | view_dispatcher_send_custom_event( 270 | inst->view_dispatcher, FromAppEventTypeCaptureSaveSuccess); 271 | } else { 272 | view_dispatcher_send_custom_event( 273 | inst->view_dispatcher, FromAppEventTypeCaptureSaveError); 274 | } 275 | furi_string_free(save_path); 276 | } else if(state == FromAppStateTemplateError) { 277 | widget_reset(widget); 278 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 279 | widget_add_icon_element(widget, 11, 18, &I_WrongDevice_32x27); 280 | widget_add_icon_element(widget, 48, 18, &I_TextErrLoadTemplate_69x13); 281 | widget_add_icon_element(widget, 104, 41, &I_PulsemonLeftWait_15x15); 282 | widget_add_button_element( 283 | widget, 284 | GuiButtonTypeLeft, 285 | "Cancel", 286 | vb_migrate_scene_from_app_widget_callback, 287 | inst); 288 | 289 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 290 | notification_message(inst->notifications, &sequence_error); 291 | notification_message(inst->notifications, &sequence_set_red_255); 292 | } else if(state == FromAppStateSaveError) { 293 | widget_reset(widget); 294 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 295 | widget_add_icon_element(widget, 11, 18, &I_WrongDevice_32x27); 296 | widget_add_icon_element(widget, 48, 18, &I_TextErrSaveCapture_65x13); 297 | widget_add_icon_element(widget, 104, 41, &I_PulsemonLeftWait_15x15); 298 | widget_add_button_element( 299 | widget, 300 | GuiButtonTypeLeft, 301 | "Cancel", 302 | vb_migrate_scene_from_app_widget_callback, 303 | inst); 304 | 305 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 306 | notification_message(inst->notifications, &sequence_error); 307 | notification_message(inst->notifications, &sequence_set_red_255); 308 | } else { 309 | furi_crash("Unknown new state in vb_migrate_scene_from_app_set_state"); 310 | } 311 | 312 | scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneFromApp, state); 313 | } 314 | } 315 | 316 | void vb_migrate_scene_from_app_on_enter(void* context) { 317 | VbMigrate* inst = context; 318 | 319 | // Perform your setup here 320 | inst->next_id = 0; 321 | scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneFromApp, FromAppStateInitial); 322 | vb_migrate_scene_from_app_set_state(inst, FromAppStateInstructions); 323 | } 324 | 325 | bool vb_migrate_scene_from_app_on_event(void* context, SceneManagerEvent event) { 326 | VbMigrate* inst = context; 327 | bool consumed = false; 328 | 329 | if(event.type == SceneManagerEventTypeCustom) { 330 | if(event.event == FromAppEventTypeWidgetLeft) { 331 | consumed = scene_manager_previous_scene(inst->scene_manager); 332 | } else if(event.event == FromAppEventTypeWidgetRight) { 333 | vb_migrate_scene_from_app_set_state(inst, FromAppStateEmulateReady); 334 | consumed = true; 335 | } else if(event.event == FromAppEventTypeTagWrite) { 336 | uint32_t state = 337 | scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneFromApp); 338 | if(vb_migrate_scene_from_app_is_state_changed(inst, state)) { 339 | if(state == FromAppStateEmulateReady) { 340 | nfc_listener_stop(inst->listener); 341 | nfc_listener_free(inst->listener); 342 | inst->listener = NULL; 343 | vb_migrate_blink_stop(inst); 344 | vb_migrate_scene_from_app_set_state(inst, FromAppStateEmulateCheckDim); 345 | consumed = true; 346 | } else if(state == FromAppStateEmulateCheckDim) { 347 | const MfUltralightData* data = 348 | nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 349 | const BantBlock* bant = vb_tag_get_bant_block_const(data); 350 | VbTagOperation operation = vb_tag_get_operation(bant); 351 | 352 | if(operation == VbTagOperationReturnFromApp) { 353 | nfc_listener_stop(inst->listener); 354 | nfc_listener_free(inst->listener); 355 | inst->listener = NULL; 356 | vb_migrate_blink_stop(inst); 357 | vb_migrate_scene_from_app_set_state( 358 | inst, FromAppStateEmulateTransferFromApp); 359 | consumed = true; 360 | } else if(operation == VbTagOperationCheckDim) { 361 | // Don't need to reset tag, but should make a beep 362 | vb_migrate_blink_stop(inst); 363 | notification_message_block(inst->notifications, &sequence_success); 364 | vb_migrate_blink_emulate(inst); 365 | } 366 | } 367 | } 368 | } else if(event.event == FromAppEventTypeTemplateLoadError) { 369 | vb_migrate_scene_from_app_set_state(inst, FromAppStateTemplateError); 370 | consumed = true; 371 | } else if(event.event == FromAppEventTypeCaptureSaveError) { 372 | vb_migrate_scene_from_app_set_state(inst, FromAppStateSaveError); 373 | consumed = true; 374 | } else if(event.event == FromAppEventTypeCaptureSaveSuccess) { 375 | ++inst->num_captured; 376 | ++inst->next_id; 377 | vb_migrate_scene_from_app_set_state(inst, FromAppStateEmulateReady); 378 | consumed = true; 379 | } else { 380 | furi_crash("Unknown event in vb_migrate_scene_from_app_on_event"); 381 | } 382 | } 383 | return consumed; 384 | } 385 | 386 | void vb_migrate_scene_from_app_on_exit(void* context) { 387 | VbMigrate* inst = context; 388 | 389 | // Perform your cleanup here 390 | widget_reset(inst->widget); 391 | if(inst->listener) { 392 | nfc_listener_stop(inst->listener); 393 | nfc_listener_free(inst->listener); 394 | inst->listener = NULL; 395 | } 396 | vb_migrate_blink_stop(inst); 397 | notification_message_block(inst->notifications, &sequence_reset_red); 398 | } 399 | -------------------------------------------------------------------------------- /scenes/vb_migrate_scene_register.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // 3 | // VB Lab Migration Assistant for Flipper Zero 4 | // Copyright (C) 2022-2024 cyanic 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "../vb_tag.h" 24 | #include "../vb_migrate_i.h" 25 | 26 | typedef enum { 27 | RegisterStateInitial, 28 | RegisterStateInstructionInitial, 29 | RegisterStateInstructionConnect, 30 | RegisterStateCaptureInitial, 31 | RegisterStateCaptureInvalidTag, 32 | RegisterStateCapturePwd, 33 | RegisterStateCaptureFull, 34 | RegisterStateCaptureFailed, 35 | RegisterStateCaptureIncorrectTag, 36 | } RegisterState; 37 | 38 | typedef enum { 39 | RegisterEventTypeNextButton, 40 | RegisterEventTypePrevButton, 41 | RegisterEventTypeVbReadInitial, 42 | RegisterEventTypeVbPwdAuth, 43 | RegisterEventTypeVbReadFullSuccess, 44 | RegisterEventTypeVbReadFullFail, 45 | } RegisterEventType; 46 | 47 | static void 48 | vb_migrate_scene_register_widget_callback(GuiButtonType result, InputType type, void* context) { 49 | VbMigrate* inst = context; 50 | 51 | if(type == InputTypeShort) { 52 | if(result == GuiButtonTypeRight) 53 | view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterEventTypeNextButton); 54 | else if(result == GuiButtonTypeLeft) 55 | view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterEventTypePrevButton); 56 | } 57 | } 58 | 59 | static NfcCommand 60 | vb_migrate_scene_register_worker_read_initial_callback(NfcGenericEvent event, void* context) { 61 | VbMigrate* inst = context; 62 | const MfUltralightPollerEvent* mf_ultralight_event = event.event_data; 63 | NfcCommand result = NfcCommandContinue; 64 | 65 | if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) { 66 | nfc_device_set_data( 67 | inst->nfc_dev, NfcProtocolMfUltralight, nfc_poller_get_data(inst->poller)); 68 | view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterEventTypeVbReadInitial); 69 | result = NfcCommandStop; 70 | } 71 | 72 | return result; 73 | } 74 | 75 | static NfcCommand 76 | vb_migrate_scene_register_worker_auth_callback(NfcGenericEvent event, void* context) { 77 | VbMigrate* inst = context; 78 | const MfUltralightListenerEvent* mf_ultralight_event = event.event_data; 79 | NfcCommand result = NfcCommandContinue; 80 | 81 | if(mf_ultralight_event->type == MfUltralightListenerEventTypeAuth) { 82 | // Set up for auth 83 | memcpy( 84 | inst->captured_pwd, 85 | mf_ultralight_event->data->password.data, 86 | sizeof(inst->captured_pwd)); 87 | view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterEventTypeVbPwdAuth); 88 | result = NfcCommandStop; 89 | } 90 | 91 | return result; 92 | } 93 | 94 | static NfcCommand 95 | vb_migrate_scene_register_worker_full_capture_callback(NfcGenericEvent event, void* context) { 96 | VbMigrate* inst = context; 97 | const MfUltralightPollerEvent* mf_ultralight_event = event.event_data; 98 | NfcCommand result = NfcCommandContinue; 99 | 100 | if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) { 101 | mf_ultralight_event->data->auth_context.skip_auth = false; 102 | memcpy( 103 | mf_ultralight_event->data->auth_context.password.data, 104 | inst->captured_pwd, 105 | sizeof(inst->captured_pwd)); 106 | } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) { 107 | nfc_device_set_data( 108 | inst->nfc_dev, NfcProtocolMfUltralight, nfc_poller_get_data(inst->poller)); 109 | view_dispatcher_send_custom_event( 110 | inst->view_dispatcher, RegisterEventTypeVbReadFullSuccess); 111 | result = NfcCommandStop; 112 | } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthFailed) { 113 | view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterEventTypeVbReadFullFail); 114 | result = NfcCommandStop; 115 | } 116 | 117 | return result; 118 | } 119 | 120 | static void vb_migrate_scene_register_cleanup_state(VbMigrate* inst, RegisterState state) { 121 | if(state == RegisterStateCaptureInvalidTag || state == RegisterStateCaptureFailed || 122 | state == RegisterStateCaptureIncorrectTag) { 123 | notification_message(inst->notifications, &sequence_reset_red); 124 | } else if(state == RegisterStateCaptureInitial || state == RegisterStateCaptureFull) { 125 | vb_migrate_blink_stop(inst); 126 | nfc_poller_stop(inst->poller); 127 | nfc_poller_free(inst->poller); 128 | inst->poller = NULL; 129 | } else if(state == RegisterStateCapturePwd) { 130 | vb_migrate_blink_stop(inst); 131 | nfc_listener_stop(inst->listener); 132 | nfc_listener_free(inst->listener); 133 | inst->listener = NULL; 134 | } 135 | } 136 | 137 | static void vb_migrate_scene_register_set_state(VbMigrate* inst, RegisterState state) { 138 | uint32_t curr_state = 139 | scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneRegister); 140 | if(state != curr_state) { 141 | vb_migrate_scene_register_cleanup_state(inst, curr_state); 142 | Widget* widget = inst->widget; 143 | 144 | if(state == RegisterStateInstructionInitial) { 145 | widget_reset(widget); 146 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton | VbMigrateBgTypeRightButton); 147 | widget_add_text_scroll_element( 148 | widget, 149 | 8, 150 | 16, 151 | 113, 152 | 33, 153 | "Please make sure your\n" 154 | "current character has\n" 155 | "been sent to VB Lab/\n" 156 | "Arena before\n" 157 | "continuing."); 158 | widget_add_button_element( 159 | widget, 160 | GuiButtonTypeLeft, 161 | "Cancel", 162 | vb_migrate_scene_register_widget_callback, 163 | inst); 164 | widget_add_button_element( 165 | widget, 166 | GuiButtonTypeRight, 167 | "Next", 168 | vb_migrate_scene_register_widget_callback, 169 | inst); 170 | 171 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 172 | } else if(state == RegisterStateInstructionConnect) { 173 | widget_reset(widget); 174 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton | VbMigrateBgTypeRightButton); 175 | widget_add_text_scroll_element( 176 | widget, 177 | 8, 178 | 16, 179 | 113, 180 | 33, 181 | "Prepare VB Lab:\n" 182 | "1. Ensure current\n" 183 | "character in VB Lab has\n" 184 | "been put in storage\n" 185 | "2. Open the \"Scan\" screen\n" 186 | "3. Tap \"Vital Bracelet ->\n" 187 | "App\"\n" 188 | "\n" 189 | "Prepare VB Arena:\n" 190 | "1. Select the device type\n" 191 | "that matches your VB\n" 192 | "2. Tap the \"Send\" button"); 193 | widget_add_button_element( 194 | widget, 195 | GuiButtonTypeLeft, 196 | "Cancel", 197 | vb_migrate_scene_register_widget_callback, 198 | inst); 199 | widget_add_button_element( 200 | widget, 201 | GuiButtonTypeRight, 202 | "Next", 203 | vb_migrate_scene_register_widget_callback, 204 | inst); 205 | 206 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 207 | } else if(state == RegisterStateCaptureInitial) { 208 | widget_reset(widget); 209 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 210 | widget_add_icon_element(widget, 11, 25, &I_CommVB_32x20); 211 | widget_add_icon_element(widget, 21, 17, &I_CommWave_12x8); 212 | widget_add_icon_element(widget, 48, 18, &I_TextRegister1_71x20); 213 | widget_add_icon_element(widget, 105, 40, &I_PulsemonLeftStand_14x16); 214 | widget_add_button_element( 215 | widget, 216 | GuiButtonTypeLeft, 217 | "Previous", 218 | vb_migrate_scene_register_widget_callback, 219 | inst); 220 | 221 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 222 | inst->poller = nfc_poller_alloc(inst->nfc, NfcProtocolMfUltralight); 223 | nfc_poller_start( 224 | inst->poller, vb_migrate_scene_register_worker_read_initial_callback, inst); 225 | vb_migrate_blink_read(inst); 226 | } else if(state == RegisterStateCaptureInvalidTag) { 227 | widget_reset(widget); 228 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 229 | widget_add_icon_element(widget, 11, 18, &I_WrongDevice_32x27); 230 | widget_add_icon_element(widget, 48, 18, &I_TextRegisterErrNotVb_71x20); 231 | widget_add_icon_element(widget, 104, 41, &I_PulsemonLeftWait_15x15); 232 | widget_add_button_element( 233 | widget, 234 | GuiButtonTypeLeft, 235 | "Retry", 236 | vb_migrate_scene_register_widget_callback, 237 | inst); 238 | 239 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 240 | notification_message(inst->notifications, &sequence_set_red_255); 241 | } else if(state == RegisterStateCapturePwd) { 242 | widget_reset(widget); 243 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 244 | widget_add_icon_element(widget, 11, 18, &I_AppInteract_32x27); 245 | widget_add_icon_element(widget, 48, 18, &I_TextTapApp_56x27); 246 | widget_add_icon_element(widget, 105, 40, &I_PulsemonLeftStand_14x16); 247 | widget_add_button_element( 248 | widget, 249 | GuiButtonTypeLeft, 250 | "Previous", 251 | vb_migrate_scene_register_widget_callback, 252 | inst); 253 | 254 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 255 | 256 | nfc_device_copy_data(inst->nfc_dev, NfcProtocolMfUltralight, inst->data_work); 257 | BantBlock* bant = vb_tag_get_bant_block(inst->data_work); 258 | vb_tag_set_operation(bant, VbTagOperationReady); 259 | vb_tag_set_status(bant, VbTagStatusReady); 260 | inst->listener = 261 | nfc_listener_alloc(inst->nfc, NfcProtocolMfUltralight, inst->data_work); 262 | nfc_listener_start( 263 | inst->listener, vb_migrate_scene_register_worker_auth_callback, inst); 264 | vb_migrate_blink_emulate(inst); 265 | } else if(state == RegisterStateCaptureFull) { 266 | widget_reset(widget); 267 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 268 | widget_add_icon_element(widget, 11, 25, &I_CommVB_32x20); 269 | widget_add_icon_element(widget, 23, 18, &I_CommReturn_10x8); 270 | widget_add_icon_element(widget, 48, 18, &I_TextRegisterTapAgain_67x20); 271 | widget_add_icon_element(widget, 104, 41, &I_PulsemonLeftWait_15x15); 272 | widget_add_button_element( 273 | widget, 274 | GuiButtonTypeLeft, 275 | "Previous", 276 | vb_migrate_scene_register_widget_callback, 277 | inst); 278 | 279 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 280 | 281 | // TODO: run detect before starting poller (need a worker thread)? 282 | inst->poller = nfc_poller_alloc(inst->nfc, NfcProtocolMfUltralight); 283 | nfc_poller_start( 284 | inst->poller, vb_migrate_scene_register_worker_full_capture_callback, inst); 285 | vb_migrate_blink_read(inst); 286 | } else if(state == RegisterStateCaptureFailed) { 287 | widget_reset(widget); 288 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 289 | widget_add_icon_element(widget, 11, 18, &I_WrongDevice_32x27); 290 | widget_add_icon_element(widget, 48, 18, &I_TextErrReadFailed_71x20); 291 | widget_add_icon_element(widget, 104, 41, &I_PulsemonLeftWait_15x15); 292 | widget_add_button_element( 293 | widget, 294 | GuiButtonTypeLeft, 295 | "Retry", 296 | vb_migrate_scene_register_widget_callback, 297 | inst); 298 | 299 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 300 | notification_message(inst->notifications, &sequence_set_red_255); 301 | } else if(state == RegisterStateCaptureIncorrectTag) { 302 | widget_reset(widget); 303 | vb_migrate_add_bg(widget, VbMigrateBgTypeLeftButton); 304 | widget_add_icon_element(widget, 11, 18, &I_WrongDevice_32x27); 305 | widget_add_icon_element(widget, 48, 18, &I_TextErrRegisterDifferentTag_47x20); 306 | widget_add_icon_element(widget, 104, 41, &I_PulsemonLeftWait_15x15); 307 | widget_add_button_element( 308 | widget, 309 | GuiButtonTypeLeft, 310 | "Retry", 311 | vb_migrate_scene_register_widget_callback, 312 | inst); 313 | 314 | view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); 315 | notification_message(inst->notifications, &sequence_set_red_255); 316 | } else { 317 | furi_crash("Unknown new state in vb_migrate_scene_register_set_state"); 318 | } 319 | 320 | scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneRegister, state); 321 | } 322 | } 323 | 324 | static bool vb_migrate_scene_register_next_state(VbMigrate* inst, RegisterState state) { 325 | if(state == RegisterStateInstructionInitial) { 326 | vb_migrate_scene_register_set_state(inst, RegisterStateInstructionConnect); 327 | return true; 328 | } else if(state == RegisterStateInstructionConnect) { 329 | vb_migrate_scene_register_set_state(inst, RegisterStateCaptureInitial); 330 | return true; 331 | } 332 | 333 | return false; 334 | } 335 | 336 | static bool 337 | vb_migrate_scene_register_prev_state(VbMigrate* inst, RegisterState state, bool is_back) { 338 | UNUSED(is_back); 339 | 340 | if(state == RegisterStateInstructionInitial || state == RegisterStateInstructionConnect) { 341 | return scene_manager_previous_scene(inst->scene_manager); 342 | } else if(state == RegisterStateCaptureInitial) { 343 | vb_migrate_scene_register_set_state(inst, RegisterStateInstructionConnect); 344 | return true; 345 | } else if( 346 | state == RegisterStateCaptureInvalidTag || state == RegisterStateCapturePwd || 347 | state == RegisterStateCaptureFull) { 348 | vb_migrate_scene_register_set_state(inst, RegisterStateCaptureInitial); 349 | return true; 350 | } else if(state == RegisterStateCaptureFailed || state == RegisterStateCaptureIncorrectTag) { 351 | vb_migrate_scene_register_set_state(inst, RegisterStateCaptureFull); 352 | return true; 353 | } 354 | 355 | return is_back; 356 | } 357 | 358 | void vb_migrate_scene_register_on_enter(void* context) { 359 | VbMigrate* inst = context; 360 | 361 | scene_manager_set_scene_state( 362 | inst->scene_manager, VbMigrateSceneRegister, RegisterStateInitial); 363 | vb_migrate_scene_register_set_state(inst, RegisterStateInstructionInitial); 364 | } 365 | 366 | bool vb_migrate_scene_register_on_event(void* context, SceneManagerEvent event) { 367 | VbMigrate* inst = context; 368 | bool consumed = false; 369 | RegisterState state = 370 | scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneRegister); 371 | 372 | if(event.type == SceneManagerEventTypeCustom) { 373 | if(event.event == RegisterEventTypeNextButton) { 374 | consumed = vb_migrate_scene_register_next_state(inst, state); 375 | } else if(event.event == RegisterEventTypePrevButton) { 376 | consumed = vb_migrate_scene_register_prev_state(inst, state, false); 377 | } else if(event.event == RegisterEventTypeVbReadInitial) { 378 | const MfUltralightData* data = 379 | nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 380 | if(vb_tag_validate_product(data)) { 381 | memcpy( 382 | inst->captured_uid, data->iso14443_3a_data->uid, sizeof(inst->captured_uid)); 383 | notification_message(inst->notifications, &sequence_success); 384 | vb_migrate_scene_register_set_state(inst, RegisterStateCapturePwd); 385 | } else { 386 | notification_message(inst->notifications, &sequence_error); 387 | vb_migrate_scene_register_set_state(inst, RegisterStateCaptureInvalidTag); 388 | } 389 | consumed = true; 390 | } else if(event.event == RegisterEventTypeVbPwdAuth) { 391 | notification_message(inst->notifications, &sequence_success); 392 | vb_migrate_scene_register_set_state(inst, RegisterStateCaptureFull); 393 | consumed = true; 394 | } else if(event.event == RegisterEventTypeVbReadFullSuccess) { 395 | const MfUltralightData* data = 396 | nfc_device_get_data(inst->nfc_dev, NfcProtocolMfUltralight); 397 | if(memcmp( 398 | data->iso14443_3a_data->uid, inst->captured_uid, sizeof(inst->captured_uid)) || 399 | data->pages_read != data->pages_total) { 400 | notification_message(inst->notifications, &sequence_error); 401 | vb_migrate_scene_register_set_state(inst, RegisterStateCaptureIncorrectTag); 402 | } else { 403 | notification_message(inst->notifications, &sequence_success); 404 | scene_manager_next_scene(inst->scene_manager, VbMigrateSceneRegisterSave); 405 | } 406 | consumed = true; 407 | } else if(event.event == RegisterEventTypeVbReadFullFail) { 408 | notification_message(inst->notifications, &sequence_error); 409 | vb_migrate_scene_register_set_state(inst, RegisterStateCaptureFailed); 410 | consumed = true; 411 | } else { 412 | furi_crash("Unknown event in vb_migrate_scene_register_on_event"); 413 | } 414 | } else if(event.type == SceneManagerEventTypeBack) { 415 | consumed = vb_migrate_scene_register_prev_state(inst, state, true); 416 | } 417 | return consumed; 418 | } 419 | 420 | void vb_migrate_scene_register_on_exit(void* context) { 421 | VbMigrate* inst = context; 422 | RegisterState state = 423 | scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneRegister); 424 | 425 | vb_migrate_scene_register_cleanup_state(inst, state); 426 | widget_reset(inst->widget); 427 | } 428 | --------------------------------------------------------------------------------