diff --git a/ReadMe.md b/ReadMe.md index a249605ac..20ce378ae 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -27,6 +27,8 @@ Thank you to all the supporters! - [Add protocol Auriol hg0601a #451 (By LY2NEO)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/451) - Added: [SubGhz: fix duration pricenton protocol #2054 (By Skorpionm)](https://github.com/flipperdevices/flipperzero-firmware/pull/2054) - Added: [POCSAG protocol decoder for subghz #2055 (By Shuma)](https://github.com/flipperdevices/flipperzero-firmware/pull/2055) +- Added: [Dice (By Ka3u6y6a)](https://github.com/Ka3u6y6a/flipper-zero-dice) +- Added: [VB Lab Migration Assistant (By GMMan)](https://github.com/GMMan/flipperzero-vb-migrate) `Req: Vital Bracelet` ## Install from Release FLASH STOCK FIRST BEFORE UPDATING TO CUSTOM FIRMWARE! @@ -163,6 +165,7 @@ $ ./fbt resources icons dolphin_ext - [BlackJack (By teeebor)](https://github.com/teeebor/flipper_games) - [Chess (By Okalachev)](https://github.com/okalachev/flipperzero-firmware/tree/chess) Crashes 1st load if FW <~750KB or every load on larger FW `Broken?` - [Dice Roller Including SEX/WAR/8BALL/WEED DICE (By RogueMaster)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/applications/dice/dice.c) +- [Dice (By Ka3u6y6a)](https://github.com/Ka3u6y6a/flipper-zero-dice) - [Doom (By p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/) - [Flappy Bird (By DroomOne)](https://github.com/DroomOne/flipperzero-firmware/tree/dev/applications/flappy_bird) [Flappy: Border hitboxes, bigger Pilars (By TQMatvey)](https://github.com/DarkFlippers/unleashed-firmware/pull/114) [Increase pilars line width to improve visibility (By ahumeniy)](https://github.com/DarkFlippers/unleashed-firmware/pull/140) - [Game of Life (Updated to work by tgxn) (By itsyourbedtime)](https://github.com/tgxn/flipperzero-firmware/blob/dev/applications/game_of_life/game_of_life.c) @@ -239,6 +242,7 @@ $ ./fbt resources icons dolphin_ext - [UART Echo (By DrZlo13)-OFW](https://github.com/flipperdevices/flipperzero-firmware/pull/831) - [USB HID Autofire (By pbek)](https://github.com/pbek/usb_hid_autofire) - [USB Keyboard (By huuck)](https://github.com/huuck/FlipperZeroUSBKeyboard) +- [VB Lab Migration Assistant (By GMMan)](https://github.com/GMMan/flipperzero-vb-migrate) `Req: Vital Bracelet` - [WAV Player (By DrZlo13)](https://github.com/flipperdevices/flipperzero-firmware/tree/zlo/wav-player) Updated by Atmanos & RogueMaster To Work - [WiFi (Deauther) V2 (By Timmotools)](https://github.com/Timmotools/flipperzero_esp8266_deautherv2) `Req: ESP8266` - [WiFi (Marauder) (By 0xchocolate)](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion) `Req: ESP32 WITH MARAUDER FLASHED` diff --git a/applications/plugins/dice/application.fam b/applications/plugins/dice/application.fam index 83cd02032..4cf6506b7 100644 --- a/applications/plugins/dice/application.fam +++ b/applications/plugins/dice/application.fam @@ -1,6 +1,6 @@ App( appid="Dice", - name="Dice", + name="Dice [RM]", apptype=FlipperAppType.EXTERNAL, entry_point="dice_app", cdefines=["APP_DICE"], diff --git a/applications/plugins/dice2/README.md b/applications/plugins/dice2/README.md new file mode 100644 index 000000000..89955d4d9 --- /dev/null +++ b/applications/plugins/dice2/README.md @@ -0,0 +1,8 @@ +# Flipper Zero Dice App + +## Compiling + +1. Clone the [flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware) repository or another firmware that you use. +2. Create a symbolic link in `applications_user` named **dice**, pointing to this repository. +3. Compile by command `./fbt fap_dice_app` +4. Copy `build/f7-firmware-D/.extapps/dice_app.fap` to **apps/Tools** on the SD card or by [qFlipper](https://flipperzero.one/update) app. diff --git a/applications/plugins/dice2/application.fam b/applications/plugins/dice2/application.fam new file mode 100644 index 000000000..81be4349f --- /dev/null +++ b/applications/plugins/dice2/application.fam @@ -0,0 +1,13 @@ +App( + appid="Dice_app", + name="Dice [Ka3u6y6a]", + apptype=FlipperAppType.EXTERNAL, + entry_point="dice_tool_app", + cdefines=["APP_DICE"], + requires=["gui"], + stack_size=1 * 1024, + order=90, + fap_icon="icon.png", + fap_category="Games", + fap_icon_assets="assets", +) \ No newline at end of file diff --git a/applications/plugins/dice2/assets/ButtonBack_35x10.png b/applications/plugins/dice2/assets/ButtonBack_35x10.png new file mode 100644 index 000000000..2c22d19c6 Binary files /dev/null and b/applications/plugins/dice2/assets/ButtonBack_35x10.png differ diff --git a/applications/plugins/dice2/assets/ButtonDown_7x4.png b/applications/plugins/dice2/assets/ButtonDown_7x4.png new file mode 100644 index 000000000..2954bb6a6 Binary files /dev/null and b/applications/plugins/dice2/assets/ButtonDown_7x4.png differ diff --git a/applications/plugins/dice2/assets/ButtonExit_35x10.png b/applications/plugins/dice2/assets/ButtonExit_35x10.png new file mode 100644 index 000000000..22f357913 Binary files /dev/null and b/applications/plugins/dice2/assets/ButtonExit_35x10.png differ diff --git a/applications/plugins/dice2/assets/ButtonLeft_4x7.png b/applications/plugins/dice2/assets/ButtonLeft_4x7.png new file mode 100644 index 000000000..0b4655d43 Binary files /dev/null and b/applications/plugins/dice2/assets/ButtonLeft_4x7.png differ diff --git a/applications/plugins/dice2/assets/ButtonRight_4x7.png b/applications/plugins/dice2/assets/ButtonRight_4x7.png new file mode 100644 index 000000000..8e1c74c1c Binary files /dev/null and b/applications/plugins/dice2/assets/ButtonRight_4x7.png differ diff --git a/applications/plugins/dice2/assets/ButtonRoll_35x10.png b/applications/plugins/dice2/assets/ButtonRoll_35x10.png new file mode 100644 index 000000000..f20d7f565 Binary files /dev/null and b/applications/plugins/dice2/assets/ButtonRoll_35x10.png differ diff --git a/applications/plugins/dice2/assets/ButtonUp_7x4.png b/applications/plugins/dice2/assets/ButtonUp_7x4.png new file mode 100644 index 000000000..1be79328b Binary files /dev/null and b/applications/plugins/dice2/assets/ButtonUp_7x4.png differ diff --git a/applications/plugins/dice2/assets/DiceCount_30x13.png b/applications/plugins/dice2/assets/DiceCount_30x13.png new file mode 100644 index 000000000..a408de025 Binary files /dev/null and b/applications/plugins/dice2/assets/DiceCount_30x13.png differ diff --git a/applications/plugins/dice2/assets/DiceCount_30x13_One.png b/applications/plugins/dice2/assets/DiceCount_30x13_One.png new file mode 100644 index 000000000..ec61bde96 Binary files /dev/null and b/applications/plugins/dice2/assets/DiceCount_30x13_One.png differ diff --git a/applications/plugins/dice2/assets/coin_1.png b/applications/plugins/dice2/assets/coin_1.png new file mode 100644 index 000000000..6f56f9644 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_1.png differ diff --git a/applications/plugins/dice2/assets/coin_2.png b/applications/plugins/dice2/assets/coin_2.png new file mode 100644 index 000000000..08c5872d9 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_2.png differ diff --git a/applications/plugins/dice2/assets/coin_3.png b/applications/plugins/dice2/assets/coin_3.png new file mode 100644 index 000000000..c757caa66 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_3.png differ diff --git a/applications/plugins/dice2/assets/coin_4.png b/applications/plugins/dice2/assets/coin_4.png new file mode 100644 index 000000000..508184d14 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_4.png differ diff --git a/applications/plugins/dice2/assets/coin_5.png b/applications/plugins/dice2/assets/coin_5.png new file mode 100644 index 000000000..85831d239 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_5.png differ diff --git a/applications/plugins/dice2/assets/coin_6.png b/applications/plugins/dice2/assets/coin_6.png new file mode 100644 index 000000000..17cdbf105 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_6.png differ diff --git a/applications/plugins/dice2/assets/coin_7.png b/applications/plugins/dice2/assets/coin_7.png new file mode 100644 index 000000000..82f828a94 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_7.png differ diff --git a/applications/plugins/dice2/assets/d100_1.png b/applications/plugins/dice2/assets/d100_1.png new file mode 100644 index 000000000..a7f2c18b0 Binary files /dev/null and b/applications/plugins/dice2/assets/d100_1.png differ diff --git a/applications/plugins/dice2/assets/d100_2.png b/applications/plugins/dice2/assets/d100_2.png new file mode 100644 index 000000000..783486976 Binary files /dev/null and b/applications/plugins/dice2/assets/d100_2.png differ diff --git a/applications/plugins/dice2/assets/d100_3.png b/applications/plugins/dice2/assets/d100_3.png new file mode 100644 index 000000000..92d5a5c0c Binary files /dev/null and b/applications/plugins/dice2/assets/d100_3.png differ diff --git a/applications/plugins/dice2/assets/d100_4.png b/applications/plugins/dice2/assets/d100_4.png new file mode 100644 index 000000000..324b7f633 Binary files /dev/null and b/applications/plugins/dice2/assets/d100_4.png differ diff --git a/applications/plugins/dice2/assets/d4_1.png b/applications/plugins/dice2/assets/d4_1.png new file mode 100644 index 000000000..8007cdca5 Binary files /dev/null and b/applications/plugins/dice2/assets/d4_1.png differ diff --git a/applications/plugins/dice2/assets/d4_2.png b/applications/plugins/dice2/assets/d4_2.png new file mode 100644 index 000000000..8757c56e3 Binary files /dev/null and b/applications/plugins/dice2/assets/d4_2.png differ diff --git a/applications/plugins/dice2/assets/d4_3.png b/applications/plugins/dice2/assets/d4_3.png new file mode 100644 index 000000000..8d1687ac8 Binary files /dev/null and b/applications/plugins/dice2/assets/d4_3.png differ diff --git a/applications/plugins/dice2/constants.h b/applications/plugins/dice2/constants.h new file mode 100644 index 000000000..07b070dc6 --- /dev/null +++ b/applications/plugins/dice2/constants.h @@ -0,0 +1,116 @@ +#include +#include "dice_app_icons.h" + +#define TAG "DiceApp" + +#define DICE_TYPES 8 + +#define MAX_DICE_COUNT 10 +#define MAX_COIN_FRAMES 9 +#define MAX_DICE_FRAMES 4 + +#define DICE_X 45 +#define DICE_Y 6 +#define DICE_Y_T 0 + +#define DICE_GAP 44 + +#define SWIPE_DIST 11 + +const Icon* coin_heads_start[] = {&I_coin_1, &I_coin_2}; +const Icon* coin_heads_end[] = {&I_coin_7, &I_coin_1}; +const Icon* coin_tails_start[] = {&I_coin_5, &I_coin_6}; +const Icon* coin_tails_end[] = {&I_coin_4, &I_coin_5}; +const Icon* coin_frames[] = { + &I_coin_1, + &I_coin_2, + &I_coin_3, + &I_coin_4, + &I_coin_5, + &I_coin_6, + &I_coin_3, + &I_coin_7, + &I_coin_1, +}; + +const Icon* dice_frames[] = { + &I_d4_1, &I_d4_2, &I_d4_3, &I_d4_1, // d4 + &I_coin_1, &I_d4_2, &I_d4_3, &I_d4_1, // d6 + &I_d4_2, &I_d4_2, &I_d4_3, &I_d4_1, // d8 + &I_coin_1, &I_d4_2, &I_d4_3, &I_d4_1, // d10 + &I_d4_1, &I_d4_2, &I_d4_3, &I_d4_1, // d12 + &I_coin_7, &I_d4_2, &I_d4_3, &I_d4_1, // d20 + &I_d100_1, &I_d100_2, &I_d100_3, &I_d100_4, // d100 +}; + +typedef struct { + uint8_t type; + int x; + int y; + char* name; +} Dice; + +const uint8_t screen_pos[] = {}; + +static const Dice dice_types[] = { + {2, 0, 0, "Coin"}, + {4, 0, 0, "d4"}, + {6, 0, 0, "d6"}, + {8, 0, 0, "d8"}, + {10, 0, 0, "d10"}, + {12, 0, 0, "d12"}, + {20, 0, 0, "d20"}, + {100, 0, 0, "d100"}, +}; + +typedef enum { EventTypeTick, EventTypeKey } EventType; +typedef enum { SelectState, SwipeLeftState, SwipeRightState, AnimState, ResultState } AppState; + +typedef struct { + EventType type; + InputEvent input; +} AppEvent; + +typedef struct { + AppState app_state; + uint16_t roll_result; + uint8_t rolled_dices[MAX_DICE_COUNT]; + uint8_t anim_frame; + uint8_t dice_index; + uint8_t dice_count; + Dice dices[DICE_TYPES]; +} State; + +void init(State* const state) { + state->app_state = SelectState; + state->roll_result = 0; + state->dice_index = 0; + state->anim_frame = 0; + state->dice_count = 1; + + for(uint8_t i = 0; i < DICE_TYPES; i++) { + state->dices[i] = dice_types[i]; + state->dices[i].x = DICE_X + (i * DICE_GAP); + state->dices[i].y = i == 0 ? DICE_Y_T : DICE_Y; + } +} + +void coin_set_start(uint16_t type) { + if(type == 1) { + coin_frames[0] = coin_heads_start[0]; + coin_frames[1] = coin_heads_start[1]; + } else { + coin_frames[0] = coin_tails_start[0]; + coin_frames[1] = coin_tails_start[1]; + } +} + +void coin_set_end(uint16_t type) { + if(type == 1) { + coin_frames[MAX_COIN_FRAMES - 2] = coin_heads_end[0]; + coin_frames[MAX_COIN_FRAMES - 1] = coin_heads_end[1]; + } else { + coin_frames[MAX_COIN_FRAMES - 2] = coin_tails_end[0]; + coin_frames[MAX_COIN_FRAMES - 1] = coin_tails_end[1]; + } +} \ No newline at end of file diff --git a/applications/plugins/dice2/dice_app.c b/applications/plugins/dice2/dice_app.c new file mode 100644 index 000000000..ab2c72257 --- /dev/null +++ b/applications/plugins/dice2/dice_app.c @@ -0,0 +1,256 @@ +#include +#include +#include +#include "constants.h" + +const Icon* draw_dice_frame; + +static void update(State* const state) { + if(state->app_state == SwipeLeftState) { + for(uint8_t i = 0; i < DICE_TYPES; i++) { + state->dices[i].x -= SWIPE_DIST; + state->dices[i].y = DICE_Y; + } + + if(state->dices[state->dice_index].x == DICE_X) { + state->app_state = SelectState; + state->dices[state->dice_index].y = DICE_Y_T; + } + + } else if(state->app_state == SwipeRightState) { + for(uint8_t i = 0; i < DICE_TYPES; i++) { + state->dices[i].x += SWIPE_DIST; + state->dices[i].y = DICE_Y; + } + + if(state->dices[state->dice_index].x == DICE_X) { + state->app_state = SelectState; + state->dices[state->dice_index].y = DICE_Y_T; + } + } else if(state->app_state == AnimState) { + state->anim_frame += 1; + + if(state->dice_index == 0) { + if(state->anim_frame == 3) coin_set_start(state->roll_result); // change coin anim + + if(state->anim_frame >= MAX_COIN_FRAMES) { + state->anim_frame = 0; + state->app_state = ResultState; + } + } else { + if(state->anim_frame >= MAX_DICE_FRAMES) { + state->anim_frame = 0; + state->app_state = ResultState; + } + } + } +} + +static void roll(State* const state) { + state->roll_result = 0; + + for(uint8_t i = 0; i < MAX_DICE_COUNT; i++) { + if(i < state->dice_count) { + state->rolled_dices[i] = (rand() % dice_types[state->dice_index].type) + 1; + state->roll_result += state->rolled_dices[i]; + } else { + state->rolled_dices[i] = 0; + } + } + + if(state->dice_index == 0) coin_set_end(state->roll_result); // change coin anim + + state->app_state = AnimState; +} + +static void draw_ui(const State* state, Canvas* canvas) { + canvas_set_font(canvas, FontSecondary); + + FuriString* count = furi_string_alloc(); + furi_string_printf(count, "%01d", state->dice_count); + + // dice name and arrows + if(state->app_state != SwipeLeftState && state->app_state != SwipeRightState) { + canvas_draw_str_aligned( + canvas, 63, 50, AlignCenter, AlignBottom, dice_types[state->dice_index].name); + + if(state->dice_index > 0) canvas_draw_icon(canvas, 45, 44, &I_ButtonLeft_4x7); + if(state->dice_index < DICE_TYPES - 1) + canvas_draw_icon(canvas, 78, 44, &I_ButtonRight_4x7); + } + + // dice settings + if(state->dice_index == 0) + canvas_draw_icon(canvas, 48, 51, &I_DiceCount_30x13_One); + else + canvas_draw_icon(canvas, 48, 51, &I_DiceCount_30x13); + canvas_draw_str_aligned(canvas, 58, 61, AlignCenter, AlignBottom, furi_string_get_cstr(count)); + + // buttons + canvas_draw_icon(canvas, 92, 54, &I_ButtonRoll_35x10); + canvas_draw_icon(canvas, 0, 54, &I_ButtonExit_35x10); + + furi_string_free(count); +} + +static void draw_dice(const State* state, Canvas* canvas) { + for(uint8_t i = 0; i < DICE_TYPES; i++) { + if(state->dices[i].x > 128 || state->dices[i].x < -35) continue; + + if(i == state->dice_index) { // draw dice with animation + if(i == 0) { // coin + draw_dice_frame = coin_frames[state->anim_frame]; + } else { // dices + draw_dice_frame = dice_frames[(i - 1) * MAX_DICE_FRAMES + state->anim_frame]; + } + } else { // draw first dice frame + if(i == 0) { // coin + draw_dice_frame = coin_frames[0]; + } else { // dices + draw_dice_frame = dice_frames[(i - 1) * MAX_DICE_FRAMES]; + } + } + + canvas_draw_icon(canvas, state->dices[i].x, state->dices[i].y, draw_dice_frame); + } +} + +static void draw_results(const State* state, Canvas* canvas) { + if(state->app_state != ResultState) return; + + canvas_set_font(canvas, FontPrimary); + + FuriString* sum = furi_string_alloc(); + furi_string_printf(sum, "%01d", state->roll_result); + + canvas_draw_str_aligned(canvas, 38, 44, AlignCenter, AlignCenter, furi_string_get_cstr(sum)); + + furi_string_free(sum); +} + +static void draw_callback(Canvas* canvas, void* ctx) { + const State* state = acquire_mutex((ValueMutex*)ctx, 25); + if(state == NULL) { + return; + } + + canvas_clear(canvas); + + draw_ui(state, canvas); + draw_dice(state, canvas); + draw_results(state, canvas); + + release_mutex((ValueMutex*)ctx, state); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + AppEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + AppEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +int32_t dice_tool_app(void* p) { + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent)); + + FURI_LOG_E(TAG, ">>> Started...\r\n"); + State* state = malloc(sizeof(State)); + init(state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, state, sizeof(State))) { + FURI_LOG_E(TAG, "cannot create mutex\r\n"); + free(state); + return 255; + } + + // Set callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, draw_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() * 0.2); + + // Create GUI, register view port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + AppEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + State* state = (State*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + // timer evetn + if(event.type == EventTypeTick) { + update(state); + } + // button events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + // lock input while animations + if(state->app_state == SelectState || state->app_state == ResultState) { + // input + if(event.input.key == InputKeyUp) { + if(state->dice_index != 0) { + state->dice_count += 1; + if(state->dice_count > MAX_DICE_COUNT) { + state->dice_count = MAX_DICE_COUNT; + } + } + } else if(event.input.key == InputKeyDown) { + state->dice_count -= 1; + if(state->dice_count < 1) { + state->dice_count = 1; + } + } else if(event.input.key == InputKeyRight) { + if(state->dice_index < DICE_TYPES - 1) { + state->dice_index += 1; + state->app_state = SwipeLeftState; + } + } else if(event.input.key == InputKeyLeft) { + if(state->dice_index > 0) { + state->dice_index -= 1; + state->app_state = SwipeRightState; + if(state->dice_index == 0) state->dice_count = 1; + } + } else if(event.input.key == InputKeyOk) { + roll(state); + } + } + // quit from app + if(event.input.key == InputKeyBack) { + processing = false; + } + } + } + } else { + FURI_LOG_D(TAG, "osMessageQueue: event timeout"); + } + + view_port_update(view_port); + release_mutex(&state_mutex, state); + } + + // Clear + free(state); + furi_timer_free(timer); + furi_message_queue_free(event_queue); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + delete_mutex(&state_mutex); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/dice2/icon.png b/applications/plugins/dice2/icon.png new file mode 100644 index 000000000..840088565 Binary files /dev/null and b/applications/plugins/dice2/icon.png differ diff --git a/applications/plugins/dice2/images/coin.pixil b/applications/plugins/dice2/images/coin.pixil new file mode 100644 index 000000000..838aeafc0 --- /dev/null +++ b/applications/plugins/dice2/images/coin.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":"2.6.1","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":"35","height":"35","colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","FFFFFF","7F7F7F","a1a1a1","C3C3C3","c40424","880015","B97A57","dba88c","ED1C24","f75b63","f26f9b","FF7F27","f7ab79","FFC90E","FFF200","cfc532","EFE4B0","1ee656","0c6624","22B14C","B5E61D","5487ff","00A2E8","99D9EA","3F48CC","7f86e3","7092BE","720899","cd55cf","A349A4","C8BFE7","ffffff"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAFlJREFUWEft1MEJACAMBEGv/6IvFeRxhoDC+hYMm0HZ9nnkiGGaTVCmI0oZyqTfF2Ywg5m0AGbSYvwzYzOS1iuuP5C4YZixmST37V3WxJpSO5jBDGbSAl+YKXbCj5ghLqGvAAAAAElFTkSuQmCC","edit":false,"name":"Layer 1","opacity":"1","active":true,"unqid":"zdzpbp","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAf5JREFUWEftVkFywjAMVAZooP//apukScYdq7ariJXsAAcO4UICtrxerVbq6I0+3RthoQOMlY1nmAkNKd4Vf9fidLgEkffH36znjDn+/0lE369gJoPYHBpCCF3XUQiBuvhA8fH/WYGMOEwCWpiRIO4Y2AmGwVqAamDyRvTdIJmyRIJ4CAxipOhlnucqmMvlwiBOpxOt65pTpNO9QYyChmEY6Ha76QD8Po5juF6v+baterRYdsGgkpXptKrJY0qKXq8rsZFm+LBYEcuyUKKaf0pR9B5LA1D4XGqRXi48/phgSoBcJeIaFvA9RcCHW7HRLbNnuJ5glChKYWYV+pNkx6K8UJ9u8SgrmdicHdcYkTBdYxItAbKq1IlaRAGY9FKk8UowUuQ6xUjkstT/NC1uYtm+vIlZll5dO/qSXtUExhKlpruC564nucxI1VsjgU5FreIkQJ0q7ch37XyTqth/zuczNKgaDeB/dMGNkdZKlgNM0xT6vvd6Uc34aJ7nkBunMYjBQScC+CGivrGM9UDlkSbZ+UqTn9ubpC48n/D0oAGVdKzryiOFdF74oiIsRBR36Y7L744zIyCWhWzW1nKNOnWt1LUvofEBnlsDA11VsIJmHwYjhnKUdqirFjCWA7fs1Sx+pOJ4GowWbKvVtIDmWM0LW09+Zt0BxmLvYMZi5hdntuIk2wS1xwAAAABJRU5ErkJggg==","edit":false,"name":"Layer 2 Copy","opacity":"1","active":true,"unqid":"","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":2,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAmlJREFUWEelWOt6qDAIs+//0O5rV1hIA9Rz9mdOsYQQLm48//bzXr42Lu2W2RfjAOB9azxjHEe3vlqD53ncKwMQDleEjV3qswOzgODhCoA9r56tNPyxJf1WYAIQdETgjjPebaDeqQBlYCSQTieZWA2AYDD4V2CugIxMMBuRsWMAbwBJMFUE00kHxAAIWxWoY2AwboyRoPMEjKrzwbb7b6841s8BBlmBa7cjB6razNYAhnetqoj9ZSPBcH73AYrFdQ/1IVK4Hs/7XGX7vuNAByFFXfnuZhjAFOmcZzsYZAdTFcB8SNE6HIVKTc2jBZ0wcG+mG1AYIEZnaOlJtAGMpUpVWQXGunsLxmg1B5TvtD9R47OKYqbm+1y5mhkUXAYm6SHmxH+DnevGxDwZQREfmgGD0CfUvGHdUFN2jeyADp1dgdlUc9PCxtZNe88WMwNpvEsTCMtzyxpCbVQjgsBgb8O0/RYjIk1KW4nPq/rrtlj5uAGD7PCWkI2JbJvwZm16gf6UM2OpglFw9BZuesnYWOPCRAzV0zc9c0DIefh5Y8SZA++mS5uBEatsZIbYCA6RBSjVLB1c1nbWZIjP9eme7jOQSw84afu8y1Q6yvbqeoXgbU98fvDED4HdrJ3VCrEChx7DAuNPFtTR/wDx1iEHXqF4zzeWJDdALALQ2rokzTiQcAEHhi9IrCqhIwUuiJqrBgPdhk5INWPCfoOVpiIndgIg/piz/YVLsQSDFPNnUvdBp+zVVEdAN9PXGRIUZ33G72OTyxgJvaM5UWqIZ0s2ACuNfEkT26bCroDBIW0WWgPB2u1/rb6wv2x/ACDUeEL6Qq7OAAAAAElFTkSuQmCC","edit":false,"name":"Background","opacity":"1","active":false,"unqid":"u6oba5","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":3,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAfZJREFUWEftVl1vgzAMNCoM2P//qxtQiDw5JZkxdj5YH6qpeULCdu7OziUNvNBqXggL/AswWKFoMeHiQACQAEpyeQ7FfwDA3SJypSAgIjZNQ7mmQogIewztHeKS++XAaEUkW4uozM3mpcBsAHADiEN+KM7U4cw5sFCb8vg+pkoWGI1VKlb7x2vEb+cc3m7E0a9DnlWEhqzfEyQzzt7/I5V85cccycXz+fcXAHxyQEXJy7Jg3/cyVgNpzQWu6wpd14WhV1uobRDk8wXati1hnLMdbW74afQ4LLap1oSBtYiUDDGvEfeypvwKGHmqZO0U8cfIMRpy+muY17ZJKlMMJmdWORUD0OzclChjmZbqFQmJqsCoQ7U7be3GZ7M5epG3Jna/maeJLkIKtE4bB5ZroSZUNErmkcnNvKsKQ9VaGj1JsQnNiX28Vdu8U0idbduCa/Jjm3NtUw1f5Lddp9anLjjNvFJeYinB2629fyIG8yaepgnGceRtiEzmecZhGLida2rk3P1kCRYY2ZaQGJnRvZVb4WKkJ4NzThI77Z0Cox31E6gcoP0/v61Nk8yBsRSKQMPJ4L6hecj+DpZPiAOXEjAhQX39VYLRvCsCqgEjQclToj2YpCnSy+7bau0VMBqop9T/C5jC2S0Pe4N5Sk/LBb8W+W6TpdsPAVbkJHCu0MYAAAAASUVORK5CYII=","edit":false,"name":"Layer 2","opacity":"1","active":false,"unqid":"","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":4,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAKFJREFUWEftVVsKwCAMa+9/6I4Oiw/cap2Cg+zHQWuaJXEyHfTwQVwIZJ7cgDJQJnpSkRlkBpmJKjCSGVkFOonD5dEWkcyHuaq5+Lo5sqcclkb1yViRiJSdEq7WNJiMvPa/vVu9Xe0Lt5BJytzES8KfyCjjiOSuh50G16YZ0IV7YiFdOLgLhYty5Ke32wUXHzbBJjckTQMyg8wgM1EFfpGZCziRWCSDveGOAAAAAElFTkSuQmCC","edit":false,"name":"Layer 4","opacity":"1","active":false,"unqid":"","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":5,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAfhJREFUWEftl91uwjAMhR21HbC9/6sOOkCZ0tWVe+q/VprEBVyh4iZfju3jUOiFPuWFWOgNY2XjiDI1kdoj64Zp+iSibyKSALxRe2Z9Z95dUFEwQ6w2rbXWUqZHhb/XWqn8PUTIKS6hphvEi24U2AnDgCGUR4wwmVqRAkiVtINtxLJgZHpq13X0fD6nk93v9xBqGAYPylRIg9EUwTgPqNxut3o+n2V6GKBer1e6XC4qUGYTWbyySxDIiuN3tAOs9ldhWmdM6HPLQIdkGgMBpn1aivu+b+uqrS9hFnJoU5RUdpd2atd7vLU3MIZfyE2t1KLHtHdU8HmPjToIs5gYG5pIl6XIZg3Io/fe6jcsOu10Vo1EIwLfW9nF7Morh49gcBxoYNaM8mKlfSw1mYGJ5koW5uWUwUZQ0yQrXys4z1v2KINtrxYwwyy2DWPfAsTnZlxr58fjQfPcUie55hkr+uaawzBYXRZafPOacRzpdDrhGhtwDabd7L7mvIQnb3cbGB2eFfB+IxF94KXLmtrTlaHrOlXOLKigwrpKTW11wDmbowrs4JplcF22i1Gvyed5iOWw6qlABW0qhwYaGRp2GYn7r1UbljrRIcJbeyuyH+OvSuQ98vfMoUMYa9h5IFqKMvG7YVKLHg1KyXd08b3vvWHMNtwr5X/G/wLO/uskz2jgOQAAAABJRU5ErkJggg==","edit":false,"name":"Layer 2 Copy Copy","opacity":"1","active":false,"unqid":"","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":6,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAkBJREFUWEetl1t2wyAMRO39L9o9pkgZDSMJ2vQrTQRcRk/u629/z+aye9NumJ0YB4DnqXnue9m6Pas1uK7LT2UAceC4YWOXntnBDBDcXAHY79Vvww0fteS5FUwAwYMIbtnjmQZqTQWUwUiQBKKNJQMQCobzFUwLMiW3ta/98tnUsWzaAZIwzQ3w8J3MZViPwQnoDAzjquBN7o+jx4Xh/xLmNZ62A2j+7xnH8bPAoCrw+bXDDXGdKjit4qT+sJcwiX+34ysrppxlU2XnWG5oBl36QjEc8kNQZwq+seIqvhcGmN/l4HS3NUPKmrEZxg8GDMRSCHBa40B0hoahwPXMnJUYYQKLSm+xJkuQGmZu7rckV3AWyVqTXMBsGWx1E/jSM8g2RbcJl6kiyK4dW2GaYxAvMVPBFPXlWBmrOS3M9EFQpim1xzCWjVswnHbfDGCMR6jKZ6mdqGO1Q8bMEukwXnAVPqozGzAMtizBKrwNgxNe0xjb3oSZx80SJkDtJjNQjTILZCr1WR/zzi1G2QhDamCr93j48ghhGbtUYCtIPPyElmBFq0n1qlWo/esRgqc98fz499hZjRBBGdFV+cmCMKiEjxQu6YwGHty6Sc/bUDFAo8zpMwWyxGMPv2NVxm/C9+EFiQrxAbb25BFHlT0wZO+mRSHMNJZfVNnwFT/m+FXAGaKSY1EopMjhw79SZAcGvDCeJzyzbmU3ztSZIkcwGBsIlTxrQh3ZUeQExlkUlAV1BgbyVfE5zFqDKtu2/HRwxg9BIkRCZQ/enQAAAABJRU5ErkJggg==","edit":false,"name":"Background Copy","opacity":"1","active":false,"unqid":"u6oba5","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":7,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAfpJREFUWEftV9tugzAMdQQM2P7/VwcUUKakJHKMHZuqk3hon6piHx8f36iDG33cjbjAh4xUjavK+AtlvYqtlukLAB4AgElYglDSFh+VTBAiAUdA7713rortDptkZE6khsqBFMSEkknBVV+JDOcYfsP2XP9IqmCVdgBouUQ4MjHIvu/QNE14rmZkaGqciIhHyQTDXwD4OQJwahQ+Qg9hJbjvCwCE4SiwODKF87quvuu6pJCoJAI+JbAsC/R9TzGoHdsDXOCTI1IufKW9dErSew/btkHXdcn+VC4ORApMW0PaJRb/ZFPY0l3ASUkz5/qVNqhWTkwm41vIWLanhUxaoDjholQWMrdRxrBCoolVmYRXLVMGRHsjbtMoy/MWWbc118DSvmIbOB/EEN89o6sLT9jOml/MER3bnCR7awghrIrlPnEq5jVQwT5JH52O6iSFaE9Y++j0+hG2edu2rCrVDFBEOnFUqWRas8PEcDLV2yTtAu6GSQq5eZ79MAz44ucTME0TjOPIDoRlQnzTNPGVIkxUkFqr03F/sGLsKFOc2nal90MlQcDppq1d/uh6hYy4i8KYCivBpAhtOkl5euYjOF2MChkt6RxbO4Lfx5sfLlHx8nUg0XNgTbYQQSPDKWbpnVdwTf+btOF52/OXMnhbdGb8/gv7Mu5HGUmyP5KE6CQNV8vwAAAAAElFTkSuQmCC","edit":false,"name":"Layer 2 Copy","opacity":"1","active":false,"unqid":"","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":1,"unqid":"rs6rne","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAdFJREFUWEftV9uqg0AMXKko6v//quL9ECESY25r+1AO7UuL7mYnM5NkW+z7vqcv+RQ/MIoS/4OZoihcp+XaMZsZCgIPg2fab0QM7/u+T23bqkmEwSCICAALmMWWC4aCsBiIgqQxOEUmGNwofbuGIQsAKI8h7VfBSIxQv8zz7OIpy/IA8Xq90rquh6+43DSICAY2DMOQmqa5BICNEHAcx1TX9RlYQsW9obFsgpFKlgbWqsmiifqJr7vE5uOA0rgsSwKq4aPRqxlSMz6CkeJdZJLKl/aJm/tJf9GYofKgzDS5yzPKjJfNRV8BiCQhHhzpPTdmaBlaPrD6hceg2q+QmU+ywqWVgEvyncy8C0bzAZeJGpj3nTAYr+RzJX3EjGZKnqHXjrlUJhhKp2YwLgUty3fAnJVmlTbMH5wvOQdLwKQEb31N6sC8J0zTlKqqUmdR5EaH3dxkXQIDB8MwtCqBlm+059B1XdcdNz9zNklzyDss8h5l3rbtuFJIsqv3GfAK3kGsQfnEuJr/3Jse3+iVOi15bzDexob3j9I6XGqEfBTksOpeyHmm0mGaVDwRrEp1vceM1jM8r+SAPhN+AiYKJHddWKbcwE/W/8B81MBPJIjs+QPyMEG2lHD1VgAAAABJRU5ErkJggg==","width":"35","height":"35"}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMA/sfR5H8Fkddasdmnacvx//8745jkhasdASD945kjknhj/AAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAdFJREFUWEftV9uqg0AMXKko6v//quL9ECESY25r+1AO7UuL7mYnM5NkW+z7vqcv+RQ/MIoS/4OZoihcp+XaMZsZCgIPg2fab0QM7/u+T23bqkmEwSCICAALmMWWC4aCsBiIgqQxOEUmGNwofbuGIQsAKI8h7VfBSIxQv8zz7OIpy/IA8Xq90rquh6+43DSICAY2DMOQmqa5BICNEHAcx1TX9RlYQsW9obFsgpFKlgbWqsmiifqJr7vE5uOA0rgsSwKq4aPRqxlSMz6CkeJdZJLKl/aJm/tJf9GYofKgzDS5yzPKjJfNRV8BiCQhHhzpPTdmaBlaPrD6hceg2q+QmU+ywqWVgEvyncy8C0bzAZeJGpj3nTAYr+RzJX3EjGZKnqHXjrlUJhhKp2YwLgUty3fAnJVmlTbMH5wvOQdLwKQEb31N6sC8J0zTlKqqUmdR5EaH3dxkXQIDB8MwtCqBlm+059B1XdcdNz9zNklzyDss8h5l3rbtuFJIsqv3GfAK3kGsQfnEuJr/3Jse3+iVOi15bzDexob3j9I6XGqEfBTksOpeyHmm0mGaVDwRrEp1vceM1jM8r+SAPhN+AiYKJHddWKbcwE/W/8B81MBPJIjs+QPyMEG2lHD1VgAAAABJRU5ErkJggg==","palette_id":false} \ No newline at end of file diff --git a/applications/plugins/dice2/images/d100.pixil b/applications/plugins/dice2/images/d100.pixil new file mode 100644 index 000000000..c4fd81342 --- /dev/null +++ b/applications/plugins/dice2/images/d100.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":"2.6.1","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":"35","height":"35","colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","FFFFFF","7F7F7F","a1a1a1","C3C3C3","c40424","880015","B97A57","dba88c","ED1C24","f75b63","f26f9b","FF7F27","f7ab79","FFC90E","FFF200","cfc532","EFE4B0","1ee656","0c6624","22B14C","B5E61D","5487ff","00A2E8","99D9EA","3F48CC","7f86e3","7092BE","720899","cd55cf","A349A4","C8BFE7","ffffff"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAExJREFUWEft1LERACAMAzGy/9DOBCncUYgJuI/OkyTvkzc+c1xCmYuoMsq088UMM8y0BZhpi9kZZphpCzDTFrMzzDDTFmCmLWZnrmIL7qWLmCXUhAcAAAAASUVORK5CYII=","edit":false,"name":"Background","opacity":"1","active":true,"unqid":"1exvb","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAArBJREFUWEfNWMF2wjAMa+n/fzJ0zyEyiiKn5W2H9bIBia3IsmLYt3/07L/AclZ7z/Pc9n3/Ova3G0oAfwHsFpjzPE86aOwJUNXfCVcwFc8VW5dgCEhL3kuwPZ/P7TgOAAKAnYFTudq+K0BLMMJICwYwCA4Ur9crGTmOA+sGsFeASjDmhAMYrUUwFU8AYdDu/4ohC6YCIgmx14oaDAI0v64YmsCoRhBMT87MxGePxwOaSGY6A7bRHKABjDLCUUITKEG8zwA0GzqPEvKS1JECUmYa5UoxRWqBHBMCaEro6AnQbJAJRsoTJ2/AiA13omzpfgjnRxOOSj/MzCREMMDObvylectQh89NwK2dgu8xcgvYUTCDsVHJMqgEGkyuR9+DVTDqEqv+BjBVBzkwRPGqtRNQrOfkWiLopnVePw3fNYOA2dJF3G4v31dtuTM9B67lITAAl/V3rfktM647XbdOYLjNIMrFbZ3gsQ9akWSsq9Sk1Y0wY93SeAhKkA0AK8DaLuAGRBOrbyV7WiZ2YXVZYSk1geDuPlo5d3wW10iaHwvYjQycKB3u7SOtY4SJ9hL3WPzPV0gkD8D83nCQThGcc3BZN8aKqBsqLZEBmNdIBYRbmzWghopxseoynfZUd5VhDoNajKTTdVC1c0NOy7Vj4CmrDmA3Nub3iV4MVGXZKq+owMRB3NzMY4Sb9PTCTG9gdpTBYnZJQaNryPUh1/zWMA1XSOjatCqVXhkFO9b8esx3e+pGdl62/qu273py44QttU55Fkxy1wVr6J8GMoCGQJkp3o91DkgJRgERe0MbayItY99X7Zmq4gScuauS8TdLPm23CvfVt8WsGEHCJZgFQww475aCmVtAlmViYbMl6xWh5uU6Lk9+8TPJJTMVqDss3AVxu0yFZ7x/jlg8Vz9/uK0/fGymIjAHw4EAAAAASUVORK5CYII=","edit":false,"name":"Layer 1","opacity":"1","active":false,"unqid":"oo532f","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":2,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAArJJREFUWEe1mFt2wjAMRDHsf8khPVItdTwaOaGn5QuSWL4ePcN4/PJznue5WzrGGJ+a/nhBbGAw3X7G+e8wAsAOwwot1z4Bu6VMA2EiGciA+/E97AYoX5cevIQhEKWEGfbrU4XcCH9HiO3ct4VBEJA7N4XNCiTGd8TWFVALI6R/HMfxeL1eRWKliMsFCRXP7IAkDMdIGDIY+yAQGF9glWpXQAVGuMaCIU+pygu6QX2PmPKIn7aUQguMAkED4B9cl6lN7sIsi6XLNQZiZTxVsXaQay6zCWPljopYh+QJ4xg3QZaA5mAOZSmg81CoDsO4jGH9/X6X7AnA5/OZEFA7cvmmNQXIAmQ2tsrgkQ3CAOYa1SSz6mKGiVLb1qRSto/jOOemWVkDREidfXN+KUAZuT/nLtU64efDeNKl10SZ34Dg4c9ZGEuzjPWdah5rBHPZY6gxIoivBZhopLJiLwvH+K4/QpmlyEGs+OFU48S+1WWg6lWLpJ0yaJBkVYXMIS3Wok1wD0NYTHWuSUoZN2wPmnGEEf5O19jzEfgIw4eBBMkyYkkt3WQXub5wb1LpjQAM0/Qr9xL2KldmN8+CXzNDKEh9fRyAO7tqshPuXtHjngK1sQBhrBj4FYxSNWKqVOAu2BQQ5azHwA0XZx2MhlxgcLLjiZ8Dl9I9U55AVM3CwdyTpGuUS+xw9+Xf2CLMqAKJzfBeY9c9VIarkvuzp4CRbqYplTYusOvx+tyvwkRmIRAZjAO0L26mAicA1quwje7BIConiqEEg5ZH0jQAU4ga2EH9ZTxFRbYwSqE4iZ08qjOPFhwPVKPQrHwX5xl4UQkV2myUryiboM/MUopcKgOx4vLyGCnglmapwHYgJZu6dMC5WL0liiBPUzQ6bD2xvclw/AfR7v8ZXHv3v5qPYNpC8kc3vgArjLsiNA/itgAAAABJRU5ErkJggg==","edit":false,"name":"Layer 2","opacity":"1","active":false,"unqid":"oo532f","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":3,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAqNJREFUWEell9uSgzAMQ6H9/0+m3bEbZ2RFduiWl90Bkhxk+dLz+Of1fr/f3dLzPM9vt/5qAQNU5zHnXbBbMAjBAHZw3AsIfAfBdlBbmADpVLBnCgTDBM/LM1sYBAEFbI0/Cgj468+O4+C/zrUDKmEKReIQ3rxTplqznC1hWBE7GcI019h7qM51Xcfz+XSw4Y9pN1Zx7JnOX2Cq0MTBKl0xhCLjpjKF2SdDgilAlpAEEPkmhUNAn/BB03eokAoTFzNeKOG6bLLwPR6PCDWeGYng95b4h+uHR6TE9M4UoSvKtt/wVGnoREnptxzCxYxM3bYH2ywMzh8ThmeYFJKgicxAr0CGbb0yak+CQaAEw8aqwiCU84+5rkuqYmk+LgdGZWR1Hje9au7SF5RyYRACDnYF4sL7r9fLaxDeS5kJMH4AAkUWEAQaPymCdSRUNAAGtcyyy55FlvlagkH1qv9xzfQch2oA+McxEGycsxVhoIx/OuHaBrAJhkKzVhgQtIMpqCp2DKiUKRthiu1niOtUdT9FeFT45FyEBo4U5B6CqU2jhFtDjAwJlPdDVVJLGUbz7gubzrBiBw5TwrsBw/6ayth6dTjuNVsJ7DK/sFOGaoUqBylEXFsYDqDSBL8ULmpwzi36iwOhNEWbmKFTgGYF1ShTVeUqGl8iMs0z8E6/quYaHiH8C9n9XBewGBaDeqofZcEag3yIsgxX/LMjNuoq6eLeIXgx4yygEqbLrOg3qq8opbphi0I9BVGTXoo9hoyA2lbSFbrqJ4uEIYWShyo/sXKiPHg0KxAs6dJjaqTAiomLzFNx8c+VuN+BbGGUhwJGzbM8gmD13oHcguGQFX2Iu3lS+g7IbRhqGVXZqEIdvy6360oDb1d+uvXPALjBLzA3eL975Q81aKYi/zu6FwAAAABJRU5ErkJggg==","edit":false,"name":"Layer 3","opacity":"1","active":false,"unqid":"oo532f","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":4,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAp5JREFUWEe9l8uWgzAMQ0v7/59M6bEbe2RFDtDFdDPTB/hGVuSwPX58HcdxrC7dtm27e+vLF3DxrhYz3oE6hUEIBrDC+Jl6H+pcgVrCBEhXcN/3x+v1sntkyxjIYEKtM6AWhkC8YKfEgJIWiWuuAEkYAeIL5Gpc6P1+m1IPg7PX8/n0v6HsGdAEo1pj9xsgCRQFrXi80LwIMNrj166ACgyC8I2jOBgyVz5gWTnZ2hVQwjSKuMr23Y+xUXyGnlMKFZjVrrH+w/e5g0yx8IaRh2+wdd19B1wy+D9ClVAji8KqJhD2BAKBAux/bKNzJIxaNcuKKwxFEISqlYXgrhKtb2HSo+gVBFM7KQJw3/cDdtgEhBsA4NfKsLwdDCnk9++AusGZtSI/uhinvIhiHm6jcP6PK+1UjWtoQ3j+ZJjhVrMbDROmybDnEf92jf3OXgNO+gRVhhbPMw2TVSWoyhmcRWpYRrCFuRuY2ECZzFPMr6I9kpYMjKHIUZDK8naXxxH0DBpskbi+ADBpkZtVgfd5e5VZxTMIgrI2obURjNooPBizLZC89Rw0yHP2dNRCNW4xDt0CAi3ittW2MgwFkTzDwM5qPRfG51nVZM3pOJi8wOYevirzzX5D2z3r81CFUVJhMEfwfNINOhWSi1UfaoRQFzL0fHLbStWZQ+0yguxU9M8bkLimHiHCN6hO6/hvb/6Cqj6rTSbmrS1OAjMMA1EbJMAdFZXq/OiCK/Fu8DmYzzk8vW0mKV9ha9hffMILG0wwJ0CycGdmHKg4p7qHOQnTeShXMD/TZ5hRO8phvlNkqQzkiRtVnWnImMvdBL9tFx+zoomH78djy2OxDDkhvcFPqXymyCVlkFI8Oyk1ymdXIW7DnIDl13cBSqAue/TPX34AQUDBIiqR4IkAAAAASUVORK5CYII=","edit":false,"name":"Layer 4","opacity":"1","active":true,"unqid":"oo532f","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":1,"unqid":"vr50fd","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAjVJREFUWEfNWEFywkAMa+D/T4Z0nEEdRZHshR5aTiQhu7JsyV62fd/3r3/y2T4Fs22bDeE3sb0FRgHUxnzPXb9D+hKYtOHj8fi63W4toAKzytYIpoAg4sREgbrf7zFtWGNiqQXDi7gaYZC10fP5PEAVuPoUa/XBuxNDEYymhhet79iQGeHNFMAKIAtGaa1rbA6q6x4i1/ShTjjFnKLE0AWMU4yyMuU+gWHQDtAJzKpqeFEoCgBRN7ju5K+ALJiUe6WdgXBNMCCslTzqtBccmBebvqOAVS1aF1MRX54rGKZXZYmXnZLAkrKSVGTVmphRerVOCig8BQy5unFKUwH87FVgOgm6lLHjOvftAuEUXwTDYBhx0Y2+o4sDQN2v34ElJ111cXbnCEYbWio+RAbndc2S10pg6n0Ff0g79R12WzYyLmBmrWMmFfbJj1KaHECOGIunVpCU2LqzY0aj1XaAWgHVrj24NGuXtzXjouj6iNbBVG/KIoLhwezYrzO9rpYcINeZtVc5Fk8+MzETTeo1Bepzljs/06aKfS9gUsFNjc5F6nqUayGX1sNHFXViO3O8jihJ0qtAtJgPMvTc1JndZPNu9GApu+8Mvh07p1GiU9xUh5b1dKJ0KXPDFXsNq09d2o0MWm9LRxU7e9DxNo0J3FBVOdYop7O2G466EZLZczXWqW88Ub7bY5ya1KEToCUwCuikADK+5MquWD9Kk27MRpXSpWY2GSOeLzNzqfy//n9mNcJPf/cNbqwfxYQm5T8AAAAASUVORK5CYII=","width":"35","height":"35"}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMA/sfR5H8Fkddasdmnacvx//8745jkhasdASD945kjknhj/AAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAjVJREFUWEfNWEFywkAMa+D/T4Z0nEEdRZHshR5aTiQhu7JsyV62fd/3r3/y2T4Fs22bDeE3sb0FRgHUxnzPXb9D+hKYtOHj8fi63W4toAKzytYIpoAg4sREgbrf7zFtWGNiqQXDi7gaYZC10fP5PEAVuPoUa/XBuxNDEYymhhet79iQGeHNFMAKIAtGaa1rbA6q6x4i1/ShTjjFnKLE0AWMU4yyMuU+gWHQDtAJzKpqeFEoCgBRN7ju5K+ALJiUe6WdgXBNMCCslTzqtBccmBebvqOAVS1aF1MRX54rGKZXZYmXnZLAkrKSVGTVmphRerVOCig8BQy5unFKUwH87FVgOgm6lLHjOvftAuEUXwTDYBhx0Y2+o4sDQN2v34ElJ111cXbnCEYbWio+RAbndc2S10pg6n0Ff0g79R12WzYyLmBmrWMmFfbJj1KaHECOGIunVpCU2LqzY0aj1XaAWgHVrj24NGuXtzXjouj6iNbBVG/KIoLhwezYrzO9rpYcINeZtVc5Fk8+MzETTeo1Bepzljs/06aKfS9gUsFNjc5F6nqUayGX1sNHFXViO3O8jihJ0qtAtJgPMvTc1JndZPNu9GApu+8Mvh07p1GiU9xUh5b1dKJ0KXPDFXsNq09d2o0MWm9LRxU7e9DxNo0J3FBVOdYop7O2G466EZLZczXWqW88Ub7bY5ya1KEToCUwCuikADK+5MquWD9Kk27MRpXSpWY2GSOeLzNzqfy//n9mNcJPf/cNbqwfxYQm5T8AAAAASUVORK5CYII=","palette_id":false} \ No newline at end of file diff --git a/applications/plugins/vbmigrate/.github/FUNDING.yml b/applications/plugins/vbmigrate/.github/FUNDING.yml new file mode 100644 index 000000000..2cabf7401 --- /dev/null +++ b/applications/plugins/vbmigrate/.github/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: caralynx diff --git a/applications/plugins/vbmigrate/.github/workflows/build-fap.yml b/applications/plugins/vbmigrate/.github/workflows/build-fap.yml new file mode 100644 index 000000000..f65b8609a --- /dev/null +++ b/applications/plugins/vbmigrate/.github/workflows/build-fap.yml @@ -0,0 +1,12 @@ +name: Build FAP +on: [push] + +jobs: + build: + if: ${{ false }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Build + uses: oleksiikutuzov/flipperzero-ufbt-action@v1 diff --git a/applications/plugins/vbmigrate/LICENSE b/applications/plugins/vbmigrate/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/plugins/vbmigrate/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/plugins/vbmigrate/README.md b/applications/plugins/vbmigrate/README.md new file mode 100644 index 000000000..7238768f0 --- /dev/null +++ b/applications/plugins/vbmigrate/README.md @@ -0,0 +1,121 @@ +VB Lab Migration Assistant for Flipper Zero +=========================================== + +This app is designed to make transferring your characters from VB Lab and VBC +Lab more convenient. + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/C0C81P4PX) + +Background +---------- +The Vital Bracelet Arena app is going to be the new companion app for the Vital +Bracelet series of fitness bracelet toys, however the app does not support +account linking from VB Lab and VBC Lab, and requires you to transfer characters +one by one from those apps through your Vital Bracelet. Because the Vital +Bracelet can only hold at most two characters at a time and requires inserting +Dim/VBM to complete the transfer, transferring all of your characters may take +a significant amount of time. The VB Lab Migration Assistant is designed to make +this process faster by allowing you to store an unlimited amount of characters +on the Flipper and to bypass the Dim loading process, therefore allowing you to +transfer your characters without having to flip back and forth between the apps +or wasting time loading data that will never be used. + +Usage +----- + +### 1. Register Vital Bracelet + +Registering your Vital Bracelet to the Flipper creates template data used for +emulating the VB using the Flipper. + +1. Select "Register Vital Bracelet" from Migration Assistant's main menu. +2. If you have an active character on your Vital Bracelet, **transfer it to VB + Lab**. The active character slot on the VB has to be empty to avoid issues + when transferring character from VB Lab to the Flipper. Press the right key + on Flipper. +3. If there is an active character in VB Lab, put it in storage. +4. On VB Lab's "Scan" page, tap "Vital Bracelet -> App". Press the right key on + Flipper. +5. Tap the Flipper to your Vital Bracelet. You do not have to put the VB in + scan mode. When Flipper finishes reading, it will beep. +6. On VB Lab, tap "Send", and tap the Flipper to your phone. VB Lab will give + you an error message. This is normal, and you can dismiss it. The Flipper + will beep. Wait for it to switch to the next screen. +7. Tap the Flipper to the original Vital Bracelet again. The Flipper will beep + when it's finished reading. +8. Name the Vital Bracelet and select "Save". + +The Vital Bracelet info will show after saving. + +### 2. Select Vital Bracelet, Vital Bracelet Info + +Use the "Select Vital Bracelet" menu to load a previously registered VB. After +loading, you will see information about this Vital Bracelet: + +- Registered name +- Vital Bracelet type +- Number of characters captured + +Press the right key to see a menu of options you can use with the registered VB. + +### Spoof Version + +Vital Bracelet Arena splits Vital Bracelet and Vital Hero Digimon into +mutually-exclusive categories, and currently transferring VBM characters from +VBDM has not been tested, so if you want to transfer between categories, you +can select the Vital Bracelet version to spoof to change the VB type and force +the app to perform transfers between categories. + +### 3. Transfer App > Flipper + +Use this mode to transfer characters from VB Lab to Flipper. + +1. An instructions screen is displayed. Press right key to continue. +2. On your VB Lab app, wake up the character you want to send (if you have not + already). +3. Go to the "Scan" screen, and tap "App -> Vital Bracelet". Select "Check Dim + Card". If prompted to send Special Missions or items, tap "No". +4. Tap the Flipper to your phone. Release when VB Lab indicates to do so, and + wait for Flipper to beep. +5. Tap "Check Dim Installation" on VB Lab, and tap Flipper to your phone. + Flipper will beep. +6. Tap "Send" on VB Lab, and tap Flipper to your phone. Hold it there until VB + Lab shows the transfer animation. Flipper will beep after it has received + the character. +7. Tap "OK" on VB Lab. Flipper will have automatically reloaded the template so + you can repeat from step 2 again to transfer more characters. +8. When you are done transferring characters, press the left key on Flipper to + exit the transfer mode. + +Note if you have previously transferred characters to Flipper, the new +characters you transfer will be put at the end of the sequence of characters +when transferring from Flipper to app. + +### 4. Transfer Flipper > App + +Use this mode to transfer characters from Flipper to VB Lab. + +1. An instructions screen is displayed. Press right key to continue. +2. On your VB Lab app, if you have an active character, save the character into + storage. +3. Go to the "Scan" screen, and tap "Vital Bracelet -> App". +4. Tap "Send". Tap your Flipper to your phone, and hold until VB Lab shows the + transfer animation. Flipper will beep when it detected the transfer and will + load the next character. +5. Repeat from step 2 until all of your characters have been transferred. +6. If you want to skip a certain character, press the right key to skip and load + the next character. +7. You will see the "Transfers complete" screen on Flipper when all characters + have been transferred. Press the right key to return to the menu. + +### 5. Clear Captures + +If you are doing transfers in batches, you can clear the currently captured +characters so you do not have to skip them when transferring to app. Check the +details, and press the right key to confirm deletion. + +### 6. Delete Vital Bracelet + +If you want to unregister the Vital Bracelet, you can use this option to delete +its captures and remove it from the "Select Vital Bracelet" list. Check the +details, and press the right key to confirm deletion. diff --git a/applications/plugins/vbmigrate/application.fam b/applications/plugins/vbmigrate/application.fam new file mode 100644 index 000000000..02871f81e --- /dev/null +++ b/applications/plugins/vbmigrate/application.fam @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# VB Lab Migration Assistant for Flipper Zero +# Copyright (C) 2022 cyanic +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +App( + appid="vb_migrate", + name="VB Migration Assistant", + apptype=FlipperAppType.EXTERNAL, + entry_point="vb_migrate_app", + requires=["gui", "storage"], + stack_size=2 * 1024, + fap_version=(0,2), + fap_libs=["nfc", "misc", "mbedtls", "ST25RFAL002", "gcc", "toolbox"], + fap_category="Tools", + fap_description="Makes transferring characters with VB Lab less cumbersome", + fap_author="cyanic", + fap_weburl="https://github.com/GMMan/flipperzero-vb-migrate", + fap_icon_assets="icons", +) diff --git a/applications/plugins/vbmigrate/gui/modules/file_select.c b/applications/plugins/vbmigrate/gui/modules/file_select.c new file mode 100644 index 000000000..c7560af75 --- /dev/null +++ b/applications/plugins/vbmigrate/gui/modules/file_select.c @@ -0,0 +1,481 @@ +#include "file_select.h" +#include +#include + +#define FILENAME_COUNT 4 + +struct FileSelect { + // public + View* view; + Storage* fs_api; + const char* path; + const char* extension; + + bool init_completed; + + FileSelectCallback callback; + void* context; + + char* buffer; + uint8_t buffer_size; +}; + +typedef struct { + FuriString* filename[FILENAME_COUNT]; + uint8_t position; + + uint16_t first_file_index; + uint16_t file_count; + +} FileSelectModel; + +bool file_select_fill_strings(FileSelect* file_select); +bool file_select_fill_count(FileSelect* file_select); +static bool file_select_init_inner(FileSelect* file_select); + +static void file_select_draw_callback(Canvas* canvas, void* _model) { + FileSelectModel* model = _model; + + FuriString* string_buff; + const uint8_t item_height = 16; + const uint8_t item_width = 123; + const uint8_t text_max_width = 115; + + canvas_clear(canvas); + canvas_set_font(canvas, FontSecondary); + + if(model->file_count) { + for(uint8_t i = 0; i < MIN(FILENAME_COUNT, model->file_count); i++) { + if(i == model->position) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 0, (i * item_height) + 1, item_width, item_height - 2); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_dot(canvas, 0, (i * item_height) + 1); + canvas_draw_dot(canvas, 0, (i * item_height) + item_height - 2); + canvas_draw_dot(canvas, item_width - 1, (i * item_height) + 1); + canvas_draw_dot(canvas, item_width - 1, (i * item_height) + item_height - 2); + } else { + canvas_set_color(canvas, ColorBlack); + } + + string_buff = furi_string_alloc_set(model->filename[i]); + elements_string_fit_width(canvas, string_buff, text_max_width); + canvas_draw_str( + canvas, 6, (i * item_height) + item_height - 4, furi_string_get_cstr(string_buff)); + + furi_string_free(string_buff); + } + } else { + canvas_draw_str(canvas, 6, item_height, "Empty folder"); + } + elements_scrollbar(canvas, model->first_file_index + model->position, model->file_count); +} + +static bool file_select_input_callback(InputEvent* event, void* context) { + FileSelect* file_select = (FileSelect*)context; + bool consumed = false; + + if((event->type == InputTypeShort) | (event->type == InputTypeRepeat)) { + if(!file_select->init_completed) { + if(!file_select_init_inner(file_select)) { + file_select->callback(false, file_select->context); + } + } else if(event->key == InputKeyUp) { + with_view_model( + file_select->view, + FileSelectModel * model, + { + if(model->position == 0) { + if(model->first_file_index == 0) { + // wrap + int16_t max_first_file_index = model->file_count - FILENAME_COUNT; + model->position = MIN(FILENAME_COUNT - 1, model->file_count - 1); + model->first_file_index = + max_first_file_index < 0 ? 0 : max_first_file_index; + } else { + model->first_file_index--; + } + } else if(model->position == 1) { + if(model->first_file_index == 0) { + model->position--; + } else { + model->first_file_index--; + } + } else { + model->position--; + } + }, + true); + consumed = true; + if(!file_select_fill_strings(file_select)) { + file_select->callback(false, file_select->context); + } + } else if(event->key == InputKeyDown) { + with_view_model( + file_select->view, + FileSelectModel * model, + { + uint16_t max_first_file_index = model->file_count > FILENAME_COUNT ? + model->file_count - FILENAME_COUNT : + 0; + + if(model->position >= MIN(FILENAME_COUNT - 1, model->file_count - 1)) { + if(model->first_file_index >= max_first_file_index) { + // wrap + model->position = 0; + model->first_file_index = 0; + } else { + model->first_file_index++; + } + } else if(model->position >= (FILENAME_COUNT - 2)) { + if(model->first_file_index >= max_first_file_index) { + model->position++; + } else { + model->first_file_index++; + } + } else { + model->position++; + } + }, + true); + consumed = true; + if(!file_select_fill_strings(file_select)) { + file_select->callback(false, file_select->context); + } + } else if(event->key == InputKeyOk) { + if(file_select->callback != NULL) { + size_t files = 0; + if(file_select->buffer) { + with_view_model( + file_select->view, + FileSelectModel * model, + { + files = model->file_count; + strlcpy( + file_select->buffer, + furi_string_get_cstr(model->filename[model->position]), + file_select->buffer_size); + }, + false); + }; + if(files > 0) { + file_select->callback(true, file_select->context); + } + } + consumed = true; + } + } + + return consumed; +} + +static bool file_select_init_inner(FileSelect* file_select) { + bool result = false; + if(file_select->path && file_select->extension && file_select->fs_api) { + if(file_select_fill_count(file_select)) { + if(file_select_fill_strings(file_select)) { + file_select->init_completed = true; + result = true; + } + } + } + + return result; +} + +FileSelect* file_select_alloc() { + FileSelect* file_select = malloc(sizeof(FileSelect)); + file_select->view = view_alloc(); + file_select->fs_api = furi_record_open("storage"); + + view_set_context(file_select->view, file_select); + view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel)); + view_set_draw_callback(file_select->view, file_select_draw_callback); + view_set_input_callback(file_select->view, file_select_input_callback); + + with_view_model( + file_select->view, + FileSelectModel * model, + { + for(uint8_t i = 0; i < FILENAME_COUNT; i++) { + model->filename[i] = furi_string_alloc(); + } + + model->first_file_index = 0; + model->file_count = 0; + }, + false); + + return file_select; +} + +void file_select_free(FileSelect* file_select) { + furi_assert(file_select); + with_view_model( + file_select->view, + FileSelectModel * model, + { + for(uint8_t i = 0; i < FILENAME_COUNT; i++) { + furi_string_free(model->filename[i]); + } + }, + false); + view_free(file_select->view); + free(file_select); + furi_record_close("storage"); +} + +View* file_select_get_view(FileSelect* file_select) { + furi_assert(file_select); + return file_select->view; +} + +void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) { + file_select->context = context; + file_select->callback = callback; +} + +void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension) { + furi_assert(file_select); + file_select->path = path; + file_select->extension = extension; +} + +void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size) { + file_select->buffer = buffer; + file_select->buffer_size = buffer_size; + + if(file_select->buffer) { + strlcpy(file_select->buffer, "", file_select->buffer_size); + } +} + +bool file_select_init(FileSelect* file_select) { + if(!file_select_init_inner(file_select)) { + file_select->callback(false, file_select->context); + return false; + } else { + return true; + } +} + +static bool filter_file(FileSelect* file_select, FileInfo* file_info, char* name) { + bool result = false; + + if((file_info->flags & FSF_DIRECTORY)) { + if(strcmp(file_select->extension, "*") == 0) { + result = true; + } else if(strstr(name, file_select->extension) != NULL) { + result = true; + } + } + + return result; +} + +bool file_select_fill_strings(FileSelect* file_select) { + furi_assert(file_select); + furi_assert(file_select->fs_api); + furi_assert(file_select->path); + furi_assert(file_select->extension); + + FileInfo file_info; + File* directory = storage_file_alloc(file_select->fs_api); + + uint8_t string_counter = 0; + uint16_t file_counter = 0; + const uint8_t name_length = 100; + char* name = malloc(name_length); + uint16_t first_file_index = 0; + + with_view_model( + file_select->view, + FileSelectModel * model, + { first_file_index = model->first_file_index; }, + false); + + if(!storage_dir_open(directory, file_select->path)) { + storage_dir_close(directory); + storage_file_free(directory); + free(name); + return true; + } + + while(1) { + if(!storage_dir_read(directory, &file_info, name, name_length)) { + break; + } + + if(storage_file_get_error(directory) == FSE_OK) { + if(filter_file(file_select, &file_info, name)) { + if(file_counter >= first_file_index) { + with_view_model( + file_select->view, + FileSelectModel * model, + { + furi_string_set(model->filename[string_counter], name); + + if(strcmp(file_select->extension, "*") != 0) { + furi_string_replace_all_str( + model->filename[string_counter], file_select->extension, ""); + } + }, + true); + string_counter++; + + if(string_counter >= FILENAME_COUNT) { + break; + } + } + file_counter++; + } + } else { + storage_dir_close(directory); + storage_file_free(directory); + free(name); + return false; + } + } + + storage_dir_close(directory); + storage_file_free(directory); + free(name); + return true; +} + +bool file_select_fill_count(FileSelect* file_select) { + furi_assert(file_select); + furi_assert(file_select->fs_api); + furi_assert(file_select->path); + furi_assert(file_select->extension); + + FileInfo file_info; + File* directory = storage_file_alloc(file_select->fs_api); + + uint16_t file_counter = 0; + const uint8_t name_length = 100; + char* name = malloc(name_length); + + if(!storage_dir_open(directory, file_select->path)) { + storage_dir_close(directory); + storage_file_free(directory); + free(name); + return true; + } + + while(1) { + if(!storage_dir_read(directory, &file_info, name, name_length)) { + break; + } + + if(storage_file_get_error(directory) == FSE_OK) { + if(filter_file(file_select, &file_info, name)) { + file_counter++; + } + } else { + storage_dir_close(directory); + storage_file_free(directory); + free(name); + return false; + } + } + + with_view_model( + file_select->view, FileSelectModel * model, { model->file_count = file_counter; }, false); + + storage_dir_close(directory); + storage_file_free(directory); + free(name); + return true; +} + +void file_select_set_selected_file_internal(FileSelect* file_select, const char* filename) { + furi_assert(file_select); + furi_assert(filename); + furi_assert(file_select->fs_api); + furi_assert(file_select->path); + furi_assert(file_select->extension); + + if(strlen(filename) == 0) return; + + FileInfo file_info; + File* directory = storage_file_alloc(file_select->fs_api); + + const uint8_t name_length = 100; + char* name = malloc(name_length); + uint16_t file_position = 0; + bool file_found = false; + + FuriString* filename_str = furi_string_alloc_set_str(filename); + if(strcmp(file_select->extension, "*") != 0) { + furi_string_cat_str(filename_str, file_select->extension); + } + + if(!storage_dir_open(directory, file_select->path)) { + furi_string_free(filename_str); + storage_dir_close(directory); + storage_file_free(directory); + free(name); + return; + } + + while(1) { + if(!storage_dir_read(directory, &file_info, name, name_length)) { + break; + } + + if(storage_file_get_error(directory) == FSE_OK) { + if(filter_file(file_select, &file_info, name)) { + if(strcmp(furi_string_get_cstr(filename_str), name) == 0) { + file_found = true; + break; + } + + file_position++; + } + } else { + furi_string_free(filename_str); + storage_dir_close(directory); + storage_file_free(directory); + free(name); + return; + } + } + + if(file_found) { + with_view_model( + file_select->view, + FileSelectModel * model, + { + uint16_t max_first_file_index = + model->file_count > FILENAME_COUNT ? model->file_count - FILENAME_COUNT : 0; + + model->first_file_index = file_position; + + if(model->first_file_index > 0) { + model->first_file_index -= 1; + } + + if(model->first_file_index >= max_first_file_index) { + model->first_file_index = max_first_file_index; + } + + model->position = file_position - model->first_file_index; + }, + true); + } + + furi_string_free(filename_str); + storage_dir_close(directory); + storage_file_free(directory); + free(name); +} + +void file_select_set_selected_file(FileSelect* file_select, const char* filename) { + file_select_set_selected_file_internal(file_select, filename); + + if(!file_select_fill_strings(file_select)) { + file_select->callback(false, file_select->context); + } +} diff --git a/applications/plugins/vbmigrate/gui/modules/file_select.h b/applications/plugins/vbmigrate/gui/modules/file_select.h new file mode 100644 index 000000000..ed3d5b60c --- /dev/null +++ b/applications/plugins/vbmigrate/gui/modules/file_select.h @@ -0,0 +1,31 @@ +/** + * @file file_select.h + * GUI: FileSelect view module API + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FileSelect FileSelect; + +typedef void (*FileSelectCallback)(bool result, void* context); + +FileSelect* file_select_alloc(); + +void file_select_free(FileSelect* file_select); +View* file_select_get_view(FileSelect* file_select); + +void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context); +void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension); +void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size); +bool file_select_init(FileSelect* file_select); +void file_select_set_selected_file(FileSelect* file_select, const char* filename); + +#ifdef __cplusplus +} +#endif diff --git a/applications/plugins/vbmigrate/icons/DolphinMafia_115x62.png b/applications/plugins/vbmigrate/icons/DolphinMafia_115x62.png new file mode 100644 index 000000000..66fdb40ff Binary files /dev/null and b/applications/plugins/vbmigrate/icons/DolphinMafia_115x62.png differ diff --git a/applications/plugins/vbmigrate/icons/DolphinNice_96x59.png b/applications/plugins/vbmigrate/icons/DolphinNice_96x59.png new file mode 100644 index 000000000..a299d3630 Binary files /dev/null and b/applications/plugins/vbmigrate/icons/DolphinNice_96x59.png differ diff --git a/applications/plugins/vbmigrate/icons/Error_18x18.png b/applications/plugins/vbmigrate/icons/Error_18x18.png new file mode 100644 index 000000000..16a5a74d9 Binary files /dev/null and b/applications/plugins/vbmigrate/icons/Error_18x18.png differ diff --git a/applications/plugins/vbmigrate/icons/Touch_26x26.png b/applications/plugins/vbmigrate/icons/Touch_26x26.png new file mode 100644 index 000000000..32a9f6293 Binary files /dev/null and b/applications/plugins/vbmigrate/icons/Touch_26x26.png differ diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene.c new file mode 100644 index 000000000..fcc168e28 --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "vb_migrate_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const vb_migrate_on_enter_handlers[])(void*) = { +#include "vb_migrate_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const vb_migrate_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "vb_migrate_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const vb_migrate_on_exit_handlers[])(void* context) = { +#include "vb_migrate_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers vb_migrate_scene_handlers = { + .on_enter_handlers = vb_migrate_on_enter_handlers, + .on_event_handlers = vb_migrate_on_event_handlers, + .on_exit_handlers = vb_migrate_on_exit_handlers, + .scene_num = VbMigrateSceneNum, +}; diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene.h b/applications/plugins/vbmigrate/scenes/vb_migrate_scene.h new file mode 100644 index 000000000..7187bdf0b --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) VbMigrateScene##id, +typedef enum { +#include "vb_migrate_scene_config.h" + VbMigrateSceneNum, +} VbMigrateScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers vb_migrate_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "vb_migrate_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "vb_migrate_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "vb_migrate_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_about.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_about.c new file mode 100644 index 000000000..19dc2f0f0 --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_about.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" + +void vb_migrate_scene_about_on_enter(void* context) { + VbMigrate* inst = context; + + // Perform your setup here + FuriString* temp_str = furi_string_alloc_printf( + "\e#Information\n" + "Version: %s\n" + "Developed by: cyanic\n" + "GitHub: https://github.com/GMMan/flipperzero-vb-migrate\n" + "\n" + "\e#Description\n" + "Makes transferring\n" + "characters with VB Lab less\n" + "cumbersome", + VB_MIGRATE_VERSION); + + Widget* widget = inst->widget; + widget_add_text_box_element( + widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! VB Migration Assistant \e!\n", + false); + widget_add_text_scroll_element(widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); +} + +bool vb_migrate_scene_about_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void vb_migrate_scene_about_on_exit(void* context) { + VbMigrate* inst = context; + + // Perform your cleanup here + widget_reset(inst->widget); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_config.h b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_config.h new file mode 100644 index 000000000..f090de9df --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_config.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +ADD_SCENE(vb_migrate, main_menu, MainMenu) +ADD_SCENE(vb_migrate, register, Register) +ADD_SCENE(vb_migrate, register_save, RegisterSave) +ADD_SCENE(vb_migrate, save_success, SaveSuccess) +ADD_SCENE(vb_migrate, select, Select) +ADD_SCENE(vb_migrate, load, Load) +ADD_SCENE(vb_migrate, info, Info) +ADD_SCENE(vb_migrate, dev_menu, DevMenu) +ADD_SCENE(vb_migrate, delete, Delete) +ADD_SCENE(vb_migrate, delete_success, DeleteSuccess) +ADD_SCENE(vb_migrate, from_app, FromApp) +ADD_SCENE(vb_migrate, to_app, ToApp) +ADD_SCENE(vb_migrate, delete_captures, DeleteCaptures) +ADD_SCENE(vb_migrate, delete_captures_success, DeleteCapturesSuccess) +ADD_SCENE(vb_migrate, about, About) diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete.c new file mode 100644 index 000000000..314d0f3c2 --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" +#include "../vb_tag.h" + +static void + vb_migrate_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) { + VbMigrate* inst = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(inst->view_dispatcher, result); + } +} + +void vb_migrate_scene_delete_on_enter(void* context) { + VbMigrate* inst = context; + FuriString* temp_str; + + // Perform your setup here + BantBlock* bant = vb_tag_get_bant_block(&inst->nfc_dev->dev_data); + const VbTagProduct* product = vb_tag_find_product(bant); + if(product) + temp_str = furi_string_alloc_printf("\e#Delete %s?\e#", product->short_name); + else + temp_str = furi_string_alloc_set_str("\e#Delete VB?\e#"); + + widget_add_text_box_element( + inst->widget, + 0, + 0, + 128, + 23, + AlignCenter, + AlignCenter, + furi_string_get_cstr(temp_str), + false); + widget_add_button_element( + inst->widget, GuiButtonTypeLeft, "Cancel", vb_migrate_scene_delete_widget_callback, inst); + widget_add_button_element( + inst->widget, GuiButtonTypeRight, "Delete", vb_migrate_scene_delete_widget_callback, inst); + + furi_string_printf(temp_str, "%s\nCharas. captured: %d", inst->text_store, inst->num_captured); + widget_add_string_multiline_element( + inst->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); +} + +bool vb_migrate_scene_delete_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(inst->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + if(vb_migrate_delete(inst, inst->text_store, true)) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneDeleteSuccess); + consumed = true; + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + inst->scene_manager, VbMigrateSceneSelect); + } + } + } + return consumed; +} + +void vb_migrate_scene_delete_on_exit(void* context) { + VbMigrate* inst = context; + + // Perform your cleanup here + widget_reset(inst->widget); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete_captures.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete_captures.c new file mode 100644 index 000000000..c1c10b68c --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete_captures.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" + +static void vb_migrate_scene_delete_captures_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + VbMigrate* inst = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(inst->view_dispatcher, result); + } +} + +void vb_migrate_scene_delete_captures_on_enter(void* context) { + VbMigrate* inst = context; + + // Perform your setup here + widget_add_text_box_element( + inst->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, "\e#Clear captures?\e#", false); + widget_add_button_element( + inst->widget, + GuiButtonTypeLeft, + "Cancel", + vb_migrate_scene_delete_captures_widget_callback, + inst); + widget_add_button_element( + inst->widget, + GuiButtonTypeRight, + "Delete", + vb_migrate_scene_delete_captures_widget_callback, + inst); + + FuriString* temp_str = + furi_string_alloc_printf("%s\nCharas. captured: %d", inst->text_store, inst->num_captured); + widget_add_string_multiline_element( + inst->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); +} + +bool vb_migrate_scene_delete_captures_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(inst->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + if(vb_migrate_delete(inst, inst->text_store, false)) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneDeleteCapturesSuccess); + consumed = true; + } else { + consumed = scene_manager_previous_scene(inst->scene_manager); + } + inst->num_captured = vb_migrate_count_captured_mons(inst, inst->text_store); + } + } + return consumed; +} + +void vb_migrate_scene_delete_captures_on_exit(void* context) { + VbMigrate* inst = context; + + // Perform your cleanup here + widget_reset(inst->widget); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete_captures_success.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete_captures_success.c new file mode 100644 index 000000000..8ba811958 --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete_captures_success.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" +#include "vb_migrate_icons.h" + +static void vb_migrate_scene_delete_captures_success_popup_callback(void* context) { + VbMigrate* inst = context; + view_dispatcher_send_custom_event(inst->view_dispatcher, 0); +} + +void vb_migrate_scene_delete_captures_success_on_enter(void* context) { + VbMigrate* inst = context; + + // Perform your setup here + Popup* popup = inst->popup; + popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); + popup_set_header(popup, "Cleared", 83, 19, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, inst); + popup_set_callback(popup, vb_migrate_scene_delete_captures_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewPopup); +} + +bool vb_migrate_scene_delete_captures_success_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = scene_manager_search_and_switch_to_previous_scene( + inst->scene_manager, VbMigrateSceneDevMenu); + } + return consumed; +} + +void vb_migrate_scene_delete_captures_success_on_exit(void* context) { + VbMigrate* inst = context; + + // Perform your cleanup here + popup_reset(inst->popup); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete_success.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete_success.c new file mode 100644 index 000000000..78386872f --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_delete_success.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" +#include "vb_migrate_icons.h" + +static void vb_migrate_scene_delete_success_popup_callback(void* context) { + VbMigrate* inst = context; + view_dispatcher_send_custom_event(inst->view_dispatcher, 0); +} + +void vb_migrate_scene_delete_success_on_enter(void* context) { + VbMigrate* inst = context; + + // Perform your setup here + Popup* popup = inst->popup; + popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); + popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, inst); + popup_set_callback(popup, vb_migrate_scene_delete_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewPopup); +} + +bool vb_migrate_scene_delete_success_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + uint32_t back_scenes[] = {VbMigrateSceneSelect, VbMigrateSceneMainMenu}; + consumed = scene_manager_search_and_switch_to_previous_scene_one_of( + inst->scene_manager, back_scenes, COUNT_OF(back_scenes)); + } + return consumed; +} + +void vb_migrate_scene_delete_success_on_exit(void* context) { + VbMigrate* inst = context; + + // Perform your cleanup here + popup_reset(inst->popup); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_dev_menu.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_dev_menu.c new file mode 100644 index 000000000..b756ac27d --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_dev_menu.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" + +typedef enum { + SubmenuDevMenuIndexTransferFromApp, + SubmenuDevMenuIndexTransferToApp, + SubmenuDevMenuIndexSpoof, + SubmenuDevMenuIndexClearCaptures, + SubmenuDevMenuIndexDeleteVb, + SubmenuDevMenuIndexSpoofSelection, // Always keep this last because we add tag type to it +} SubmenuDevMenuIndex; + +static void vb_migrate_scene_dev_menu_var_list_enter_callback(void* context, uint32_t index) { + VbMigrate* inst = context; + + view_dispatcher_send_custom_event(inst->view_dispatcher, index); +} + +static void vb_migrate_scene_dev_menu_spoof_change_callback(VariableItem* item) { + VbMigrate* inst = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + VbTagType tag_type = index + 1; + variable_item_set_current_value_text(item, vb_tag_get_tag_type_name(tag_type)); + view_dispatcher_send_custom_event( + inst->view_dispatcher, SubmenuDevMenuIndexSpoofSelection + tag_type); +} + +void vb_migrate_scene_dev_menu_on_enter(void* context) { + VbMigrate* inst = context; + VariableItemList* variable_list = inst->variable_list; + VariableItem* item; + + variable_item_list_set_enter_callback( + variable_list, vb_migrate_scene_dev_menu_var_list_enter_callback, inst); + variable_item_list_add(variable_list, "Transfer App > Flipper", 0, NULL, NULL); + variable_item_list_add(variable_list, "Transfer Flipper > App", 0, NULL, NULL); + item = variable_item_list_add( + variable_list, + "Spoof Version", + VbTagTypeMax - 1, + vb_migrate_scene_dev_menu_spoof_change_callback, + inst); + variable_item_set_current_value_index(item, inst->override_type - 1); + variable_item_set_current_value_text(item, vb_tag_get_tag_type_name(inst->override_type)); + + variable_item_list_add(variable_list, "Clear Captures", 0, NULL, NULL); + variable_item_list_add(variable_list, "Delete Vital Bracelet", 0, NULL, NULL); + + variable_item_list_set_selected_item( + variable_list, scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneDevMenu)); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewVariableItemList); +} + +bool vb_migrate_scene_dev_menu_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event < SubmenuDevMenuIndexSpoofSelection) { + scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneDevMenu, event.event); + } + + if(event.event == SubmenuDevMenuIndexTransferFromApp) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneFromApp); + consumed = true; + } else if(event.event == SubmenuDevMenuIndexTransferToApp) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneToApp); + consumed = true; + } else if(event.event == SubmenuDevMenuIndexClearCaptures) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneDeleteCaptures); + consumed = true; + } else if(event.event == SubmenuDevMenuIndexDeleteVb) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneDelete); + consumed = true; + } else if(event.event >= SubmenuDevMenuIndexSpoofSelection) { + // Note: skipping SubmenuDevMenuIndexSpoof because there's nothing to do on enter + inst->override_type = event.event - SubmenuDevMenuIndexSpoofSelection; + consumed = true; + } + } + return consumed; +} + +void vb_migrate_scene_dev_menu_on_exit(void* context) { + VbMigrate* inst = context; + + variable_item_list_reset(inst->variable_list); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_from_app.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_from_app.c new file mode 100644 index 000000000..ea3919bbd --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_from_app.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#include "../vb_migrate_i.h" +#include "../vb_tag.h" +#include "vb_migrate_icons.h" + +#define TAG "vb_migrate_scene_from_app" + +typedef enum { + FromAppStateInitial, + FromAppStateInstructions, + FromAppStateEmulateReady, + FromAppStateEmulateCheckDim, + FromAppStateEmulateTransferFromApp, + FromAppStateTemplateError, + FromAppStateSaveError, +} FromAppState; + +typedef enum { + FromAppEventTypeWidgetLeft, + FromAppEventTypeWidgetRight, + FromAppEventTypeTemplateLoadError, + FromAppEventTypeTagWrite, + FromAppEventTypeCaptureSaveError, + FromAppEventTypeCaptureSaveSuccess, +} FromAppEventType; + +static void + vb_migrate_scene_from_app_widget_callback(GuiButtonType result, InputType type, void* context) { + VbMigrate* inst = context; + + if(type == InputTypeShort) { + if(result == GuiButtonTypeLeft) + view_dispatcher_send_custom_event(inst->view_dispatcher, FromAppEventTypeWidgetLeft); + else if(result == GuiButtonTypeRight) + view_dispatcher_send_custom_event(inst->view_dispatcher, FromAppEventTypeWidgetRight); + } +} + +static bool vb_migrate_scene_from_app_worker_callback(NfcWorkerEvent event, void* context) { + VbMigrate* inst = context; + bool result = false; + + if(event == NfcWorkerEventSuccess) { + view_dispatcher_send_custom_event(inst->view_dispatcher, FromAppEventTypeTagWrite); + result = true; + } + + return result; +} + +static void vb_migrate_scene_from_app_set_nfc_state(VbMigrate* inst, FromAppState state) { + BantBlock* bant = vb_tag_get_bant_block(&inst->nfc_dev->dev_data); + if(state == FromAppStateEmulateReady) { + vb_tag_set_status(bant, VbTagStatusReady); + vb_tag_set_operation(bant, VbTagOperationReady); + } else if(state == FromAppStateEmulateCheckDim) { + vb_tag_set_status(bant, VbTagStatusReady | VbTagStatusDimReady); + vb_tag_set_operation(bant, VbTagOperationReady); + } else if(state == FromAppStateEmulateTransferFromApp) { + vb_tag_set_status(bant, 0); + vb_tag_set_operation(bant, VbTagOperationIdle); + } + + // Override tag type + if(inst->override_type != inst->orig_type && inst->override_type != VbTagTypeUnknown) { + vb_tag_set_item_id_no(bant, vb_tag_get_default_product(inst->override_type)); + } +} + +static bool vb_migrate_scene_from_app_is_state_changed(VbMigrate* inst, FromAppState state) { + BantBlock* bant = vb_tag_get_bant_block(&inst->nfc_dev->dev_data); + VbTagOperation operation = vb_tag_get_operation(bant); + + if(state == FromAppStateEmulateReady) { + return operation == VbTagOperationCheckDim; + } else if(state == FromAppStateEmulateCheckDim) { + return operation == VbTagOperationCheckDim || operation == VbTagOperationReturnFromApp; + } + + return false; +} + +static void vb_migrate_scene_from_app_set_state(VbMigrate* inst, FromAppState state) { + uint32_t curr_state = + scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneFromApp); + if(state != curr_state) { + Widget* widget = inst->widget; + + if(state == FromAppStateInstructions) { + widget_reset(widget); + widget_add_text_scroll_element( + widget, + 0, + 0, + 128, + 45, + "\e#To transfer characters\n" + "\e#to Flipper:\n" + "1. Wake up character from\n" + "storage\n" + "2. Sync character to Flipper\n" + "as if it was a Vital Bracelet.\n" + "Flipper will beep when it is\n" + "ready for the next step\n" + "3. The character is\n" + "automatically saved when the\n" + "transfer is complete, and will\n" + "be ready for another transfer\n" + "4. Repeat the above until you\n" + "have transferred all the\n" + "characters you want\n" + "\n" + "You can cancel at any time to\n" + "finish transferring."); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Cancel", + vb_migrate_scene_from_app_widget_callback, + inst); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "Next", + vb_migrate_scene_from_app_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + } else if(state == FromAppStateEmulateReady) { + vb_migrate_show_loading_popup(inst, true); + if(vb_migrate_load_nfc(inst, inst->text_store, VB_MIGRATE_TEMPLATE_NAME)) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 20, + AlignCenter, + AlignCenter, + FontPrimary, + "Ready, waiting\nfor Dim check"); + FuriString* temp_str = + furi_string_alloc_printf("Charas. captured: %d", inst->num_captured); + widget_add_string_element( + widget, + 63, + 43, + AlignCenter, + AlignCenter, + FontSecondary, + furi_string_get_cstr(temp_str)); + widget_add_icon_element(widget, 7, 7, &I_Touch_26x26); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Cancel", + vb_migrate_scene_from_app_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + furi_string_free(temp_str); + + vb_migrate_scene_from_app_set_nfc_state(inst, state); + nfc_worker_start( + inst->worker, + NfcWorkerStateMfUltralightEmulate, + &inst->nfc_dev->dev_data, + vb_migrate_scene_from_app_worker_callback, + inst); + vb_migrate_blink_emulate(inst); + } else { + view_dispatcher_send_custom_event( + inst->view_dispatcher, FromAppEventTypeTemplateLoadError); + } + vb_migrate_show_loading_popup(inst, false); + } else if(state == FromAppStateEmulateCheckDim) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 31, + AlignCenter, + AlignCenter, + FontPrimary, + "Waiting for\nsecond Dim\ncheck/transfer"); + widget_add_icon_element(widget, 7, 18, &I_Touch_26x26); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Cancel", + vb_migrate_scene_from_app_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + notification_message(inst->notifications, &sequence_success); + vb_migrate_scene_from_app_set_nfc_state(inst, state); + nfc_worker_start( + inst->worker, + NfcWorkerStateMfUltralightEmulate, + &inst->nfc_dev->dev_data, + vb_migrate_scene_from_app_worker_callback, + inst); + vb_migrate_blink_emulate(inst); + } else if(state == FromAppStateEmulateTransferFromApp) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 31, + AlignCenter, + AlignCenter, + FontPrimary, + "Transfer captured,\nsaving..."); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Cancel", + vb_migrate_scene_from_app_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + nfc_worker_stop(inst->worker); + vb_migrate_blink_stop(inst); + vb_migrate_scene_from_app_set_nfc_state(inst, state); + notification_message(inst->notifications, &sequence_success); + + // Restore original tag type if necessary + if(inst->override_type != inst->orig_type && inst->override_type != VbTagTypeUnknown) { + BantBlock* bant = vb_tag_get_bant_block(&inst->nfc_dev->dev_data); + vb_tag_set_item_id_no(bant, inst->orig_product); + } + + // Save the tag + inst->next_id = vb_migrate_get_next_id(inst, inst->text_store, inst->next_id, false); + FuriString* save_path = furi_string_alloc_printf( + VB_MIGRATE_CAPTURE_FORMAT, inst->next_id, NFC_APP_EXTENSION); + if(vb_migrate_save_nfc(inst, inst->text_store, furi_string_get_cstr(save_path))) { + view_dispatcher_send_custom_event( + inst->view_dispatcher, FromAppEventTypeCaptureSaveSuccess); + } else { + view_dispatcher_send_custom_event( + inst->view_dispatcher, FromAppEventTypeCaptureSaveError); + } + furi_string_free(save_path); + } else if(state == FromAppStateTemplateError) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, 80, 31, AlignCenter, AlignCenter, FontPrimary, "Could not\nload template"); + widget_add_icon_element(widget, 15, 22, &I_Error_18x18); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Cancel", + vb_migrate_scene_from_app_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + notification_message(inst->notifications, &sequence_error); + notification_message(inst->notifications, &sequence_set_red_255); + } else if(state == FromAppStateSaveError) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, 80, 31, AlignCenter, AlignCenter, FontPrimary, "Could not\nsave capture"); + widget_add_icon_element(widget, 15, 22, &I_Error_18x18); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Cancel", + vb_migrate_scene_from_app_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + notification_message(inst->notifications, &sequence_error); + notification_message(inst->notifications, &sequence_set_red_255); + } else { + furi_crash("Unknown new state in vb_migrate_scene_from_app_set_state"); + } + + scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneFromApp, state); + } +} + +void vb_migrate_scene_from_app_on_enter(void* context) { + VbMigrate* inst = context; + + // Perform your setup here + inst->next_id = 0; + scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneFromApp, FromAppStateInitial); + vb_migrate_scene_from_app_set_state(inst, FromAppStateInstructions); +} + +bool vb_migrate_scene_from_app_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == FromAppEventTypeWidgetLeft) { + consumed = scene_manager_previous_scene(inst->scene_manager); + } else if(event.event == FromAppEventTypeWidgetRight) { + vb_migrate_scene_from_app_set_state(inst, FromAppStateEmulateReady); + consumed = true; + } else if(event.event == FromAppEventTypeTagWrite) { + uint32_t state = + scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneFromApp); + if(vb_migrate_scene_from_app_is_state_changed(inst, state)) { + if(state == FromAppStateEmulateReady) { + nfc_worker_stop(inst->worker); + vb_migrate_blink_stop(inst); + vb_migrate_scene_from_app_set_state(inst, FromAppStateEmulateCheckDim); + consumed = true; + } else if(state == FromAppStateEmulateCheckDim) { + BantBlock* bant = vb_tag_get_bant_block(&inst->nfc_dev->dev_data); + VbTagOperation operation = vb_tag_get_operation(bant); + + if(operation == VbTagOperationReturnFromApp) { + nfc_worker_stop(inst->worker); + vb_migrate_blink_stop(inst); + vb_migrate_scene_from_app_set_state( + inst, FromAppStateEmulateTransferFromApp); + consumed = true; + } else if(operation == VbTagOperationCheckDim) { + // Don't need to reset tag, but should make a beep + vb_migrate_blink_stop(inst); + notification_message_block(inst->notifications, &sequence_success); + vb_migrate_blink_emulate(inst); + } + } + } + } else if(event.event == FromAppEventTypeTemplateLoadError) { + vb_migrate_scene_from_app_set_state(inst, FromAppStateTemplateError); + consumed = true; + } else if(event.event == FromAppEventTypeCaptureSaveError) { + vb_migrate_scene_from_app_set_state(inst, FromAppStateSaveError); + consumed = true; + } else if(event.event == FromAppEventTypeCaptureSaveSuccess) { + ++inst->num_captured; + ++inst->next_id; + vb_migrate_scene_from_app_set_state(inst, FromAppStateEmulateReady); + consumed = true; + } else { + furi_crash("Unknown event in vb_migrate_scene_from_app_on_event"); + } + } + return consumed; +} + +void vb_migrate_scene_from_app_on_exit(void* context) { + VbMigrate* inst = context; + + // Perform your cleanup here + widget_reset(inst->widget); + nfc_worker_stop(inst->worker); + vb_migrate_blink_stop(inst); + notification_message_block(inst->notifications, &sequence_reset_red); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_info.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_info.c new file mode 100644 index 000000000..e60f5dc0f --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_info.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" +#include "../vb_tag.h" + +static void + vb_migrate_scene_info_button_callback(GuiButtonType result, InputType type, void* context) { + VbMigrate* inst = context; + + if(type == InputTypeShort) { + if(result == GuiButtonTypeRight) { + view_dispatcher_send_custom_event(inst->view_dispatcher, result); + } + } +} + +void vb_migrate_scene_info_on_enter(void* context) { + VbMigrate* inst = context; + FuriString* temp_str = furi_string_alloc(); + + // Build info scroll + // Name + furi_string_cat_printf(temp_str, "\ec%s\n", inst->text_store); + + // Type + if(inst->orig_product == NULL) + furi_string_cat(temp_str, "Unknown product\n"); + else + furi_string_cat_printf(temp_str, "\e#%s\n", inst->orig_product->name); + + // Number of mons loaded + furi_string_cat_printf(temp_str, "Charas. captured: %d", inst->num_captured); + + widget_add_text_scroll_element(inst->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + widget_add_button_element( + inst->widget, GuiButtonTypeRight, "More", vb_migrate_scene_info_button_callback, inst); + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); +} + +bool vb_migrate_scene_info_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + UNUSED(inst); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + // Reset menu selection index + scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneDevMenu, 0); + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneDevMenu); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + uint32_t back_scenes[] = {VbMigrateSceneSelect, VbMigrateSceneMainMenu}; + consumed = scene_manager_search_and_switch_to_previous_scene_one_of( + inst->scene_manager, back_scenes, COUNT_OF(back_scenes)); + } + return consumed; +} + +void vb_migrate_scene_info_on_exit(void* context) { + VbMigrate* inst = context; + + // Perform your cleanup here + widget_reset(inst->widget); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_load.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_load.c new file mode 100644 index 000000000..8f316a1ed --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_load.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" + +#define TAG "vb_migrate_scene_load" + +void vb_migrate_scene_load_on_enter(void* context) { + VbMigrate* inst = context; + + // Perform your setup here + vb_migrate_show_loading_popup(inst, true); + view_dispatcher_send_custom_event(inst->view_dispatcher, 0); +} + +bool vb_migrate_scene_load_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(vb_migrate_load_nfc(inst, inst->text_store, VB_MIGRATE_TEMPLATE_NAME)) { + inst->num_captured = vb_migrate_count_captured_mons(inst, inst->text_store); + + BantBlock* bant = vb_tag_get_bant_block(&inst->nfc_dev->dev_data); + const VbTagProduct* product = vb_tag_find_product(bant); + inst->orig_product = product; + if(product) { + inst->orig_type = product->type; + } else { + inst->orig_type = VbTagTypeUnknown; + } + inst->override_type = inst->orig_type; + + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneInfo); + consumed = true; + } else { + consumed = scene_manager_previous_scene(inst->scene_manager); + } + vb_migrate_show_loading_popup(inst, false); + } + return consumed; +} + +void vb_migrate_scene_load_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_main_menu.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_main_menu.c new file mode 100644 index 000000000..13dea042d --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_main_menu.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" + +typedef enum { + SubmenuMainIndexRegister, + SubmenuMainIndexSelect, + SubmenuMainIndexAbout, +} SubmenuMainIndex; + +static void vb_migrate_scene_main_menu_submenu_callback(void* context, uint32_t index) { + VbMigrate* inst = context; + + view_dispatcher_send_custom_event(inst->view_dispatcher, index); +} + +void vb_migrate_scene_main_menu_on_enter(void* context) { + VbMigrate* inst = context; + Submenu* submenu = inst->submenu; + + submenu_add_item( + submenu, + "Register Vital Bracelet", + SubmenuMainIndexRegister, + vb_migrate_scene_main_menu_submenu_callback, + inst); + submenu_add_item( + submenu, + "Select Vital Bracelet", + SubmenuMainIndexSelect, + vb_migrate_scene_main_menu_submenu_callback, + inst); + submenu_add_item( + submenu, "About", SubmenuMainIndexAbout, vb_migrate_scene_main_menu_submenu_callback, inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewMenu); +} + +bool vb_migrate_scene_main_menu_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuMainIndexRegister) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneRegister); + consumed = true; + } else if(event.event == SubmenuMainIndexSelect) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneSelect); + consumed = true; + } else if(event.event == SubmenuMainIndexAbout) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneAbout); + consumed = true; + } + } + return consumed; +} + +void vb_migrate_scene_main_menu_on_exit(void* context) { + VbMigrate* inst = context; + + submenu_reset(inst->submenu); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_register.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_register.c new file mode 100644 index 000000000..9d800a0b9 --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_register.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#include "../vb_tag.h" +#include "../vb_migrate_i.h" +#include "vb_migrate_icons.h" + +typedef enum { + RegisterStateInitial, + RegisterStateInstructionInitial, + RegisterStateInstructionConnect, + RegisterStateCaptureInitial, + RegisterStateCaptureInvalidTag, + RegisterStateCapturePwd, + RegisterStateCaptureFull, + RegisterStateCaptureFailed, + RegisterStateCaptureIncorrectTag, +} RegisterState; + +typedef enum { + RegisterEventTypeNextButton, + RegisterEventTypePrevButton, + RegisterEventTypeVbReadInitial, + RegisterEventTypeVbPwdAuth, + RegisterEventTypeVbReadFullSuccess, + RegisterEventTypeVbReadFullFail, +} RegisterEventType; + +static void + vb_migrate_scene_register_widget_callback(GuiButtonType result, InputType type, void* context) { + VbMigrate* inst = context; + + if(type == InputTypeShort) { + if(result == GuiButtonTypeRight) + view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterEventTypeNextButton); + else if(result == GuiButtonTypeLeft) + view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterEventTypePrevButton); + } +} + +static bool + vb_migrate_scene_register_worker_read_initial_callback(NfcWorkerEvent event, void* context) { + VbMigrate* inst = context; + bool consumed = false; + + if(event == NfcWorkerEventReadMfUltralight) { + view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterEventTypeVbReadInitial); + consumed = true; + } + + return consumed; +} + +static bool vb_migrate_scene_register_worker_auth_callback(NfcWorkerEvent event, void* context) { + VbMigrate* inst = context; + bool consumed = false; + + if(event == NfcWorkerEventMfUltralightPwdAuth) { + view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterEventTypeVbPwdAuth); + consumed = true; + } + + return consumed; +} + +static bool + vb_migrate_scene_register_worker_full_capture_callback(NfcWorkerEvent event, void* context) { + VbMigrate* inst = context; + bool consumed = false; + + if(event == NfcWorkerEventMfUltralightPassKey) { + memcpy( + inst->nfc_dev->dev_data.mf_ul_data.auth_key, + inst->captured_pwd, + sizeof(inst->captured_pwd)); + consumed = true; + } else if(event == NfcWorkerEventSuccess) { + view_dispatcher_send_custom_event( + inst->view_dispatcher, RegisterEventTypeVbReadFullSuccess); + consumed = true; + } else if(event == NfcWorkerEventFail) { + view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterEventTypeVbReadFullFail); + consumed = true; + } + + return consumed; +} + +static void vb_migrate_scene_register_cleanup_state(VbMigrate* inst, RegisterState state) { + if(state == RegisterStateCaptureInvalidTag || state == RegisterStateCaptureFailed || + state == RegisterStateCaptureIncorrectTag) { + notification_message(inst->notifications, &sequence_reset_red); + } else if( + state == RegisterStateCaptureInitial || state == RegisterStateCapturePwd || + state == RegisterStateCaptureFull) { + vb_migrate_blink_stop(inst); + nfc_worker_stop(inst->worker); + } +} + +static void vb_migrate_scene_register_set_state(VbMigrate* inst, RegisterState state) { + uint32_t curr_state = + scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneRegister); + if(state != curr_state) { + vb_migrate_scene_register_cleanup_state(inst, curr_state); + Widget* widget = inst->widget; + + if(state == RegisterStateInstructionInitial) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 0, + 0, + AlignLeft, + AlignTop, + FontSecondary, + "Please make sure your current\ncharacter has been sent to\nVB Lab before continuing."); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Cancel", + vb_migrate_scene_register_widget_callback, + inst); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "Next", + vb_migrate_scene_register_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + } else if(state == RegisterStateInstructionConnect) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 0, + 0, + AlignLeft, + AlignTop, + FontSecondary, + "Prepare VB Lab:\n" + "1. Ensure current character in\n" + "VB Lab has been put in storage\n" + "2. Open the \"Scan\" screen\n" + "3. Tap \"Vital Bracelet -> App\""); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Cancel", + vb_migrate_scene_register_widget_callback, + inst); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "Next", + vb_migrate_scene_register_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + } else if(state == RegisterStateCaptureInitial) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 31, + AlignCenter, + AlignCenter, + FontPrimary, + "Tap Flipper to\nyour Vital\nBracelet"); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Previous", + vb_migrate_scene_register_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + nfc_device_clear(inst->nfc_dev); + inst->nfc_dev->dev_data.read_mode = NfcReadModeMfUltralight; + nfc_worker_start( + inst->worker, + NfcWorkerStateRead, + &inst->nfc_dev->dev_data, + vb_migrate_scene_register_worker_read_initial_callback, + inst); + vb_migrate_blink_read(inst); + } else if(state == RegisterStateCaptureInvalidTag) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 31, + AlignCenter, + AlignCenter, + FontSecondary, + "Tag is not a valid\nVital Bracelet,\nplease try again"); + widget_add_icon_element(widget, 15, 22, &I_Error_18x18); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + vb_migrate_scene_register_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + notification_message(inst->notifications, &sequence_set_red_255); + } else if(state == RegisterStateCapturePwd) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 31, + AlignCenter, + AlignCenter, + FontPrimary, + "Tap \"Send\" on\nVB Lab, then tap\nto Flipper"); + widget_add_icon_element(widget, 7, 18, &I_Touch_26x26); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Previous", + vb_migrate_scene_register_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + + BantBlock* bant = vb_tag_get_bant_block(&inst->nfc_dev->dev_data); + vb_tag_set_operation(bant, VbTagOperationReady); + vb_tag_set_status(bant, VbTagStatusReady); + nfc_worker_start( + inst->worker, + NfcWorkerStateMfUltralightEmulate, + &inst->nfc_dev->dev_data, + vb_migrate_scene_register_worker_auth_callback, + inst); + vb_migrate_blink_emulate(inst); + } else if(state == RegisterStateCaptureFull) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 31, + AlignCenter, + AlignCenter, + FontPrimary, + "Tap Flipper to\nVital Bracelet\nagain"); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Previous", + vb_migrate_scene_register_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + + inst->nfc_dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodAuto; + nfc_worker_start( + inst->worker, + NfcWorkerStateReadMfUltralightReadAuth, + &inst->nfc_dev->dev_data, + vb_migrate_scene_register_worker_full_capture_callback, + inst); + vb_migrate_blink_read(inst); + } else if(state == RegisterStateCaptureFailed) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 31, + AlignCenter, + AlignCenter, + FontSecondary, + "Failed to read\nVital Bracelet,\nplease try again"); + widget_add_icon_element(widget, 15, 22, &I_Error_18x18); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + vb_migrate_scene_register_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + notification_message(inst->notifications, &sequence_set_red_255); + } else if(state == RegisterStateCaptureIncorrectTag) { + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 31, + AlignCenter, + AlignCenter, + FontSecondary, + "Different tag read,\nplease try again"); + widget_add_icon_element(widget, 15, 22, &I_Error_18x18); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + vb_migrate_scene_register_widget_callback, + inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + notification_message(inst->notifications, &sequence_set_red_255); + } else { + furi_crash("Unknown new state in vb_migrate_scene_register_set_state"); + } + + scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneRegister, state); + } +} + +static bool vb_migrate_scene_register_next_state(VbMigrate* inst, RegisterState state) { + if(state == RegisterStateInstructionInitial) { + vb_migrate_scene_register_set_state(inst, RegisterStateInstructionConnect); + return true; + } else if(state == RegisterStateInstructionConnect) { + vb_migrate_scene_register_set_state(inst, RegisterStateCaptureInitial); + return true; + } + + return false; +} + +static bool + vb_migrate_scene_register_prev_state(VbMigrate* inst, RegisterState state, bool is_back) { + UNUSED(is_back); + + if(state == RegisterStateInstructionInitial || state == RegisterStateInstructionConnect) { + return scene_manager_previous_scene(inst->scene_manager); + } else if(state == RegisterStateCaptureInitial) { + vb_migrate_scene_register_set_state(inst, RegisterStateInstructionConnect); + return true; + } else if( + state == RegisterStateCaptureInvalidTag || state == RegisterStateCapturePwd || + state == RegisterStateCaptureFull) { + vb_migrate_scene_register_set_state(inst, RegisterStateCaptureInitial); + return true; + } else if(state == RegisterStateCaptureFailed || state == RegisterStateCaptureIncorrectTag) { + vb_migrate_scene_register_set_state(inst, RegisterStateCaptureFull); + return true; + } + + return is_back; +} + +void vb_migrate_scene_register_on_enter(void* context) { + VbMigrate* inst = context; + + scene_manager_set_scene_state( + inst->scene_manager, VbMigrateSceneRegister, RegisterStateInitial); + vb_migrate_scene_register_set_state(inst, RegisterStateInstructionInitial); +} + +bool vb_migrate_scene_register_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + RegisterState state = + scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneRegister); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == RegisterEventTypeNextButton) { + consumed = vb_migrate_scene_register_next_state(inst, state); + } else if(event.event == RegisterEventTypePrevButton) { + consumed = vb_migrate_scene_register_prev_state(inst, state, false); + } else if(event.event == RegisterEventTypeVbReadInitial) { + if(vb_tag_validate_product(&inst->nfc_dev->dev_data)) { + memcpy( + inst->captured_uid, + inst->nfc_dev->dev_data.nfc_data.uid, + sizeof(inst->captured_uid)); + notification_message(inst->notifications, &sequence_success); + vb_migrate_scene_register_set_state(inst, RegisterStateCapturePwd); + } else { + notification_message(inst->notifications, &sequence_error); + vb_migrate_scene_register_set_state(inst, RegisterStateCaptureInvalidTag); + } + consumed = true; + } else if(event.event == RegisterEventTypeVbPwdAuth) { + // Set up for auth + memcpy( + inst->captured_pwd, + inst->nfc_dev->dev_data.mf_ul_auth.pwd.raw, + sizeof(inst->captured_pwd)); + + notification_message(inst->notifications, &sequence_success); + vb_migrate_scene_register_set_state(inst, RegisterStateCaptureFull); + consumed = true; + } else if(event.event == RegisterEventTypeVbReadFullSuccess) { + NfcDeviceData* dev_data = &inst->nfc_dev->dev_data; + if(memcmp(dev_data->nfc_data.uid, inst->captured_uid, sizeof(inst->captured_uid)) || + dev_data->mf_ul_data.data_read != dev_data->mf_ul_data.data_size) { + notification_message(inst->notifications, &sequence_error); + vb_migrate_scene_register_set_state(inst, RegisterStateCaptureIncorrectTag); + } else { + notification_message(inst->notifications, &sequence_success); + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneRegisterSave); + } + consumed = true; + } else if(event.event == RegisterEventTypeVbReadFullFail) { + notification_message(inst->notifications, &sequence_error); + vb_migrate_scene_register_set_state(inst, RegisterStateCaptureFailed); + consumed = true; + } else { + furi_crash("Unknown event in vb_migrate_scene_register_on_event"); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = vb_migrate_scene_register_prev_state(inst, state, true); + } + return consumed; +} + +void vb_migrate_scene_register_on_exit(void* context) { + VbMigrate* inst = context; + RegisterState state = + scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneRegister); + + vb_migrate_scene_register_cleanup_state(inst, state); + widget_reset(inst->widget); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_register_save.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_register_save.c new file mode 100644 index 000000000..e388ed25e --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_register_save.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#include "../vb_migrate_i.h" +#include "../vb_tag.h" + +// This thing doesn't know what a FuriString is +#include + +typedef enum { + RegisterSaveEventTextInput, +} RegisterSaveEvent; + +static void vb_migrate_scene_register_save_text_input_callback(void* context) { + VbMigrate* inst = context; + + view_dispatcher_send_custom_event(inst->view_dispatcher, RegisterSaveEventTextInput); +} + +void vb_migrate_scene_register_save_on_enter(void* context) { + VbMigrate* inst = context; + + // Setup view + TextInput* text_input = inst->text_input; + text_input_set_header_text(text_input, "Name this Vital Bracelet"); + text_input_set_result_callback( + text_input, + vb_migrate_scene_register_save_text_input_callback, + inst, + inst->text_store, + VB_MIGRATE_MAX_DEV_NAME_LENGTH, + true); + + // Set default name + FuriString* temp_str; + NfcDeviceData* dev_data = &inst->nfc_dev->dev_data; + BantBlock* bant = vb_tag_get_bant_block(dev_data); + const VbTagProduct* prod = vb_tag_find_product(bant); + temp_str = furi_string_alloc_printf("%s_", prod->short_name); + for(size_t i = 0; i < dev_data->nfc_data.uid_len; ++i) { + furi_string_cat_printf(temp_str, "%02x", dev_data->nfc_data.uid[i]); + } + vb_migrate_text_store_set(inst, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + // We're validating whether folder exists + ValidatorIsFile* validator_is_file = + validator_is_file_alloc_init(VB_MIGRATE_FOLDER, "/" VB_MIGRATE_TEMPLATE_NAME, NULL); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewTextInput); + notification_message(inst->notifications, &sequence_set_green_255); +} + +bool vb_migrate_scene_register_save_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == RegisterSaveEventTextInput) { + if(strlen(inst->text_store) != 0) { + vb_migrate_show_loading_popup(inst, true); + if(vb_migrate_save_nfc(inst, inst->text_store, VB_MIGRATE_TEMPLATE_NAME)) { + inst->num_captured = 0; + + BantBlock* bant = vb_tag_get_bant_block(&inst->nfc_dev->dev_data); + const VbTagProduct* product = vb_tag_find_product(bant); + inst->orig_product = product; + if(product) { + inst->orig_type = product->type; + } else { + inst->orig_type = VbTagTypeUnknown; + } + inst->override_type = inst->orig_type; + + // Go to success + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneSaveSuccess); + } else { + // Otherwise just stay here + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewTextInput); + } + vb_migrate_show_loading_popup(inst, false); + + consumed = true; + } + } + } + return consumed; +} + +void vb_migrate_scene_register_save_on_exit(void* context) { + VbMigrate* inst = context; + + // Clear view + TextInput* text_input = inst->text_input; + ValidatorIsFile* validator = text_input_get_validator_callback_context(text_input); + text_input_set_validator(text_input, NULL, NULL); + validator_is_file_free(validator); + + text_input_reset(text_input); + notification_message_block(inst->notifications, &sequence_reset_green); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_save_success.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_save_success.c new file mode 100644 index 000000000..69d3710a3 --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_save_success.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" +#include "vb_migrate_icons.h" + +typedef enum { + SaveSuccessEventPopup, +} SaveSuccessEvent; + +static void vb_migrate_scene_save_success_popup_callback(void* context) { + VbMigrate* inst = context; + + view_dispatcher_send_custom_event(inst->view_dispatcher, SaveSuccessEventPopup); +} + +void vb_migrate_scene_save_success_on_enter(void* context) { + VbMigrate* inst = context; + + // Setup view + Popup* popup = inst->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, inst); + popup_set_callback(popup, vb_migrate_scene_save_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewPopup); +} + +bool vb_migrate_scene_save_success_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SaveSuccessEventPopup) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneInfo); + consumed = true; + } + } + return consumed; +} + +void vb_migrate_scene_save_success_on_exit(void* context) { + VbMigrate* inst = context; + + // Clear view + popup_reset(inst->popup); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_select.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_select.c new file mode 100644 index 000000000..84f8590d8 --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_select.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../vb_migrate_i.h" + +static void vb_migrate_scene_select_file_select_callback(bool result, void* context) { + VbMigrate* inst = context; + + view_dispatcher_send_custom_event(inst->view_dispatcher, result); +} + +void vb_migrate_scene_select_on_enter(void* context) { + VbMigrate* inst = context; + + // Perform your setup here + file_select_set_callback( + inst->file_select, vb_migrate_scene_select_file_select_callback, inst); + file_select_set_filter(inst->file_select, VB_MIGRATE_FOLDER, "*"); + file_select_set_result_buffer(inst->file_select, inst->text_store, sizeof(inst->text_store)); + file_select_init(inst->file_select); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewFileSelect); +} + +bool vb_migrate_scene_select_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + bool consumed = false; + UNUSED(inst); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event) { + // File selected + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneLoad); + consumed = true; + } + } + return consumed; +} + +void vb_migrate_scene_select_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/vbmigrate/scenes/vb_migrate_scene_to_app.c b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_to_app.c new file mode 100644 index 000000000..5f6055d4b --- /dev/null +++ b/applications/plugins/vbmigrate/scenes/vb_migrate_scene_to_app.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#include "../vb_migrate_i.h" +#include "../vb_tag.h" +#include "vb_migrate_icons.h" + +#define TAG "vb_migrate_scene_to_app" + +typedef enum { + ToAppStateInitial, + ToAppStateInstructions, + ToAppStateEmulateReady, + ToAppStateEmulateTransferToApp, + ToAppStateLoadError, + ToAppStateComplete, +} ToAppState; + +typedef enum { + ToAppEventTypeWidgetLeft, + ToAppEventTypeWidgetRight, + ToAppEventTypeEmulateStart, + ToAppEventTypeCaptureLoadError, + ToAppEventTypeTagWrite, +} ToAppEventType; + +static void + vb_migrate_scene_to_app_widget_callback(GuiButtonType result, InputType type, void* context) { + VbMigrate* inst = context; + + if(type == InputTypeShort) { + if(result == GuiButtonTypeLeft) + view_dispatcher_send_custom_event(inst->view_dispatcher, ToAppEventTypeWidgetLeft); + else if(result == GuiButtonTypeRight) + view_dispatcher_send_custom_event(inst->view_dispatcher, ToAppEventTypeWidgetRight); + } +} + +static bool vb_migrate_scene_to_app_worker_callback(NfcWorkerEvent event, void* context) { + VbMigrate* inst = context; + bool result = false; + + if(event == NfcWorkerEventSuccess) { + view_dispatcher_send_custom_event(inst->view_dispatcher, ToAppEventTypeTagWrite); + result = true; + } + + return result; +} + +static void vb_migrate_scene_to_app_set_nfc_state(VbMigrate* inst, ToAppState state) { + BantBlock* bant = vb_tag_get_bant_block(&inst->nfc_dev->dev_data); + if(state == ToAppStateEmulateReady) { + vb_tag_set_status(bant, VbTagStatusReady); + vb_tag_set_operation(bant, VbTagOperationReady); + } + + // Override tag type + if(inst->override_type != inst->orig_type && inst->override_type != VbTagTypeUnknown) { + vb_tag_set_item_id_no(bant, vb_tag_get_default_product(inst->override_type)); + } +} + +static bool vb_migrate_scene_to_app_is_state_changed(VbMigrate* inst, ToAppState state) { + BantBlock* bant = vb_tag_get_bant_block(&inst->nfc_dev->dev_data); + VbTagOperation operation = vb_tag_get_operation(bant); + + if(state == ToAppStateEmulateReady) { + return operation == VbTagOperationTransferToApp; + } + + return false; +} + +static void vb_migrate_scene_to_app_set_state(VbMigrate* inst, ToAppState state) { + uint32_t curr_state = scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneToApp); + if(state != curr_state) { + Widget* widget = inst->widget; + + if(state == ToAppStateInstructions) { + widget_reset(widget); + widget_add_text_scroll_element( + widget, + 0, + 0, + 128, + 45, + "\e#To transfer characters\n" + "\e#from Flipper:\n" + "1. Put the current character\n" + "into storage\n" + "2. Sync character from\n" + "Flipperas if it was a Vital\n" + "Bracelet. Flipper will beep\n" + "and automatically advance to\n" + "the next captured character\n" + "when VB Lab has transferred\n" + "the current character\n" + "\n" + "-> You can press the right\n" + "key to skip sending the\n" + "current character\n" + "3. Repeat the above until you\n" + "have transferred all the\n" + "characters you want\n" + "\n" + "You can cancel at any time to\n" + "finish transferring."); + widget_add_button_element( + widget, GuiButtonTypeLeft, "Cancel", vb_migrate_scene_to_app_widget_callback, inst); + widget_add_button_element( + widget, GuiButtonTypeRight, "Next", vb_migrate_scene_to_app_widget_callback, inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + } else if(state == ToAppStateEmulateReady) { + view_dispatcher_send_custom_event(inst->view_dispatcher, ToAppEventTypeEmulateStart); + } else if(state == ToAppStateLoadError) { + FuriString* temp_str = + furi_string_alloc_printf("Could not load\ncapture %03d", inst->next_id); + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 31, + AlignCenter, + AlignCenter, + FontPrimary, + furi_string_get_cstr(temp_str)); + widget_add_icon_element(widget, 15, 22, &I_Error_18x18); + widget_add_button_element( + widget, GuiButtonTypeLeft, "Cancel", vb_migrate_scene_to_app_widget_callback, inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + furi_string_free(temp_str); + notification_message(inst->notifications, &sequence_error); + notification_message(inst->notifications, &sequence_set_red_255); + } else if(state == ToAppStateComplete) { + widget_reset(widget); + widget_add_icon_element(widget, 32, 5, &I_DolphinNice_96x59); + widget_add_string_multiline_element( + widget, 28, 26, AlignCenter, AlignBottom, FontPrimary, "Transfers\ncomplete"); + widget_add_button_element( + widget, GuiButtonTypeRight, "Done", vb_migrate_scene_to_app_widget_callback, inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + } else { + furi_crash("Unknown new state in vb_migrate_scene_to_app_set_state"); + } + + scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneToApp, state); + } +} + +static void vb_migrate_scene_to_app_load_capture(VbMigrate* inst, bool go_next) { + if(go_next) { + nfc_worker_stop(inst->worker); + vb_migrate_blink_stop(inst); + ++inst->next_id; + ++inst->num_sent; + } + + if(inst->num_sent == inst->num_captured) { + vb_migrate_scene_to_app_set_state(inst, ToAppStateComplete); + } else { + uint32_t state = scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneToApp); + inst->next_id = vb_migrate_get_next_id(inst, inst->text_store, inst->next_id, true); + FuriString* temp_str = + furi_string_alloc_printf(VB_MIGRATE_CAPTURE_FORMAT, inst->next_id, NFC_APP_EXTENSION); + + vb_migrate_show_loading_popup(inst, true); + if(vb_migrate_load_nfc(inst, inst->text_store, furi_string_get_cstr(temp_str))) { + Widget* widget = inst->widget; + + widget_reset(widget); + widget_add_string_multiline_element( + widget, + 80, + 20, + AlignCenter, + AlignCenter, + FontPrimary, + "Ready, waiting\nfor transfer"); + furi_string_printf( + temp_str, "Progress: %d/%d", inst->num_sent + 1, inst->num_captured); + widget_add_string_element( + widget, + 63, + 43, + AlignCenter, + AlignCenter, + FontSecondary, + furi_string_get_cstr(temp_str)); + widget_add_icon_element(widget, 7, 7, &I_Touch_26x26); + widget_add_button_element( + widget, GuiButtonTypeLeft, "Cancel", vb_migrate_scene_to_app_widget_callback, inst); + widget_add_button_element( + widget, GuiButtonTypeRight, "Skip", vb_migrate_scene_to_app_widget_callback, inst); + + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewWidget); + + vb_migrate_scene_to_app_set_nfc_state(inst, state); + nfc_worker_start( + inst->worker, + NfcWorkerStateMfUltralightEmulate, + &inst->nfc_dev->dev_data, + vb_migrate_scene_to_app_worker_callback, + inst); + vb_migrate_blink_emulate(inst); + } else { + view_dispatcher_send_custom_event( + inst->view_dispatcher, ToAppEventTypeCaptureLoadError); + } + vb_migrate_show_loading_popup(inst, false); + furi_string_free(temp_str); + } +} + +void vb_migrate_scene_to_app_on_enter(void* context) { + VbMigrate* inst = context; + + // Perform your setup here + inst->next_id = 0; + inst->num_sent = 0; + scene_manager_set_scene_state(inst->scene_manager, VbMigrateSceneToApp, ToAppStateInitial); + vb_migrate_scene_to_app_set_state(inst, ToAppStateInstructions); +} + +bool vb_migrate_scene_to_app_on_event(void* context, SceneManagerEvent event) { + VbMigrate* inst = context; + uint32_t state = scene_manager_get_scene_state(inst->scene_manager, VbMigrateSceneToApp); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == ToAppEventTypeWidgetLeft) { + consumed = scene_manager_previous_scene(inst->scene_manager); + } else if(event.event == ToAppEventTypeWidgetRight) { + if(state == ToAppStateInstructions) { + vb_migrate_scene_to_app_set_state(inst, ToAppStateEmulateReady); + consumed = true; + } else if(state == ToAppStateEmulateReady) { + vb_migrate_scene_to_app_load_capture(inst, true); + consumed = true; + } else if(state == ToAppStateComplete) { + consumed = scene_manager_previous_scene(inst->scene_manager); + } + } else if(event.event == ToAppEventTypeEmulateStart) { + vb_migrate_scene_to_app_load_capture(inst, false); + } else if(event.event == ToAppEventTypeTagWrite) { + if(vb_migrate_scene_to_app_is_state_changed(inst, state)) { + if(state == ToAppStateEmulateReady) { + notification_message(inst->notifications, &sequence_success); + vb_migrate_scene_to_app_load_capture(inst, true); + consumed = true; + } + } + } else if(event.event == ToAppEventTypeCaptureLoadError) { + vb_migrate_scene_to_app_set_state(inst, ToAppStateLoadError); + consumed = true; + } else { + furi_crash("Unknown event in vb_migrate_scene_to_app_on_event"); + } + } + return consumed; +} + +void vb_migrate_scene_to_app_on_exit(void* context) { + VbMigrate* inst = context; + + // Perform your cleanup here + widget_reset(inst->widget); + nfc_worker_stop(inst->worker); + vb_migrate_blink_stop(inst); + notification_message_block(inst->notifications, &sequence_reset_red); +} diff --git a/applications/plugins/vbmigrate/vb_migrate.c b/applications/plugins/vbmigrate/vb_migrate.c new file mode 100644 index 000000000..fec624a0b --- /dev/null +++ b/applications/plugins/vbmigrate/vb_migrate.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#include "vb_migrate_i.h" + +#define TAG "vb_migrate" + +bool vb_migrate_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + VbMigrate* inst = context; + return scene_manager_handle_custom_event(inst->scene_manager, event); +} + +bool vb_migrate_back_event_callback(void* context) { + furi_assert(context); + VbMigrate* inst = context; + return scene_manager_handle_back_event(inst->scene_manager); +} + +void vb_migrate_blink_read(VbMigrate* inst) { + notification_message(inst->notifications, &sequence_blink_start_cyan); +} + +void vb_migrate_blink_emulate(VbMigrate* inst) { + notification_message(inst->notifications, &sequence_blink_start_magenta); +} + +void vb_migrate_blink_stop(VbMigrate* inst) { + notification_message_block(inst->notifications, &sequence_blink_stop); +} + +void vb_migrate_text_store_set(VbMigrate* inst, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(inst->text_store, sizeof(inst->text_store), text, args); + + va_end(args); +} + +void vb_migrate_text_store_clear(VbMigrate* inst) { + memset(inst->text_store, 0, sizeof(inst->text_store)); +} + +bool vb_migrate_save_nfc(VbMigrate* inst, const char* dev_name, const char* file_name) { + bool saved = false; + FuriString* temp_str = furi_string_alloc(); + + do { + furi_string_printf(temp_str, "%s/%s", VB_MIGRATE_FOLDER, dev_name); + if(!storage_simply_mkdir(inst->storage, furi_string_get_cstr(temp_str))) { + dialog_message_show_storage_error(inst->dialogs, "Can not create\ndata folder"); + break; + } + furi_string_cat_printf(temp_str, "/%s", file_name); + inst->nfc_dev->format = NfcDeviceSaveFormatMifareUl; + saved = nfc_device_save(inst->nfc_dev, furi_string_get_cstr(temp_str)); + } while(false); + + furi_string_free(temp_str); + return saved; +} + +bool vb_migrate_load_nfc(VbMigrate* inst, const char* dev_name, const char* file_name) { + bool saved = false; + FuriString* temp_str = + furi_string_alloc_printf("%s/%s/%s", VB_MIGRATE_FOLDER, dev_name, file_name); + saved = nfc_device_load(inst->nfc_dev, furi_string_get_cstr(temp_str), true); + + furi_string_free(temp_str); + return saved; +} + +bool vb_migrate_delete(VbMigrate* inst, const char* dev_name, bool whole_vb) { + bool deleted = false; + FuriString* dir_path = furi_string_alloc_printf("%s/%s", VB_MIGRATE_FOLDER, dev_name); + + if(whole_vb) { + deleted = storage_simply_remove_recursive(inst->storage, furi_string_get_cstr(dir_path)); + } else { + File* dir_handle = storage_file_alloc(inst->storage); + if(storage_dir_open(dir_handle, furi_string_get_cstr(dir_path))) { + FileInfo file_info; + char name[256]; + FuriString* file_path = furi_string_alloc(); + while(storage_dir_read(dir_handle, &file_info, name, sizeof(name))) { + // Files that is .nfc, but is not template + if(!(file_info.flags & FSF_DIRECTORY) && strstr(name, NFC_APP_EXTENSION) && + !strstr(name, VB_MIGRATE_TEMPLATE_NAME)) { + furi_string_printf(file_path, "%s/%s", furi_string_get_cstr(dir_path), name); + deleted = + storage_simply_remove(inst->storage, furi_string_get_cstr(file_path)); + if(!deleted) break; + } + } + + furi_string_free(file_path); + storage_dir_close(dir_handle); + } + storage_file_free(dir_handle); + } + + furi_string_free(dir_path); + return deleted; +} + +int vb_migrate_count_captured_mons(VbMigrate* inst, const char* dev_name) { + int count = 0; + + FuriString* dir_path = furi_string_alloc_printf("%s/%s", VB_MIGRATE_FOLDER, dev_name); + File* dir_handle = storage_file_alloc(inst->storage); + if(storage_dir_open(dir_handle, furi_string_get_cstr(dir_path))) { + FileInfo file_info; + char name[256]; + while(storage_dir_read(dir_handle, &file_info, name, sizeof(name))) { + // Files that is .nfc, but is not template + if(!(file_info.flags & FSF_DIRECTORY) && strstr(name, NFC_APP_EXTENSION) && + !strstr(name, VB_MIGRATE_TEMPLATE_NAME)) + ++count; + } + + storage_dir_close(dir_handle); + } + storage_file_free(dir_handle); + furi_string_free(dir_path); + + return count; +} + +int vb_migrate_get_next_id(VbMigrate* inst, const char* dev_name, int i, bool is_load) { + FuriString* dir_path = furi_string_alloc_printf("%s/%s", VB_MIGRATE_FOLDER, dev_name); + FuriString* file_path = furi_string_alloc(); + while(true) { + furi_string_printf( + file_path, + "%s/" VB_MIGRATE_CAPTURE_FORMAT, + furi_string_get_cstr(dir_path), + i, + NFC_APP_EXTENSION); + bool exit_cond = + storage_common_stat(inst->storage, furi_string_get_cstr(file_path), NULL) == + FSE_NOT_EXIST; + if(is_load) exit_cond = !exit_cond; + if(exit_cond) break; + ++i; + } + + furi_string_free(file_path); + furi_string_free(dir_path); + return i; +} + +void vb_migrate_show_loading_popup(VbMigrate* inst, bool show) { + TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + if(show) { + // Raise timer priority so that animations can play + vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + view_dispatcher_switch_to_view(inst->view_dispatcher, VbMigrateViewLoading); + } else { + // Restore default timer priority + vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + } +} + +VbMigrate* vb_migrate_alloc() { + VbMigrate* inst = malloc(sizeof(VbMigrate)); + + inst->view_dispatcher = view_dispatcher_alloc(); + inst->scene_manager = scene_manager_alloc(&vb_migrate_scene_handlers, inst); + view_dispatcher_enable_queue(inst->view_dispatcher); + view_dispatcher_set_event_callback_context(inst->view_dispatcher, inst); + view_dispatcher_set_custom_event_callback( + inst->view_dispatcher, vb_migrate_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + inst->view_dispatcher, vb_migrate_back_event_callback); + + // GUI + inst->gui = furi_record_open(RECORD_GUI); + + // Storage + inst->storage = furi_record_open(RECORD_STORAGE); + + // Notifications service + inst->notifications = furi_record_open(RECORD_NOTIFICATION); + + // Dialogs + inst->dialogs = furi_record_open(RECORD_DIALOGS); + + // NFC + inst->nfc_dev = nfc_device_alloc(); + inst->worker = nfc_worker_alloc(); + + // Submenu + inst->submenu = submenu_alloc(); + view_dispatcher_add_view( + inst->view_dispatcher, VbMigrateViewMenu, submenu_get_view(inst->submenu)); + + // Popup + inst->popup = popup_alloc(); + view_dispatcher_add_view( + inst->view_dispatcher, VbMigrateViewPopup, popup_get_view(inst->popup)); + + // Widget + inst->widget = widget_alloc(); + view_dispatcher_add_view( + inst->view_dispatcher, VbMigrateViewWidget, widget_get_view(inst->widget)); + + // File select + inst->file_select = file_select_alloc(); + view_dispatcher_add_view( + inst->view_dispatcher, VbMigrateViewFileSelect, file_select_get_view(inst->file_select)); + + // Text input + inst->text_input = text_input_alloc(); + view_dispatcher_add_view( + inst->view_dispatcher, VbMigrateViewTextInput, text_input_get_view(inst->text_input)); + + // Loading + inst->loading = loading_alloc(); + view_dispatcher_add_view( + inst->view_dispatcher, VbMigrateViewLoading, loading_get_view(inst->loading)); + + // Variable item list + inst->variable_list = variable_item_list_alloc(); + view_dispatcher_add_view( + inst->view_dispatcher, + VbMigrateViewVariableItemList, + variable_item_list_get_view(inst->variable_list)); + + // Dialog ex + // inst->dialog_ex = dialog_ex_alloc(); + // view_dispatcher_add_view( + // inst->view_dispatcher, VbMigrateViewDialogEx, dialog_ex_get_view(inst->dialog_ex)); + + return inst; +} + +void vb_migrate_free(VbMigrate* inst) { + // Dialog ex + // view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewDialogEx); + // dialog_ex_free(inst->dialog_ex); + + // Variable item list + view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewVariableItemList); + variable_item_list_free(inst->variable_list); + + // Loading + view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewLoading); + loading_free(inst->loading); + + // Text input + view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewTextInput); + text_input_free(inst->text_input); + + // File select + view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewFileSelect); + file_select_free(inst->file_select); + + // Widget + view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewWidget); + widget_free(inst->widget); + + // Popup + view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewPopup); + popup_free(inst->popup); + + // Submenu + view_dispatcher_remove_view(inst->view_dispatcher, VbMigrateViewMenu); + submenu_free(inst->submenu); + + // NFC + nfc_worker_free(inst->worker); + nfc_device_free(inst->nfc_dev); + + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_GUI); + + view_dispatcher_free(inst->view_dispatcher); + scene_manager_free(inst->scene_manager); + + free(inst); +} + +int32_t vb_migrate_app(void* p) { + UNUSED(p); + + VbMigrate* inst = vb_migrate_alloc(); + view_dispatcher_attach_to_gui(inst->view_dispatcher, inst->gui, ViewDispatcherTypeFullscreen); + + if(storage_simply_mkdir(inst->storage, VB_MIGRATE_FOLDER)) { + scene_manager_next_scene(inst->scene_manager, VbMigrateSceneMainMenu); + view_dispatcher_run(inst->view_dispatcher); + } else { + dialog_message_show_storage_error(inst->dialogs, "Can not create\napp folder"); + } + + vb_migrate_free(inst); + return 0; +} diff --git a/applications/plugins/vbmigrate/vb_migrate.h b/applications/plugins/vbmigrate/vb_migrate.h new file mode 100644 index 000000000..886ba0773 --- /dev/null +++ b/applications/plugins/vbmigrate/vb_migrate.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#define VB_MIGRATE_VERSION "0.2" + +#define VB_MIGRATE_FOLDER ANY_PATH("vb_migrate") + +typedef struct VbMigrate VbMigrate; + +VbMigrate* vb_migrate_alloc(); + +void vb_migrate_free(VbMigrate* inst); diff --git a/applications/plugins/vbmigrate/vb_migrate_i.h b/applications/plugins/vbmigrate/vb_migrate_i.h new file mode 100644 index 000000000..51a32318e --- /dev/null +++ b/applications/plugins/vbmigrate/vb_migrate_i.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#include "gui/modules/file_select.h" +#include +#include + +#include +#include + +#include + +#include "vb_migrate.h" +#include "scenes/vb_migrate_scene.h" +#include "vb_tag.h" + +#define VB_MIGRATE_TEMPLATE_NAME "template" NFC_APP_EXTENSION +#define VB_MIGRATE_CAPTURE_FORMAT "%03d%s" + +#define VB_MIGRATE_MAX_DEV_NAME_LENGTH (30) + +struct VbMigrate { + Gui* gui; + Storage* storage; + DialogsApp* dialogs; + NotificationApp* notifications; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + Submenu* submenu; + Popup* popup; + Widget* widget; + // DialogEx* dialog_ex; + FileSelect* file_select; + TextInput* text_input; + Loading* loading; + VariableItemList* variable_list; + NfcWorker* worker; + NfcDevice* nfc_dev; + char text_store[128]; + uint8_t captured_pwd[4]; + uint8_t captured_uid[7]; + int num_captured; + int next_id; + int num_sent; + VbTagType orig_type; + VbTagType override_type; + const VbTagProduct* orig_product; +}; + +typedef enum { + VbMigrateViewMenu, + VbMigrateViewPopup, + VbMigrateViewWidget, + VbMigrateViewTextInput, + // VbMigrateViewDialogEx, + VbMigrateViewFileSelect, + VbMigrateViewLoading, + VbMigrateViewVariableItemList, +} VbMigrateView; + +void vb_migrate_blink_read(VbMigrate* inst); +void vb_migrate_blink_emulate(VbMigrate* inst); +void vb_migrate_blink_stop(VbMigrate* inst); +void vb_migrate_text_store_set(VbMigrate* inst, const char* text, ...); +void vb_migrate_text_store_clear(VbMigrate* inst); +bool vb_migrate_save_nfc(VbMigrate* inst, const char* dev_name, const char* file_name); +bool vb_migrate_load_nfc(VbMigrate* inst, const char* dev_name, const char* file_name); +int vb_migrate_count_captured_mons(VbMigrate* inst, const char* dev_name); +bool vb_migrate_delete(VbMigrate* inst, const char* dev_name, bool whole_vb); +int vb_migrate_get_next_id(VbMigrate* inst, const char* dev_name, int i, bool is_load); +void vb_migrate_show_loading_popup(VbMigrate* inst, bool show); diff --git a/applications/plugins/vbmigrate/vb_tag.c b/applications/plugins/vbmigrate/vb_tag.c new file mode 100644 index 000000000..ce8edf7d5 --- /dev/null +++ b/applications/plugins/vbmigrate/vb_tag.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "vb_tag.h" + +#define VB_NAME_VBDM "VB Digital Monster" +#define VB_NAME_VBV "VB Digivice -V-" +#define VB_NAME_VBC "VB Characters" +#define VB_NAME_VH "Vital Hero" + +#define VB_NAME_VBDM_SHORT "VBDM" +#define VB_NAME_VBV_SHORT "VBV" +#define VB_NAME_VBC_SHORT "VBC" +#define VB_NAME_VH_SHORT "VH" + +static const VbTagProduct vb_tag_valid_products[] = { + {.item_id = 0x0200, + .item_no = 0x0100, + .name = VB_NAME_VBDM, + .short_name = VB_NAME_VBDM_SHORT, + .type = VbTagTypeVBDM}, + {.item_id = 0x0200, + .item_no = 0x0200, + .name = VB_NAME_VBDM, + .short_name = VB_NAME_VBDM_SHORT, + .type = VbTagTypeVBDM}, + {.item_id = 0x0200, + .item_no = 0x0300, + .name = VB_NAME_VBDM, + .short_name = VB_NAME_VBDM_SHORT, + .type = VbTagTypeVBDM}, + {.item_id = 0x0200, + .item_no = 0x0400, + .name = VB_NAME_VBV, + .short_name = VB_NAME_VBV_SHORT, + .type = VbTagTypeVBV}, + {.item_id = 0x0200, + .item_no = 0x0500, + .name = VB_NAME_VBV, + .short_name = VB_NAME_VBV_SHORT, + .type = VbTagTypeVBV}, + {.item_id = 0x0200, + .item_no = 0x0600, + .name = VB_NAME_VH, + .short_name = VB_NAME_VH_SHORT, + .type = VbTagTypeVH}, + {.item_id = 0x0300, + .item_no = 0x0100, + .name = VB_NAME_VBC, + .short_name = VB_NAME_VBC_SHORT, + .type = VbTagTypeVBC}, +}; + +static const char* vb_tag_type_names[] = { + "Unknown", + VB_NAME_VBDM_SHORT, + VB_NAME_VBV_SHORT, + VB_NAME_VBC_SHORT, + VB_NAME_VH_SHORT, +}; + +BantBlock* vb_tag_get_bant_block(NfcDeviceData* dev) { + return (BantBlock*)&dev->mf_ul_data.data[16]; +} + +const VbTagProduct* vb_tag_find_product(const BantBlock* bant) { + for(size_t i = 0; i < COUNT_OF(vb_tag_valid_products); ++i) { + const VbTagProduct* product = &vb_tag_valid_products[i]; + if(bant->item_id == product->item_id && bant->item_no == product->item_no) return product; + } + + return NULL; +} + +bool vb_tag_validate_product(NfcDeviceData* dev) { + // Must be NTAG I2C Plus 1k + if(dev->protocol != NfcDeviceProtocolMifareUl) return false; + if(dev->mf_ul_data.type != MfUltralightTypeNTAGI2CPlus1K) return false; + // Must match one of the known product IDs + BantBlock* bant = vb_tag_get_bant_block(dev); + if(bant->magic != BANT_MAGIC) return false; + return vb_tag_find_product(bant) != NULL; +} + +VbTagStatus vb_tag_get_status(const BantBlock* bant) { + return bant->status; +} + +void vb_tag_set_status(BantBlock* bant, VbTagStatus status) { + bant->status = status; +} + +VbTagOperation vb_tag_get_operation(const BantBlock* bant) { + return bant->operation; +} + +void vb_tag_set_operation(BantBlock* bant, VbTagOperation operation) { + bant->operation = operation; +} + +const VbTagProduct* vb_tag_get_default_product(VbTagType type) { + // IMPORTANT: Update when vb_tag_valid_products changes + switch(type) { + case VbTagTypeVBDM: + return &vb_tag_valid_products[2]; + case VbTagTypeVBV: + return &vb_tag_valid_products[4]; + case VbTagTypeVBC: + return &vb_tag_valid_products[6]; + case VbTagTypeVH: + return &vb_tag_valid_products[5]; + + default: + return NULL; + } +} + +void vb_tag_set_item_id_no(BantBlock* bant, const VbTagProduct* product) { + bant->item_id = product->item_id; + bant->item_no = product->item_no; +} + +const char* vb_tag_get_tag_type_name(VbTagType type) { + if(type < VbTagTypeMax) { + return vb_tag_type_names[type]; + } else { + return NULL; + } +} diff --git a/applications/plugins/vbmigrate/vb_tag.h b/applications/plugins/vbmigrate/vb_tag.h new file mode 100644 index 000000000..e101bade2 --- /dev/null +++ b/applications/plugins/vbmigrate/vb_tag.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// VB Lab Migration Assistant for Flipper Zero +// Copyright (C) 2022 cyanic +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#define BANT_MAGIC (0x544E4142) + +typedef struct { + uint32_t magic; + // Note: this should be big endian, but for convenience, we'll treat them as little endian + uint16_t item_id; + uint16_t item_no; + uint8_t status; + uint8_t dim_no; + uint8_t operation; + uint8_t reserved; + uint8_t app_flag; + uint8_t padding[3]; +} __attribute__((packed)) BantBlock; + +typedef enum { + VbTagTypeUnknown, + VbTagTypeVBDM, + VbTagTypeVBV, + VbTagTypeVBC, + VbTagTypeVH, + VbTagTypeMax +} VbTagType; + +typedef struct { + uint16_t item_id; + uint16_t item_no; + const char* name; + const char* short_name; + VbTagType type; +} VbTagProduct; + +typedef enum { + VbTagStatusReady = 1 << 0, + VbTagStatusDimReady = 1 << 1, +} VbTagStatus; + +typedef enum { + VbTagOperationIdle, + VbTagOperationReady, + VbTagOperationTransferToApp, + VbTagOperationCheckDim, + VbTagOperationReturnFromApp, + VbTagOperationSpotInit, + VbTagOperationSpotCommit, +} VbTagOperation; + +BantBlock* vb_tag_get_bant_block(NfcDeviceData* dev); +const VbTagProduct* vb_tag_find_product(const BantBlock* bant); +bool vb_tag_validate_product(NfcDeviceData* dev); +VbTagStatus vb_tag_get_status(const BantBlock* bant); +void vb_tag_set_status(BantBlock* bant, VbTagStatus status); +VbTagOperation vb_tag_get_operation(const BantBlock* bant); +void vb_tag_set_operation(BantBlock* bant, VbTagOperation operation); +const VbTagProduct* vb_tag_get_default_product(VbTagType type); +void vb_tag_set_item_id_no(BantBlock* bant, const VbTagProduct* product); +const char* vb_tag_get_tag_type_name(VbTagType type);