Просмотр исходного кода

Add: GAME BOY Photo Application

Esteban Fuentealba 2 лет назад
Сommit
5d0d4faa5d
45 измененных файлов с 2840 добавлено и 0 удалено
  1. BIN
      .flipcorg/gallery/1.png
  2. BIN
      .flipcorg/gallery/2.png
  3. BIN
      .flipcorg/gallery/3.png
  4. BIN
      .flipcorg/gallery/4.png
  5. 11 0
      .vscode/settings.json
  6. 124 0
      README.md
  7. 40 0
      README_catalog.md
  8. 21 0
      application.fam
  9. 4 0
      docs/changelog.md
  10. BIN
      docs/images/flipper-zero-flat-01.png
  11. BIN
      docs/images/flipper-zero-flat-02.png
  12. BIN
      docs/images/flipper-zero-flat-03.png
  13. BIN
      docs/images/flipper-zero-flat-04.png
  14. BIN
      docs/images/flipper-zero-flat-05.png
  15. BIN
      docs/images/flipper-zero-flat-06.png
  16. BIN
      docs/images/flipper-zero-flat-07.png
  17. BIN
      docs/images/flipper-zero-flat-08.png
  18. BIN
      docs/images/flipper-zero-flat-09.png
  19. BIN
      docs/images/flipper-zero-flat-10.png
  20. BIN
      docs/psd/gb_photo.psd
  21. 130 0
      helpers/bmp.c
  22. 13 0
      helpers/bmp.h
  23. 60 0
      helpers/boilerplate_custom_event.h
  24. 18 0
      helpers/malveke_photo.c
  25. 10 0
      helpers/malveke_photo.h
  26. BIN
      icons/arrow_13x6.png
  27. BIN
      icons/icon_10px.png
  28. 140 0
      malveke_gb_photo.c
  29. 147 0
      malveke_gb_photo.h
  30. 30 0
      scenes/boilerplate_scene.c
  31. 29 0
      scenes/boilerplate_scene.h
  32. 8 0
      scenes/boilerplate_scene_config.h
  33. 71 0
      scenes/boilerplate_scene_menu.c
  34. 50 0
      scenes/boilerplate_scene_scene_1.c
  35. 51 0
      scenes/boilerplate_scene_scene_2.c
  36. 595 0
      scenes/boilerplate_scene_settings.c
  37. 54 0
      scenes/boilerplate_scene_startscreen.c
  38. 82 0
      scenes/scene_file_select.c
  39. 78 0
      u8g2_font_5x7_mf.h
  40. 587 0
      views/boilerplate_scene_1.c
  41. 30 0
      views/boilerplate_scene_1.h
  42. 278 0
      views/boilerplate_scene_2.c
  43. 22 0
      views/boilerplate_scene_2.h
  44. 138 0
      views/boilerplate_startscreen.c
  45. 19 0
      views/boilerplate_startscreen.h

BIN
.flipcorg/gallery/1.png


BIN
.flipcorg/gallery/2.png


BIN
.flipcorg/gallery/3.png


BIN
.flipcorg/gallery/4.png


+ 11 - 0
.vscode/settings.json

@@ -0,0 +1,11 @@
+{
+    "files.associations": {
+        "malveke_gb_photo.h": "c",
+        "boilerplate_custom_event.h": "c",
+        "boilerplate_scene_1.h": "c",
+        "boilerplate_startscreen.h": "c",
+        "boilerplate_scene.h": "c",
+        "boilerplate_storage.h": "c",
+        "u8g2_font_5x7_mf.h": "c"
+    }
+}

+ 124 - 0
README.md

@@ -0,0 +1,124 @@
+# [MALVEKE] ***GAME BOY*** PHOTO
+
+<div align="center">
+
+**Official** | **Unleashed** | **RogueMaster** | **Xtreme**
+:- | :- | :- | :- 
+[![FlipC.org](https://flipc.org/EstebanFuentealba/MALVEKE-Flipper-Zero/badge?branch=main&root=flipper_companion_apps%2Fapplications%2Fexternal%2Fmalveke_gb_photo&firmware=official)](https://flipc.org/EstebanFuentealba/MALVEKE-Flipper-Zero?branch=main&root=flipper_companion_apps%2Fapplications%2Fexternal%2Fmalveke_gb_photo&firmware=official)|[![FlipC.org](https://flipc.org/EstebanFuentealba/MALVEKE-Flipper-Zero/badge?branch=main&root=flipper_companion_apps%2Fapplications%2Fexternal%2Fmalveke_gb_photo&firmware=unleashed)](https://flipc.org/EstebanFuentealba/MALVEKE-Flipper-Zero?branch=main&root=flipper_companion_apps%2Fapplications%2Fexternal%2Fmalveke_gb_photo&firmware=unleashed)|[![FlipC.org](https://flipc.org/EstebanFuentealba/MALVEKE-Flipper-Zero/badge?branch=main&root=flipper_companion_apps%2Fapplications%2Fexternal%2Fmalveke_gb_photo&firmware=roguemaster)](https://flipc.org/EstebanFuentealba/MALVEKE-Flipper-Zero?branch=main&root=flipper_companion_apps%2Fapplications%2Fexternal%2Fmalveke_gb_photo&firmware=roguemaster)|[![FlipC.org](https://flipc.org/EstebanFuentealba/MALVEKE-Flipper-Zero/badge?branch=main&root=flipper_companion_apps%2Fapplications%2Fexternal%2Fmalveke_gb_photo&firmware=xtreme)](https://flipc.org/EstebanFuentealba/MALVEKE-Flipper-Zero?branch=main&root=flipper_companion_apps%2Fapplications%2Fexternal%2Fmalveke_gb_photo&firmware=xtreme)
+</div>
+<br>
+
+<p align='center'>
+<a href="https://www.tindie.com/stores/efuentealba/?ref=offsite_badges&utm_source=sellers_efuentealba&utm_medium=badges&utm_campaign=badge_large"><img src="https://d2ss6ovg47m0r5.cloudfront.net/badges/tindie-larges.png" alt="I sell on Tindie" width="200" height="104"></a>
+</p>
+
+## Introduction
+
+## Features
+- Extract "large" photos of 4 colors (128x112) into .BMP format.
+- Extract a single photo or batch extract all photos.
+- Implement a Photo Viewer within Flipper Zero.
+- Restore deleted photos.
+- Modify the Color Palette.
+
+
+
+## Instructions for use.
+
+These instructions assume that you are starting at the Flipper Zero desktop. Otherwise, press the Back button until you are at the desktop.
+
+- Press the `OK` button on the Flipper to open the main menu.
+- Choose `Applications` from the menu.
+- Choose `GPIO` from the submenu.
+- Choose `[GB] GAME BOY PHOTO`
+
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-01.png" width="400" />
+        <br />
+    </p>
+
+- For the next step, you'll need a `.SAV` file extracted from the GAME BOY Camera's RAM. If you don't have one, you can use the [**[MALVEKE] GAME BOY Cartridge (GB/GBC)**](./flipper_companion_apps/applications/external/malveke_gb_cartridge/README.md#instructions-for-use) app to save the RAM.
+
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-02.png" width="400" />
+        <br />
+    </p>
+
+- Press the `OK` to enter to menu, After selecting the `.SAV` file, it will display the main menu. 
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-03.png" width="400" />
+        <br />
+    </p>
+- **Photo Viewer**, a simple image viewer where you navigate between photos using `LEFT`/`RIGHT`. Since the images are 128x112, you scroll vertically using the `UP` and `DOWN` keys.
+
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-04.png" width="400" />
+        <br />
+    </p>
+
+    - You can save the currently displayed image to the Flipper Zero's SD card by pressing the `OK` button. The image will be saved in the `SD Card/apps_data/malveke/photos` folder. 
+
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-05.png" width="400" />
+        <br />
+    </p>
+
+
+- **Export all** allows you to export all the images. Simply press the `OK` button and wait for the transfer of all images. 
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-06.png" width="400" />
+        <br />
+    </p>
+
+    - The images will be saved in the `SD Card/apps_data/malveke/photos` folder. 
+
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-07.png" width="400" />
+        <br />
+    </p>
+
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-08.png" width="400" />
+        <br />
+    </p>
+- **Settings**: General application configurations.
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-09.png" width="400" />
+        <br />
+    </p>
+
+    - `Palette`: Configure the color palette; there are over 50 palettes to choose from. By default, it's set to Black and White.
+    - `Info`: displays which images have been deleted.
+
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-10.png" width="400" />
+        <br />
+    </p>
+
+- Press the `Back` to back to principal menu.
+
+
+## TODO
+- [ ] Refactor Code
+- [ ] For BMP, avoid using memory to generate images; write directly to files.
+- [ ] Exhibit and view images through a Web Server interface.
+- [ ] Add frames to images.
+- [ ] Configure dimension upscaling.
+- [ ] Export to other file formats.
+- [ ] Extract Game Faces.
+
+<p align='center'>
+<br />
+<br />
+From Talcahuano 🇨🇱 with ❤ 
+</p>

+ 40 - 0
README_catalog.md

@@ -0,0 +1,40 @@
+# MALVEKE ***GAME BOY*** PHOTO
+
+
+## Introduction
+
+## Features
+- Extract "large" photos of 4 colors (128x112) into .BMP format.
+- Extract a single photo or batch extract all photos.
+- Implement a Photo Viewer within Flipper Zero.
+- Restore deleted photos.
+- Modify the Color Palette.
+
+## Instructions for use.
+
+These instructions assume that you are starting at the Flipper Zero desktop. Otherwise, press the Back button until you are at the desktop.
+
+- Press the **OK** button on the Flipper to open the main menu.
+- Choose **Applications** from the menu.
+- Choose **GPIO** from the submenu.
+- Choose **GB GAME BOY PHOTO**
+- For the next step, you'll need a **.SAV** file extracted from the GAME BOY Camera's RAM. If you don't have one, you can use the [**MALVEKE GAME BOY Cartridge (GB/GBC)**](https://github.com/EstebanFuentealba/MALVEKE-Flipper-Zero/blob/main/flipper_companion_apps/applications/external/malveke_gb_cartridge/README.md#instructions-for-use) app to save the RAM.
+- Press the **OK** to enter to menu, After selecting the **.SAV** file, it will display the main menu. 
+- **Photo Viewer**, a simple image viewer where you navigate between photos using **LEFT**/**RIGHT**. Since the images are 128x112, you scroll vertically using the **UP** and **DOWN** keys.
+    - You can save the currently displayed image to the Flipper Zero's SD card by pressing the **OK** button. The image will be saved in the **SD Card/apps_data/malveke/photos** folder. 
+- **Export all** allows you to export all the images. Simply press the **OK** button and wait for the transfer of all images. 
+    - The images will be saved in the **SD Card/apps_data/malveke/photos** folder. 
+- **Settings**: General application configurations.
+    - **Palette**: Configure the color palette; there are over 50 palettes to choose from. By default, it's set to Black and White.
+    - **Info**: displays which images have been deleted.
+- Press the **Back** to back to principal menu.
+
+
+## TODO
+- Refactor Code
+- For BMP, avoid using memory to generate images; write directly to files.
+- Exhibit and view images through a Web Server interface.
+- Add frames to images.
+- Configure dimension upscaling.
+- Export to other file formats.
+- Extract Game Faces.

+ 21 - 0
application.fam

@@ -0,0 +1,21 @@
+App(
+    appid="malveke_gb_photo",
+    name="[GB] GAME BOY PHOTO",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="boilerplate_app",
+    cdefines=["APP_GB_PHOTO"],
+    requires=[
+        "gui",
+        "storage",
+    ],
+    stack_size=2 * 1024,
+    order=10,
+    fap_version=[1,0],
+    fap_libs=["assets"],
+    fap_icon="icons/icon_10px.png",
+    fap_icon_assets="icons",
+    fap_category="GPIO",
+    fap_description="Game Boy Camera save RAM photo to BMP from the Flipper Zero.",
+	fap_author="Esteban Fuentealba",
+	fap_weburl="https://github.com/EstebanFuentealba/MALVEKE-Flipper-Zero/"
+)

+ 4 - 0
docs/changelog.md

@@ -0,0 +1,4 @@
+# Changelog - Patch Notes
+
+## Version 1.0.0
+The initial version of the application includes features to view and export images stored on a GAME BOY Camera cartridge.

BIN
docs/images/flipper-zero-flat-01.png


BIN
docs/images/flipper-zero-flat-02.png


BIN
docs/images/flipper-zero-flat-03.png


BIN
docs/images/flipper-zero-flat-04.png


BIN
docs/images/flipper-zero-flat-05.png


BIN
docs/images/flipper-zero-flat-06.png


BIN
docs/images/flipper-zero-flat-07.png


BIN
docs/images/flipper-zero-flat-08.png


BIN
docs/images/flipper-zero-flat-09.png


BIN
docs/images/flipper-zero-flat-10.png


BIN
docs/psd/gb_photo.psd


+ 130 - 0
helpers/bmp.c

@@ -0,0 +1,130 @@
+#include "bmp.h"
+void bmp_init(void *buf, long width, long height)
+{
+    long pad;
+    unsigned long size;
+    unsigned long uw = width;
+    unsigned long uh = -height;
+    unsigned char *p = (unsigned char *)buf;
+
+#ifdef BMP_COMPAT
+    uh = height;
+#endif
+
+    /* bfType */
+    *p++ = 0x42;
+    *p++ = 0x4D;
+
+    /* bfSize */
+    pad = (width * -3UL) & 3;
+    size = height * (width * 3 + pad) + 14 + 40;
+    *p++ = size >> 0;
+    *p++ = size >> 8;
+    *p++ = size >> 16;
+    *p++ = size >> 24;
+
+    /* bfReserved1 + bfReserved2 */
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+
+    /* bfOffBits */
+    *p++ = 0x36;
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+
+    /* biSize */
+    *p++ = 0x28;
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+
+    /* biWidth */
+    *p++ = uw >> 0;
+    *p++ = uw >> 8;
+    *p++ = uw >> 16;
+    *p++ = uw >> 24;
+
+    /* biHeight */
+    *p++ = uh >> 0;
+    *p++ = uh >> 8;
+    *p++ = uh >> 16;
+    *p++ = uh >> 24;
+
+    /* biPlanes */
+    *p++ = 0x01;
+    *p++ = 0x00;
+
+    /* biBitCount */
+    *p++ = 0x18;
+    *p++ = 0x00;
+
+    /* biCompression */
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+
+    /* biSizeImage */
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+
+    /* biXPelsPerMeter */
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+
+    /* biYPelsPerMeter */
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+
+    /* biClrUsed */
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+
+    /* biClrImportant */
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p++ = 0x00;
+    *p = 0x00;
+}
+void bmp_set(void *buf, long x, long y, unsigned long color)
+{
+    unsigned char *p;
+    unsigned char *hdr = (unsigned char *)buf;
+    unsigned long width =
+        (unsigned long)hdr[18] << 0 |
+        (unsigned long)hdr[19] << 8 |
+        (unsigned long)hdr[20] << 16 |
+        (unsigned long)hdr[21] << 24;
+    long pad = (width * -3UL) & 3;
+#ifdef BMP_COMPAT
+    unsigned long height =
+        (unsigned long)hdr[22] << 0 |
+        (unsigned long)hdr[23] << 8 |
+        (unsigned long)hdr[24] << 16 |
+        (unsigned long)hdr[25] << 24;
+    y = height - y - 1;
+#endif
+    p = hdr + 14 + 40 + y * (width * 3 + pad) + x * 3;
+    p[0] = color >> 0;
+    p[1] = color >> 8;
+    p[2] = color >> 16;
+}
+unsigned long bmp_encode(unsigned long color_hex)
+{
+    unsigned char r = (color_hex >> 16) & 0xFF;
+    unsigned char g = (color_hex >> 8) & 0xFF;
+    unsigned char b = color_hex & 0xFF;
+
+    return b | g << 8 | r << 16;
+}

+ 13 - 0
helpers/bmp.h

@@ -0,0 +1,13 @@
+#pragma once
+
+#ifndef BMP_H
+#define BMP_H
+
+    
+#define BMP_SIZE(w, h) ((h) * ((w) * 3 + (((w) * -3UL) & 3)) + 14 + 40)
+
+void bmp_init(void *buf, long width, long height);
+void bmp_set(void *buf, long x, long y, unsigned long color);
+unsigned long bmp_encode(unsigned long color_hex);
+
+#endif /* BMP_H */

+ 60 - 0
helpers/boilerplate_custom_event.h

@@ -0,0 +1,60 @@
+#pragma once
+
+typedef enum {
+    BoilerplateCustomEventStartscreenUp,
+    BoilerplateCustomEventStartscreenDown,
+    BoilerplateCustomEventStartscreenLeft,
+    BoilerplateCustomEventStartscreenRight,
+    BoilerplateCustomEventStartscreenOk,
+    BoilerplateCustomEventStartscreenBack,
+    BoilerplateCustomEventScene1Up,
+    BoilerplateCustomEventScene1Down,
+    BoilerplateCustomEventScene1Left,
+    BoilerplateCustomEventScene1Right,
+    BoilerplateCustomEventScene1Ok,
+    BoilerplateCustomEventScene1Back,
+    BoilerplateCustomEventScene2Up,
+    BoilerplateCustomEventScene2Down,
+    BoilerplateCustomEventScene2Left,
+    BoilerplateCustomEventScene2Right,
+    BoilerplateCustomEventScene2Ok,
+    BoilerplateCustomEventScene2Back,
+} BoilerplateCustomEvent;
+
+enum BoilerplateCustomEventType {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    BoilerplateCustomEventMenuVoid,
+    BoilerplateCustomEventMenuSelected,
+};
+
+#pragma pack(push, 1)
+typedef union {
+    uint32_t packed_value;
+    struct {
+        uint16_t type;
+        int16_t value;
+    } content;
+} BoilerplateCustomEventMenu;
+#pragma pack(pop)
+
+static inline uint32_t boilerplate_custom_menu_event_pack(uint16_t type, int16_t value) {
+    BoilerplateCustomEventMenu event = {.content = {.type = type, .value = value}};
+    return event.packed_value;
+}
+static inline void boilerplate_custom_menu_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) {
+    BoilerplateCustomEventMenu event = {.packed_value = packed_value};
+    if(type) *type = event.content.type;
+    if(value) *value = event.content.value;
+}
+
+static inline uint16_t boilerplate_custom_menu_event_get_type(uint32_t packed_value) {
+    uint16_t type;
+    boilerplate_custom_menu_event_unpack(packed_value, &type, NULL);
+    return type;
+}
+
+static inline int16_t boilerplate_custom_menu_event_get_value(uint32_t packed_value) {
+    int16_t value;
+    boilerplate_custom_menu_event_unpack(packed_value, NULL, &value);
+    return value;
+}

+ 18 - 0
helpers/malveke_photo.c

@@ -0,0 +1,18 @@
+#include "malveke_photo.h"
+
+void get_timefilename(FuriString *name, int index)
+{
+    FuriHalRtcDateTime datetime = {0};
+    furi_hal_rtc_get_datetime(&datetime);
+    furi_string_printf(
+        name,
+        "%s/%.4d%.2d%.2d-%.2d%.2d%.2d-%d.bmp",
+        MALVEKE_APP_FOLDER_PHOTOS,
+        datetime.year,
+        datetime.month,
+        datetime.day,
+        datetime.hour,
+        datetime.minute,
+        datetime.second,
+        index);
+}

+ 10 - 0
helpers/malveke_photo.h

@@ -0,0 +1,10 @@
+#pragma once
+
+#ifndef MALVEKE_PHOTO_H
+#define MALVEKE_PHOTO_H
+
+#include "../malveke_gb_photo.h"
+
+void get_timefilename(FuriString *name, int index);
+
+#endif /* MALVEKE_PHOTO_H */

BIN
icons/arrow_13x6.png


BIN
icons/icon_10px.png


+ 140 - 0
malveke_gb_photo.c

@@ -0,0 +1,140 @@
+#include "malveke_gb_photo.h"
+#include "u8g2_font_5x7_mf.h"
+
+bool boilerplate_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+void boilerplate_tick_event_callback(void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
+}
+
+//leave app if back button pressed
+bool boilerplate_navigation_event_callback(void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+Boilerplate* boilerplate_app_alloc() {
+    Boilerplate* app = malloc(sizeof(Boilerplate));
+    app->gui = furi_record_open(RECORD_GUI);
+    app->notification = furi_record_open(RECORD_NOTIFICATION);
+    app->storage = furi_record_open(RECORD_STORAGE);
+    
+    //Turn backlight on, believe me this makes testing your app easier
+    notification_message(app->notification, &sequence_display_backlight_on);
+
+
+    //Scene additions
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    app->scene_manager = scene_manager_alloc(&boilerplate_scene_handlers, app);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_navigation_event_callback(app->view_dispatcher, boilerplate_navigation_event_callback);
+    view_dispatcher_set_tick_event_callback(app->view_dispatcher, boilerplate_tick_event_callback, 100);
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, boilerplate_custom_event_callback);
+    app->submenu = submenu_alloc();
+
+
+    app->palette = BoilerplatePaletteBlackAndWhite;
+    app->info = BoilerplateInfoOn;
+    app->palette_color_hex_a = 0xFFFFFF;
+    app->palette_color_hex_b = 0xAAAAAA;
+    app->palette_color_hex_c = 0x555555;
+    app->palette_color_hex_d = 0x000000;
+    
+
+    app->file_path = furi_string_alloc();
+    app->camera_ram_sav = storage_file_alloc(app->storage);
+    app->page = 1;
+    app->pos_x = 0;
+    app->pos_y = 0;
+    app->show_instructions = true;
+
+   
+
+    // Used for File Browser
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+    app->file_path = furi_string_alloc();
+
+
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdMenu, submenu_get_view(app->submenu));
+    app->boilerplate_startscreen = boilerplate_startscreen_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdStartscreen, boilerplate_startscreen_get_view(app->boilerplate_startscreen));
+    app->boilerplate_scene_1 = boilerplate_scene_1_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdScene1, boilerplate_scene_1_get_view(app->boilerplate_scene_1));
+    app->boilerplate_scene_2 = boilerplate_scene_2_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdScene2, boilerplate_scene_2_get_view(app->boilerplate_scene_2));
+    
+    app->variable_item_list = variable_item_list_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdSettings, variable_item_list_get_view(app->variable_item_list));
+
+    //End Scene Additions
+
+    return app;
+}
+
+void boilerplate_app_free(Boilerplate* app) {
+    furi_assert(app);
+    
+    // Scene manager
+    scene_manager_free(app->scene_manager);
+
+    // View Dispatcher
+    view_dispatcher_remove_view(app->view_dispatcher, BoilerplateViewIdMenu);
+    view_dispatcher_remove_view(app->view_dispatcher, BoilerplateViewIdScene1);
+    view_dispatcher_remove_view(app->view_dispatcher, BoilerplateViewIdScene2);
+    view_dispatcher_remove_view(app->view_dispatcher, BoilerplateViewIdSettings);
+    submenu_free(app->submenu);
+
+    view_dispatcher_free(app->view_dispatcher);
+
+    if(app->camera_ram_sav && storage_file_is_open(app->camera_ram_sav)) {
+        storage_file_close(app->camera_ram_sav);
+        storage_file_free(app->camera_ram_sav);
+    }
+
+
+    furi_record_close(RECORD_GUI);
+    furi_record_close(RECORD_STORAGE);
+
+
+    
+    app->gui = NULL;
+    app->notification = NULL;
+    app->storage = NULL;
+
+    // Close File Browser
+    furi_record_close(RECORD_DIALOGS);
+    furi_string_free(app->file_path);
+
+    //Remove whatever is left
+    free(app);
+}
+
+int32_t boilerplate_app(void* p) {
+    UNUSED(p);
+    Boilerplate* app = boilerplate_app_alloc();
+    
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+    
+    scene_manager_next_scene(app->scene_manager, BoilerplateSceneStartscreen); //Start with start screen
+
+    furi_hal_power_suppress_charge_enter();
+
+    view_dispatcher_run(app->view_dispatcher);
+
+    furi_hal_power_suppress_charge_exit();
+    boilerplate_app_free(app);
+
+    return 0;
+}
+
+
+

+ 147 - 0
malveke_gb_photo.h

@@ -0,0 +1,147 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <malveke_gb_photo_icons.h>
+#include <dialogs/dialogs.h>
+#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/dialog_ex.h>
+#include <storage/storage.h>
+#include "scenes/boilerplate_scene.h"
+#include "views/boilerplate_startscreen.h"
+#include "views/boilerplate_scene_1.h"
+#include "views/boilerplate_scene_2.h"
+
+
+#include <string.h>
+
+
+#define TAG "GBPHOTO"
+
+#define MALVEKE_APP_RAM_EXTENSION ".sav"
+
+#define MALVEKE_APP_FOLDER_USER "apps_data/malveke"
+#define MALVEKE_APP_FOLDER EXT_PATH(MALVEKE_APP_FOLDER_USER)
+#define MALVEKE_APP_FOLDER_RAMS MALVEKE_APP_FOLDER "/rams"
+#define MALVEKE_APP_FOLDER_PHOTOS MALVEKE_APP_FOLDER "/photos"
+
+
+
+typedef struct {
+    Gui* gui;
+    NotificationApp* notification;
+    Storage* storage;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+    SceneManager* scene_manager;
+    VariableItemList* variable_item_list;
+    BoilerplateStartscreen* boilerplate_startscreen;
+    BoilerplateScene1* boilerplate_scene_1;
+    BoilerplateScene2* boilerplate_scene_2;
+    DialogsApp* dialogs; // File Browser
+    FuriString* file_path; // File Browser
+    uint32_t palette;
+    uint32_t info;
+    File* camera_ram_sav;
+    int page;
+    int pos_x;
+    int pos_y;
+    bool show_instructions;
+    uint8_t tile_data[16];
+    uint8_t scratchpad1[0x11FC]; // 0000 - 11fb
+
+
+    unsigned long palette_color_hex_a;
+    unsigned long palette_color_hex_b;
+    unsigned long palette_color_hex_c;
+    unsigned long palette_color_hex_d;
+
+} Boilerplate;
+
+typedef enum {
+    WorkerEvtStop = (1 << 0),
+    WorkerEvtRxDone = (1 << 1),
+} WorkerEvtFlags;
+
+typedef enum {
+    BoilerplateViewIdStartscreen,
+    BoilerplateViewIdMenu,
+    BoilerplateViewIdScene1,
+    BoilerplateViewIdScene2,
+    // BoilerplateViewIdScene3,
+    // BoilerplateViewIdScene4,
+    // BoilerplateViewIdScene5,
+    BoilerplateViewIdSettings,
+} BoilerplateViewId;
+
+typedef enum {
+    BoilerplatePaletteBlackAndWhite,
+    BoilerplatePaletteOriginal,
+    BoilerplatePaletteSplashUp,
+    BoilerplatePaletteGBLight,
+    BoilerplatePalettePocket,
+    BoilerplatePaletteAudiQuattroPikesPeak,
+    BoilerplatePaletteAzureClouds,
+    BoilerplatePaletteTheresalwaysmoney,
+    BoilerplatePaletteBGBEmulator,
+    BoilerplatePaletteGameBoyBlackZeropalette,
+    BoilerplatePaletteCandyCottonTowerRaid,
+    BoilerplatePaletteCaramelFudgeParanoia,
+    BoilerplatePaletteCGAPaletteCrush1,
+    BoilerplatePaletteCGAPaletteCrush2,
+    BoilerplatePaletteChildhoodinGreenland,
+    BoilerplatePaletteCMYKeystone,
+    BoilerplatePaletteCyanideBlues,
+    BoilerplatePaletteDune2000remastered,
+    BoilerplatePaletteDrowningatnight,
+    BoilerplatePaletteDeepHazeGreen,
+    BoilerplatePaletteDiesistmeineWassermelone,
+    BoilerplatePaletteFlowerfeldstrabe,
+    BoilerplatePaletteFloydSteinberginLove,
+    BoilerplatePaletteGameBoyColorSplashDown,
+    BoilerplatePaletteGameBoyColorSplashDownA,
+    BoilerplatePaletteGameBoyColorSplashDownB,
+    BoilerplatePaletteGameBoyColorSplashRightAGameBoyCamera,
+    BoilerplatePaletteGameBoyColorSplashLeft,
+    BoilerplatePaletteGameBoyColorSplashLeftA,
+    BoilerplatePaletteGameBoyColorSplashLeftB,
+    BoilerplatePaletteGameBoyColorSplashRight,
+    BoilerplatePaletteGameBoyColorSplashRightB,
+    BoilerplatePaletteGameBoyColorSplashUpA,
+    BoilerplatePaletteGameBoyColorSplashUpB,
+    BoilerplatePaletteGoldenElephantCurry,
+    BoilerplatePaletteGlowingMountains,
+    BoilerplatePaletteGrafixkidGray,
+    BoilerplatePaletteGrafixkidGreen,
+    BoilerplatePaletteArtisticCaffeinatedLactose,
+    BoilerplatePaletteKneeDeepintheWood,
+    BoilerplatePaletteLinkslateAwakening,
+    BoilerplatePaletteMetroidAranremixed,
+    BoilerplatePaletteNortoriousComandante,
+    BoilerplatePalettePurpleRain,
+    BoilerplatePaletteRustedCitySign,
+    BoilerplatePaletteRomerosGarden,
+    BoilerplatePaletteSunflowerHolidays,
+    BoilerplatePaletteSuperHyperMegaGameboy,
+    BoilerplatePaletteSpaceHazeOverload,
+    BoilerplatePaletteStarlitMemories,
+    BoilerplatePaletteMyFriendfromBavaria,
+    BoilerplatePaletteThedeathofYungColumbus,
+    BoilerplatePaletteTramontoalParcodegliAcquedotti,
+    BoilerplatePaletteThestarryknight,
+    BoilerplatePaletteVirtualBoy1985,
+    BoilerplatePaletteWaterfrontPlaza,
+    BoilerplatePaletteYouthIkarusreloaded
+} BoilerplatePaletteState;
+
+typedef enum {
+    BoilerplateInfoOff,
+    BoilerplateInfoOn,
+} BoilerplateInfoState;

+ 30 - 0
scenes/boilerplate_scene.c

@@ -0,0 +1,30 @@
+#include "boilerplate_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const boilerplate_on_enter_handlers[])(void*) = {
+#include "boilerplate_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 boilerplate_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "boilerplate_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 boilerplate_on_exit_handlers[])(void* context) = {
+#include "boilerplate_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers boilerplate_scene_handlers = {
+    .on_enter_handlers = boilerplate_on_enter_handlers,
+    .on_event_handlers = boilerplate_on_event_handlers,
+    .on_exit_handlers = boilerplate_on_exit_handlers,
+    .scene_num = BoilerplateSceneNum,
+};

+ 29 - 0
scenes/boilerplate_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) BoilerplateScene##id,
+typedef enum {
+#include "boilerplate_scene_config.h"
+    BoilerplateSceneNum,
+} BoilerplateScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers boilerplate_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "boilerplate_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 "boilerplate_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 "boilerplate_scene_config.h"
+#undef ADD_SCENE

+ 8 - 0
scenes/boilerplate_scene_config.h

@@ -0,0 +1,8 @@
+ADD_SCENE(boilerplate, startscreen, Startscreen)
+ADD_SCENE(boilerplate, fileselect, Fileselect)
+ADD_SCENE(boilerplate, menu, Menu)
+ADD_SCENE(boilerplate, scene_1, Scene_1)
+ADD_SCENE(boilerplate, scene_2, Scene_2)
+// ADD_SCENE(boilerplate, scene_3, Scene_3)
+// ADD_SCENE(boilerplate, scene_4, Scene_4)
+ADD_SCENE(boilerplate, settings, Settings)

+ 71 - 0
scenes/boilerplate_scene_menu.c

@@ -0,0 +1,71 @@
+#include "../malveke_gb_photo.h"
+
+enum SubmenuIndex {
+    SubmenuIndexScene1 = 10,
+    SubmenuIndexScene2,
+    SubmenuIndexScene3,
+    SubmenuIndexScene4,
+    SubmenuIndexScene5,
+    SubmenuIndexSettings,
+};
+
+void boilerplate_scene_menu_submenu_callback(void* context, uint32_t index) {
+    Boilerplate* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void boilerplate_scene_menu_on_enter(void* context) {
+    Boilerplate* app = context;
+
+    submenu_add_item(app->submenu, "Photo Viewer", SubmenuIndexScene1, boilerplate_scene_menu_submenu_callback, app);
+    submenu_add_item(app->submenu, "Export All", SubmenuIndexScene2, boilerplate_scene_menu_submenu_callback, app);
+    // submenu_add_item(app->submenu, "Scene 3 (Buttonmenu)", SubmenuIndexScene3, boilerplate_scene_menu_submenu_callback, app);
+    // submenu_add_item(app->submenu, "Scene 4 (File Browser)", SubmenuIndexScene4, boilerplate_scene_menu_submenu_callback, app);
+    submenu_add_item(app->submenu, "Settings", SubmenuIndexSettings, boilerplate_scene_menu_submenu_callback, app);
+
+    submenu_set_selected_item(app->submenu, scene_manager_get_scene_state(app->scene_manager, BoilerplateSceneMenu));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, BoilerplateViewIdMenu);
+}
+
+bool boilerplate_scene_menu_on_event(void* context, SceneManagerEvent event) {
+    Boilerplate* app = context;
+    UNUSED(app);
+    if(event.type == SceneManagerEventTypeBack) {
+        //exit app
+        scene_manager_stop(app->scene_manager);
+        view_dispatcher_stop(app->view_dispatcher);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexScene1) {
+            scene_manager_set_scene_state(
+                app->scene_manager, BoilerplateSceneMenu, SubmenuIndexScene1);
+            scene_manager_next_scene(app->scene_manager, BoilerplateSceneScene_1);
+            return true;
+        } else if (event.event == SubmenuIndexScene2) {
+            scene_manager_set_scene_state(
+                app->scene_manager, BoilerplateSceneMenu, SubmenuIndexScene2);
+            scene_manager_next_scene(app->scene_manager, BoilerplateSceneScene_2);
+            return true;
+        // } else if (event.event == SubmenuIndexScene3) {
+        //     scene_manager_set_scene_state(
+        //         app->scene_manager, BoilerplateSceneMenu, SubmenuIndexScene3);
+        //     scene_manager_next_scene(app->scene_manager, BoilerplateSceneScene_3);
+        // } else if (event.event == SubmenuIndexScene4) {
+        //     scene_manager_set_scene_state(
+        //         app->scene_manager, BoilerplateSceneMenu, SubmenuIndexScene4);
+        //     scene_manager_next_scene(app->scene_manager, BoilerplateSceneScene_4);
+        } else if (event.event == SubmenuIndexSettings) {
+            scene_manager_set_scene_state(
+                app->scene_manager, BoilerplateSceneMenu, SubmenuIndexSettings);
+            scene_manager_next_scene(app->scene_manager, BoilerplateSceneSettings);
+            return true;
+        }
+    }
+    return false;
+}
+
+void boilerplate_scene_menu_on_exit(void* context) {
+    Boilerplate* app = context;
+    submenu_reset(app->submenu);
+}

+ 50 - 0
scenes/boilerplate_scene_scene_1.c

@@ -0,0 +1,50 @@
+#include "../malveke_gb_photo.h"
+#include "../helpers/boilerplate_custom_event.h"
+#include "../views/boilerplate_scene_1.h"
+
+void boilerplate_scene_1_callback(BoilerplateCustomEvent event, void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void boilerplate_scene_scene_1_on_enter(void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    boilerplate_scene_1_set_callback(app->boilerplate_scene_1, boilerplate_scene_1_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, BoilerplateViewIdScene1);
+}
+
+bool boilerplate_scene_scene_1_on_event(void* context, SceneManagerEvent event) {
+    Boilerplate* app = context;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+            case BoilerplateCustomEventScene1Left:
+            case BoilerplateCustomEventScene1Right:
+                break;
+            case BoilerplateCustomEventScene1Up:
+            case BoilerplateCustomEventScene1Down:
+                break;
+            case BoilerplateCustomEventScene1Back:
+                notification_message(app->notification, &sequence_reset_red);
+                notification_message(app->notification, &sequence_reset_green);
+                notification_message(app->notification, &sequence_reset_blue);
+                if(!scene_manager_search_and_switch_to_previous_scene(
+                    app->scene_manager, BoilerplateSceneMenu)) {
+                        scene_manager_stop(app->scene_manager);
+                        view_dispatcher_stop(app->view_dispatcher);
+                    }
+                consumed = true;
+                break;
+        }
+    }
+    
+    return consumed;
+}
+
+void boilerplate_scene_scene_1_on_exit(void* context) {
+    Boilerplate* app = context;
+    UNUSED(app);
+}

+ 51 - 0
scenes/boilerplate_scene_scene_2.c

@@ -0,0 +1,51 @@
+#include "../malveke_gb_photo.h"
+#include "../helpers/boilerplate_custom_event.h"
+#include "../views/boilerplate_scene_2.h"
+
+void boilerplate_scene_2_callback(BoilerplateCustomEvent event, void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void boilerplate_scene_scene_2_on_enter(void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    boilerplate_scene_2_set_callback(app->boilerplate_scene_2, boilerplate_scene_2_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, BoilerplateViewIdScene2);
+}
+
+bool boilerplate_scene_scene_2_on_event(void* context, SceneManagerEvent event) {
+    Boilerplate* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+            case BoilerplateCustomEventScene2Left:
+            case BoilerplateCustomEventScene2Right:
+                break;
+            case BoilerplateCustomEventScene2Up:
+            case BoilerplateCustomEventScene2Down:
+                break;
+            case BoilerplateCustomEventScene2Back:
+                notification_message(app->notification, &sequence_reset_red);
+                notification_message(app->notification, &sequence_reset_green);
+                notification_message(app->notification, &sequence_reset_blue);
+                if(!scene_manager_search_and_switch_to_previous_scene(
+                    app->scene_manager, BoilerplateSceneMenu)) {
+                        scene_manager_stop(app->scene_manager);
+                        view_dispatcher_stop(app->view_dispatcher);
+                    }
+                consumed = true;
+                break;
+        }
+    }
+
+    return consumed;
+}
+
+void boilerplate_scene_scene_2_on_exit(void* context) {
+    Boilerplate* app = context;
+    UNUSED(app);
+}
+

+ 595 - 0
scenes/boilerplate_scene_settings.c

@@ -0,0 +1,595 @@
+#include "../malveke_gb_photo.h"
+#include <lib/toolbox/value_index.h>
+
+enum SettingsIndex
+{
+    SettingsIndexHaptic = 10,
+    SettingsIndexValue1,
+    SettingsIndexValue2,
+};
+
+const char *const palette_text[57] = {
+    "B&W",
+    "Original",
+    "Splash Up",
+    "GB Light",
+    "Pocket",
+    "aqpp", "azc", "banana", "bgb", "blackzero", "cctr", "cfp", "cga1", "cga2", "chig", "cmyk", "cybl", "d2kr", "datn", "dhg", "dimwm", "ffs", "fsil", "gbcd", "gbcda", "gbcdb", "gbceuus", "gbcl", "gbcla", "gbclb", "gbcr", "gbcrb", "gbcua", "gbcub", "gelc", "glmo", "grafixkidgray", "grafixkidgreen", "hipster", "kditw", "llawk", "marmx", "nc", "ppr", "rcs", "roga", "sfh", "shmgy", "shzol", "slmem", "spezi", "tdoyc", "tpa", "tsk", "vb85", "wtfp", "yirl"};
+const uint32_t palette_value[57] = {
+    BoilerplatePaletteBlackAndWhite,
+    BoilerplatePaletteOriginal,
+    BoilerplatePaletteSplashUp,
+    BoilerplatePaletteGBLight,
+    BoilerplatePalettePocket,
+    BoilerplatePaletteAudiQuattroPikesPeak,
+    BoilerplatePaletteAzureClouds,
+    BoilerplatePaletteTheresalwaysmoney,
+    BoilerplatePaletteBGBEmulator,
+    BoilerplatePaletteGameBoyBlackZeropalette,
+    BoilerplatePaletteCandyCottonTowerRaid,
+    BoilerplatePaletteCaramelFudgeParanoia,
+    BoilerplatePaletteCGAPaletteCrush1,
+    BoilerplatePaletteCGAPaletteCrush2,
+    BoilerplatePaletteChildhoodinGreenland,
+    BoilerplatePaletteCMYKeystone,
+    BoilerplatePaletteCyanideBlues,
+    BoilerplatePaletteDune2000remastered,
+    BoilerplatePaletteDrowningatnight,
+    BoilerplatePaletteDeepHazeGreen,
+    BoilerplatePaletteDiesistmeineWassermelone,
+    BoilerplatePaletteFlowerfeldstrabe,
+    BoilerplatePaletteFloydSteinberginLove,
+    BoilerplatePaletteGameBoyColorSplashDown,
+    BoilerplatePaletteGameBoyColorSplashDownA,
+    BoilerplatePaletteGameBoyColorSplashDownB,
+    BoilerplatePaletteGameBoyColorSplashRightAGameBoyCamera,
+    BoilerplatePaletteGameBoyColorSplashLeft,
+    BoilerplatePaletteGameBoyColorSplashLeftA,
+    BoilerplatePaletteGameBoyColorSplashLeftB,
+    BoilerplatePaletteGameBoyColorSplashRight,
+    BoilerplatePaletteGameBoyColorSplashRightB,
+    BoilerplatePaletteGameBoyColorSplashUpA,
+    BoilerplatePaletteGameBoyColorSplashUpB,
+    BoilerplatePaletteGoldenElephantCurry,
+    BoilerplatePaletteGlowingMountains,
+    BoilerplatePaletteGrafixkidGray,
+    BoilerplatePaletteGrafixkidGreen,
+    BoilerplatePaletteArtisticCaffeinatedLactose,
+    BoilerplatePaletteKneeDeepintheWood,
+    BoilerplatePaletteLinkslateAwakening,
+    BoilerplatePaletteMetroidAranremixed,
+    BoilerplatePaletteNortoriousComandante,
+    BoilerplatePalettePurpleRain,
+    BoilerplatePaletteRustedCitySign,
+    BoilerplatePaletteRomerosGarden,
+    BoilerplatePaletteSunflowerHolidays,
+    BoilerplatePaletteSuperHyperMegaGameboy,
+    BoilerplatePaletteSpaceHazeOverload,
+    BoilerplatePaletteStarlitMemories,
+    BoilerplatePaletteMyFriendfromBavaria,
+    BoilerplatePaletteThedeathofYungColumbus,
+    BoilerplatePaletteTramontoalParcodegliAcquedotti,
+    BoilerplatePaletteThestarryknight,
+    BoilerplatePaletteVirtualBoy1985,
+    BoilerplatePaletteWaterfrontPlaza,
+    BoilerplatePaletteYouthIkarusreloaded
+};
+
+const char *const info_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t info_value[2] = {
+    BoilerplateInfoOff,
+    BoilerplateInfoOn,
+};
+
+
+static void boilerplate_scene_settings_set_palette(VariableItem *item)
+{
+    Boilerplate *app = variable_item_get_context(item);
+    UNUSED(app);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, palette_text[index]);
+    app->palette = palette_value[index];
+    if (app->palette == BoilerplatePaletteBlackAndWhite)
+    {
+        app->palette_color_hex_a = 0xFFFFFF;
+        app->palette_color_hex_b = 0xAAAAAA;
+        app->palette_color_hex_c = 0x555555;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteOriginal)
+    {
+        app->palette_color_hex_a = 0x9bbc0f;
+        app->palette_color_hex_b = 0x77a112;
+        app->palette_color_hex_c = 0x306230;
+        app->palette_color_hex_d = 0x0f380f;
+    }
+    else if (app->palette == BoilerplatePaletteSplashUp)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0xffad63;
+        app->palette_color_hex_c = 0x833100;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGBLight)
+    {
+        app->palette_color_hex_a = 0x1ddece;
+        app->palette_color_hex_b = 0x19c7b3;
+        app->palette_color_hex_c = 0x16a596;
+        app->palette_color_hex_d = 0x0b7a6d;
+    }
+    else if (app->palette == BoilerplatePalettePocket)
+    {
+        app->palette_color_hex_a = 0xc4cfa1;
+        app->palette_color_hex_b = 0x8b956d;
+        app->palette_color_hex_c = 0x4d533c;
+        app->palette_color_hex_d = 0x1f1f1f;
+    }
+    else if (app->palette == BoilerplatePaletteAudiQuattroPikesPeak)
+    {
+        app->palette_color_hex_a = 0xebeee7;
+        app->palette_color_hex_b = 0x868779;
+        app->palette_color_hex_c = 0xfa2b25;
+        app->palette_color_hex_d = 0x2a201e;
+    }
+    else if (app->palette == BoilerplatePaletteAzureClouds)
+    {
+        app->palette_color_hex_a = 0x47ff99;
+        app->palette_color_hex_b = 0x32b66d;
+        app->palette_color_hex_c = 0x124127;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteTheresalwaysmoney)
+    {
+        app->palette_color_hex_a = 0xfdfe0a;
+        app->palette_color_hex_b = 0xfed638;
+        app->palette_color_hex_c = 0x977b25;
+        app->palette_color_hex_d = 0x221a09;
+    }
+    else if (app->palette == BoilerplatePaletteBGBEmulator)
+    {
+        app->palette_color_hex_a = 0xe0f8d0;
+        app->palette_color_hex_b = 0x88c070;
+        app->palette_color_hex_c = 0x346856;
+        app->palette_color_hex_d = 0x081820;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyBlackZeropalette)
+    {
+        app->palette_color_hex_a = 0x7e8416;
+        app->palette_color_hex_b = 0x577b46;
+        app->palette_color_hex_c = 0x385d49;
+        app->palette_color_hex_d = 0x2e463d;
+    }
+    else if (app->palette == BoilerplatePaletteCandyCottonTowerRaid)
+    {
+        app->palette_color_hex_a = 0xe6aec4;
+        app->palette_color_hex_b = 0xe65790;
+        app->palette_color_hex_c = 0x8f0039;
+        app->palette_color_hex_d = 0x380016;
+    }
+    else if (app->palette == BoilerplatePaletteCaramelFudgeParanoia)
+    {
+        app->palette_color_hex_a = 0xcf9255;
+        app->palette_color_hex_b = 0xcf7163;
+        app->palette_color_hex_c = 0xb01553;
+        app->palette_color_hex_d = 0x3f1711;
+    }
+    else if (app->palette == BoilerplatePaletteCGAPaletteCrush1)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0x55ffff;
+        app->palette_color_hex_c = 0xff55ff;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteCGAPaletteCrush2)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0x55ffff;
+        app->palette_color_hex_c = 0xff5555;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteChildhoodinGreenland)
+    {
+        app->palette_color_hex_a = 0xd0d058;
+        app->palette_color_hex_b = 0xa0a840;
+        app->palette_color_hex_c = 0x708028;
+        app->palette_color_hex_d = 0x405010;
+    }
+    else if (app->palette == BoilerplatePaletteCMYKeystone)
+    {
+        app->palette_color_hex_a = 0xffff00;
+        app->palette_color_hex_b = 0x0be8fd;
+        app->palette_color_hex_c = 0xfb00fa;
+        app->palette_color_hex_d = 0x373737;
+    }
+    else if (app->palette == BoilerplatePaletteCyanideBlues)
+    {
+        app->palette_color_hex_a = 0x9efbe3;
+        app->palette_color_hex_b = 0x21aff5;
+        app->palette_color_hex_c = 0x1e4793;
+        app->palette_color_hex_d = 0x0e1e3d;
+    }
+    else if (app->palette == BoilerplatePaletteDune2000remastered)
+    {
+        app->palette_color_hex_a = 0xfbf1cd;
+        app->palette_color_hex_b = 0xc09e7d;
+        app->palette_color_hex_c = 0x725441;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteDrowningatnight)
+    {
+        app->palette_color_hex_a = 0xa9b0b3;
+        app->palette_color_hex_b = 0x586164;
+        app->palette_color_hex_c = 0x20293f;
+        app->palette_color_hex_d = 0x030c22;
+    }
+    else if (app->palette == BoilerplatePaletteDeepHazeGreen)
+    {
+        app->palette_color_hex_a = 0xa1d909;
+        app->palette_color_hex_b = 0x467818;
+        app->palette_color_hex_c = 0x27421f;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteDiesistmeineWassermelone)
+    {
+        app->palette_color_hex_a = 0xffdbcb;
+        app->palette_color_hex_b = 0xf27d7a;
+        app->palette_color_hex_c = 0x558429;
+        app->palette_color_hex_d = 0x222903;
+    }
+    else if (app->palette == BoilerplatePaletteFlowerfeldstrabe)
+    {
+        app->palette_color_hex_a = 0xe9d9cc;
+        app->palette_color_hex_b = 0xc5c5ce;
+        app->palette_color_hex_c = 0x75868f;
+        app->palette_color_hex_d = 0x171f62;
+    }
+    else if (app->palette == BoilerplatePaletteFloydSteinberginLove)
+    {
+        app->palette_color_hex_a = 0xeaf5fa;
+        app->palette_color_hex_b = 0x5fb1f5;
+        app->palette_color_hex_c = 0xd23c4e;
+        app->palette_color_hex_d = 0x4c1c2d;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashDown)
+    {
+        app->palette_color_hex_a = 0xffffa5;
+        app->palette_color_hex_b = 0xfe9494;
+        app->palette_color_hex_c = 0x9394fe;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashDownA)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0xffff00;
+        app->palette_color_hex_c = 0xfe0000;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashDownB)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0xffff00;
+        app->palette_color_hex_c = 0x7d4900;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashRightAGameBoyCamera)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0x7bff30;
+        app->palette_color_hex_c = 0x0163c6;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashLeft)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0x65a49b;
+        app->palette_color_hex_c = 0x0000fe;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashLeftA)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0x8b8cde;
+        app->palette_color_hex_c = 0x53528c;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashLeftB)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0xa5a5a5;
+        app->palette_color_hex_c = 0x525252;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashRight)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0x51ff00;
+        app->palette_color_hex_c = 0xff4200;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashRightB)
+    {
+        app->palette_color_hex_a = 0x000000;
+        app->palette_color_hex_b = 0x008486;
+        app->palette_color_hex_c = 0xffde00;
+        app->palette_color_hex_d = 0xffffff;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashUpA)
+    {
+        app->palette_color_hex_a = 0xffffff;
+        app->palette_color_hex_b = 0xff8f84;
+        app->palette_color_hex_c = 0x943a3a;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGameBoyColorSplashUpB)
+    {
+        app->palette_color_hex_a = 0xffe7c5;
+        app->palette_color_hex_b = 0xce9c85;
+        app->palette_color_hex_c = 0x846b29;
+        app->palette_color_hex_d = 0x5b3109;
+    }
+    else if (app->palette == BoilerplatePaletteGoldenElephantCurry)
+    {
+        app->palette_color_hex_a = 0xff9c00;
+        app->palette_color_hex_b = 0xc27600;
+        app->palette_color_hex_c = 0x4f3000;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteGlowingMountains)
+    {
+        app->palette_color_hex_a = 0xffbf98;
+        app->palette_color_hex_b = 0xa1a8b8;
+        app->palette_color_hex_c = 0x514f6c;
+        app->palette_color_hex_d = 0x2f1c35;
+    }
+    else if (app->palette == BoilerplatePaletteGrafixkidGray)
+    {
+        app->palette_color_hex_a = 0xe0dbcd;
+        app->palette_color_hex_b = 0xa89f94;
+        app->palette_color_hex_c = 0x706b66;
+        app->palette_color_hex_d = 0x2b2b26;
+    }
+    else if (app->palette == BoilerplatePaletteGrafixkidGreen)
+    {
+        app->palette_color_hex_a = 0xdbf4b4;
+        app->palette_color_hex_b = 0xabc396;
+        app->palette_color_hex_c = 0x7b9278;
+        app->palette_color_hex_d = 0x4c625a;
+    }
+    else if (app->palette == BoilerplatePaletteArtisticCaffeinatedLactose)
+    {
+        app->palette_color_hex_a = 0xfdfef5;
+        app->palette_color_hex_b = 0xdea963;
+        app->palette_color_hex_c = 0x9e754f;
+        app->palette_color_hex_d = 0x241606;
+    }
+    else if (app->palette == BoilerplatePaletteKneeDeepintheWood)
+    {
+        app->palette_color_hex_a = 0xfffe6e;
+        app->palette_color_hex_b = 0xd5690f;
+        app->palette_color_hex_c = 0x3c3ca9;
+        app->palette_color_hex_d = 0x2c2410;
+    }
+    else if (app->palette == BoilerplatePaletteLinkslateAwakening)
+    {
+        app->palette_color_hex_a = 0xffffb5;
+        app->palette_color_hex_b = 0x7bc67b;
+        app->palette_color_hex_c = 0x6b8c42;
+        app->palette_color_hex_d = 0x5a3921;
+    }
+    else if (app->palette == BoilerplatePaletteMetroidAranremixed)
+    {
+        app->palette_color_hex_a = 0xaedf1e;
+        app->palette_color_hex_b = 0x047e60;
+        app->palette_color_hex_c = 0xb62558;
+        app->palette_color_hex_d = 0x2c1700;
+    }
+    else if (app->palette == BoilerplatePaletteNortoriousComandante)
+    {
+        app->palette_color_hex_a = 0xfcfe54;
+        app->palette_color_hex_b = 0x54fefc;
+        app->palette_color_hex_c = 0x04aaac;
+        app->palette_color_hex_d = 0x0402ac;
+    }
+    else if (app->palette == BoilerplatePalettePurpleRain)
+    {
+        app->palette_color_hex_a = 0xadfffc;
+        app->palette_color_hex_b = 0x8570b2;
+        app->palette_color_hex_c = 0xff0084;
+        app->palette_color_hex_d = 0x68006a;
+    }
+    else if (app->palette == BoilerplatePaletteRustedCitySign)
+    {
+        app->palette_color_hex_a = 0xedb4a1;
+        app->palette_color_hex_b = 0xa96868;
+        app->palette_color_hex_c = 0x764462;
+        app->palette_color_hex_d = 0x2c2137;
+    }
+    else if (app->palette == BoilerplatePaletteRomerosGarden)
+    {
+        app->palette_color_hex_a = 0xebc4ab;
+        app->palette_color_hex_b = 0x649a57;
+        app->palette_color_hex_c = 0x574431;
+        app->palette_color_hex_d = 0x323727;
+    }
+    else if (app->palette == BoilerplatePaletteSunflowerHolidays)
+    {
+        app->palette_color_hex_a = 0xffff55;
+        app->palette_color_hex_b = 0xff5555;
+        app->palette_color_hex_c = 0x881400;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteSuperHyperMegaGameboy)
+    {
+        app->palette_color_hex_a = 0xf7e7c6;
+        app->palette_color_hex_b = 0xd68e49;
+        app->palette_color_hex_c = 0xa63725;
+        app->palette_color_hex_d = 0x331e50;
+    }
+    else if (app->palette == BoilerplatePaletteSpaceHazeOverload)
+    {
+        app->palette_color_hex_a = 0xf8e3c4;
+        app->palette_color_hex_b = 0xcc3495;
+        app->palette_color_hex_c = 0x6b1fb1;
+        app->palette_color_hex_d = 0x0b0630;
+    }
+    else if (app->palette == BoilerplatePaletteStarlitMemories)
+    {
+        app->palette_color_hex_a = 0x869ad9;
+        app->palette_color_hex_b = 0x6d53bd;
+        app->palette_color_hex_c = 0x6f2096;
+        app->palette_color_hex_d = 0x4f133f;
+    }
+    else if (app->palette == BoilerplatePaletteMyFriendfromBavaria)
+    {
+        app->palette_color_hex_a = 0xfeda1b;
+        app->palette_color_hex_b = 0xdf7925;
+        app->palette_color_hex_c = 0xb60077;
+        app->palette_color_hex_d = 0x382977;
+    }
+    else if (app->palette == BoilerplatePaletteThedeathofYungColumbus)
+    {
+        app->palette_color_hex_a = 0xb5ff32;
+        app->palette_color_hex_b = 0xff2261;
+        app->palette_color_hex_c = 0x462917;
+        app->palette_color_hex_d = 0x1d1414;
+    }
+    else if (app->palette == BoilerplatePaletteTramontoalParcodegliAcquedotti)
+    {
+        app->palette_color_hex_a = 0xf3c677;
+        app->palette_color_hex_b = 0xe64a4e;
+        app->palette_color_hex_c = 0x912978;
+        app->palette_color_hex_d = 0x0c0a3e;
+    }
+    else if (app->palette == BoilerplatePaletteThestarryknight)
+    {
+        app->palette_color_hex_a = 0xf5db37;
+        app->palette_color_hex_b = 0x37cae5;
+        app->palette_color_hex_c = 0x0f86b6;
+        app->palette_color_hex_d = 0x123f77;
+    }
+    else if (app->palette == BoilerplatePaletteVirtualBoy1985)
+    {
+        app->palette_color_hex_a = 0xff0000;
+        app->palette_color_hex_b = 0xdb0000;
+        app->palette_color_hex_c = 0x520000;
+        app->palette_color_hex_d = 0x000000;
+    }
+    else if (app->palette == BoilerplatePaletteWaterfrontPlaza)
+    {
+        app->palette_color_hex_a = 0xcecece;
+        app->palette_color_hex_b = 0x6f9edf;
+        app->palette_color_hex_c = 0x42678e;
+        app->palette_color_hex_d = 0x102533;
+    }
+    else if (app->palette == BoilerplatePaletteYouthIkarusreloaded)
+    {
+        app->palette_color_hex_a = 0xcef7f7;
+        app->palette_color_hex_b = 0xf78e50;
+        app->palette_color_hex_c = 0x9e0000;
+        app->palette_color_hex_d = 0x1e0000;
+    }
+}
+
+    static void boilerplate_scene_settings_set_info(VariableItem * item)
+    {
+        Boilerplate *app = variable_item_get_context(item);
+        UNUSED(app);
+        uint8_t index = variable_item_get_current_value_index(item);
+        variable_item_set_current_value_text(item, info_text[index]);
+        app->info = info_value[index];
+    }
+
+    // static void boilerplate_scene_settings_set_led(VariableItem* item) {
+    //     Boilerplate* app = variable_item_get_context(item);
+    //     uint8_t index = variable_item_get_current_value_index(item);
+    //     variable_item_set_current_value_text(item, led_text[index]);
+    //     app->led = led_value[index];
+    // }
+
+    // static void boilerplate_scene_settings_set_save_settings(VariableItem* item) {
+    //     Boilerplate* app = variable_item_get_context(item);
+    //     uint8_t index = variable_item_get_current_value_index(item);
+    //     variable_item_set_current_value_text(item, settings_text[index]);
+    //     app->save_settings = settings_value[index];
+    // }
+
+    void boilerplate_scene_settings_submenu_callback(void *context, uint32_t index)
+    {
+        Boilerplate *app = context;
+        view_dispatcher_send_custom_event(app->view_dispatcher, index);
+    }
+
+    void boilerplate_scene_settings_on_enter(void *context)
+    {
+        Boilerplate *app = context;
+        VariableItem *item;
+        uint8_t value_index;
+
+        // Palette
+        item = variable_item_list_add(
+            app->variable_item_list,
+            "Palette:",
+            57,
+            boilerplate_scene_settings_set_palette,
+            app);
+        value_index = value_index_uint32(app->palette, palette_value, 1);
+        variable_item_set_current_value_index(item, value_index);
+        variable_item_set_current_value_text(item, palette_text[value_index]);
+
+        // Info
+        item = variable_item_list_add(
+            app->variable_item_list,
+            "Info:",
+            2,
+            boilerplate_scene_settings_set_info,
+            app);
+        value_index = value_index_uint32(app->info, info_value, 2);
+        variable_item_set_current_value_index(item, value_index);
+        variable_item_set_current_value_text(item, info_text[value_index]);
+
+        // // LED Effects on/off
+        // item = variable_item_list_add(
+        //     app->variable_item_list,
+        //     "Save Prefix",
+        //     2,
+        //     boilerplate_scene_settings_set_led,
+        //     app);
+        // value_index = value_index_uint32(app->led, led_value, 2);
+        // variable_item_set_current_value_index(item, value_index);
+        // variable_item_set_current_value_text(item, led_text[value_index]);
+
+        // // Save Settings to File
+        // item = variable_item_list_add(
+        //     app->variable_item_list,
+        //     "Save Settings",
+        //     2,
+        //     boilerplate_scene_settings_set_save_settings,
+        //     app);
+        // value_index = value_index_uint32(app->save_settings, settings_value, 2);
+        // variable_item_set_current_value_index(item, value_index);
+        // variable_item_set_current_value_text(item, settings_text[value_index]);
+
+        view_dispatcher_switch_to_view(app->view_dispatcher, BoilerplateViewIdSettings);
+    }
+
+    bool boilerplate_scene_settings_on_event(void *context, SceneManagerEvent event)
+    {
+        Boilerplate *app = context;
+        UNUSED(app);
+        bool consumed = false;
+        if (event.type == SceneManagerEventTypeCustom)
+        {
+        }
+        return consumed;
+    }
+
+    void boilerplate_scene_settings_on_exit(void *context)
+    {
+        Boilerplate *app = context;
+        variable_item_list_set_selected_item(app->variable_item_list, 0);
+        variable_item_list_reset(app->variable_item_list);
+    }

+ 54 - 0
scenes/boilerplate_scene_startscreen.c

@@ -0,0 +1,54 @@
+#include "../malveke_gb_photo.h"
+#include "../helpers/boilerplate_custom_event.h"
+#include "../views/boilerplate_startscreen.h"
+
+void boilerplate_scene_startscreen_callback(BoilerplateCustomEvent event, void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void boilerplate_scene_startscreen_on_enter(void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    boilerplate_startscreen_set_callback(app->boilerplate_startscreen, boilerplate_scene_startscreen_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, BoilerplateViewIdStartscreen);
+}
+
+bool boilerplate_scene_startscreen_on_event(void* context, SceneManagerEvent event) {
+    Boilerplate* app = context;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+            case BoilerplateCustomEventStartscreenLeft:
+            case BoilerplateCustomEventStartscreenRight:
+                break;
+            case BoilerplateCustomEventStartscreenUp:
+            case BoilerplateCustomEventStartscreenDown:
+                break;
+            case BoilerplateCustomEventStartscreenOk:
+                scene_manager_next_scene(app->scene_manager, BoilerplateSceneFileselect);
+                consumed = true;
+                break;
+            case BoilerplateCustomEventStartscreenBack:
+                notification_message(app->notification, &sequence_reset_red);
+                notification_message(app->notification, &sequence_reset_green);
+                notification_message(app->notification, &sequence_reset_blue);
+                if(!scene_manager_search_and_switch_to_previous_scene(
+                    app->scene_manager, BoilerplateSceneStartscreen)) {
+                        scene_manager_stop(app->scene_manager);
+                        view_dispatcher_stop(app->view_dispatcher);
+                    }
+                consumed = true;
+                break;
+        }
+    }
+    
+    return consumed;
+}
+
+void boilerplate_scene_startscreen_on_exit(void* context) {
+    Boilerplate* app = context;
+    UNUSED(app);
+}

+ 82 - 0
scenes/scene_file_select.c

@@ -0,0 +1,82 @@
+#include "../malveke_gb_photo.h"
+#include <storage/storage.h>
+
+// static int32_t game_boy_camera_worker(void* context) {
+//     Boilerplate* app = context;
+
+//     if(!storage_file_open(
+//                 app->camera_ram_sav,
+//                 furi_string_get_cstr(app->file_path),
+//                 FSAM_READ,
+//                 FSOM_OPEN_EXISTING)) {
+
+//                     return 1;
+//     }
+
+//     storage_file_read(app->camera_ram_sav, app->scratchpad1, sizeof(app->scratchpad1));
+//     scene_manager_next_scene(app->scene_manager, BoilerplateSceneMenu);
+
+//     return 0;
+
+// }
+
+static bool file_select(Boilerplate *app)
+{
+    furi_assert(app);
+
+    furi_string_set(app->file_path, MALVEKE_APP_FOLDER_RAMS);
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, MALVEKE_APP_RAM_EXTENSION, &I_icon_10px);
+    browser_options.base_path = MALVEKE_APP_FOLDER_RAMS;
+    browser_options.skip_assets = true;
+
+    // Input events and views are managed by file_browser
+    bool res = dialog_file_browser_show(
+        app->dialogs, app->file_path, app->file_path, &browser_options);
+    return res;
+}
+
+void boilerplate_scene_fileselect_on_enter(void *context)
+{
+    Boilerplate *app = context;
+
+    // if(app->script) {
+    //     script_close(app->script);
+    //     app->script = NULL;
+    // }
+    // if(app->camera_ram_sav && storage_file_is_open(app->camera_ram_sav)) {
+    //     storage_file_close(app->camera_ram_sav);
+    // }
+    // app->camera_ram_sav = storage_file_alloc(app->storage);
+    if (file_select(app))
+    {
+        if (storage_file_open(
+                app->camera_ram_sav,
+                furi_string_get_cstr(app->file_path),
+                FSAM_READ,
+                FSOM_OPEN_EXISTING))
+        {
+            storage_file_read(app->camera_ram_sav, app->scratchpad1, sizeof(app->scratchpad1));
+            scene_manager_next_scene(app->scene_manager, BoilerplateSceneMenu);
+        }
+    }
+    else
+    {
+        view_dispatcher_stop(app->view_dispatcher);
+    }
+}
+
+bool boilerplate_scene_fileselect_on_event(void *context, SceneManagerEvent event)
+{
+    UNUSED(context);
+    UNUSED(event);
+    // Boilerplate* app = context;
+    return false;
+}
+
+void boilerplate_scene_fileselect_on_exit(void *context)
+{
+    UNUSED(context);
+    // Boilerplate* app = context;
+}

+ 78 - 0
u8g2_font_5x7_mf.h

@@ -0,0 +1,78 @@
+#pragma once
+
+#ifndef U8G2_FONT_5X7_H
+#define U8G2_FONT_5X7_H
+
+#include <stdint.h>
+
+/*
+  Fontname: -Misc-Fixed-Medium-R-Normal--7-70-75-75-C-50-ISO10646-1
+  Copyright: Public domain font.  Share and enjoy.
+  Glyphs: 191/1848
+  BBX Build Mode: 2
+*/
+
+
+const uint8_t u8g2_font_5x7_mf[1911] =
+    "\277\2\3\2\3\3\1\1\4\5\7\0\377\6\377\6\0\1*\2r\7Z \5}}\36!\10}\255"
+    "\260\35\312\1\42\11}\235R\22%\71\3#\13}\355$\31\224\312\240\224\1$\11}\355\245\266%\311"
+    "\14%\11}\215\60\312\232\302\30&\12}\355,\311*Y\22\3'\7}\255\260\316\2(\10}\255,"
+    "l\315\1)\11}\235\64l\313\21\0*\11}\355$\313\266J\14+\11}\375\60\32\244\60\7,\7"
+    "}}\26\255\6-\10}}\312\220\223\0.\7}}\272\246\3/\7}}$k'\60\14}\255,"
+    "\211\222(\211\222,\7\61\10}\255Ll\233\1\62\11}\35\251\230\325\206\30\63\13}\215!\214\324$"
+    "\212t\0\64\13}\255LJ\242!\13s\0\65\13}\215!\11\327$\212t\0\66\12}\35)\234*"
+    "Q\244\3\67\13}\215!\314\302,\314\21\0\70\12}\35\251$U\242H\7\71\12}\35\251\22Ec"
+    "\244\3:\10}mMG\64\35;\11}mMG\264\42\0<\10}}$\253\226\1=\11}}l"
+    "\210\207\34\4>\10}\355\264VG\0\77\12}\255,\11\263\34\312\1@\13}\35\251\222(\211\222\352"
+    "\0A\14}\35\251\22%C\22%Q\14B\12}\215\251\62U\242d\7C\11}\35\251\22\226\42\35"
+    "D\14}\215\251\22%Q\22%;\0E\13}\215!\11\247\60\34b\0F\12}\215!\11\247\260\16"
+    "\1G\13}\35\251\22&J\24\315\0H\16}\215(\211\222!\211\222(\211b\0I\10}\235-l"
+    "\233\1J\11}\275\260%\212t\0K\13}\215()iZ\22\225\1L\10}\215\260\343\20\3M\16"
+    "}\215(\31\222!\211\222(\211b\0N\15}\215(Q\22\245\242$J\24\3O\14}\35\251\22%"
+    "Q\22E:\0P\13}\215\251\22%S\230C\0Q\14}\35\251\22%Q\242Dj\2R\13}\215"
+    "\251\22%S\22\225\1S\11}\35\251\65*\351\0T\7}\235-\354\16U\15}\215(\211\222(\211"
+    "\222(\322\1V\14}\215(\211\222(\211\42M\7W\16}\215(\211\222(\31\222!\211b\0X\14"
+    "}\215(\211\42M\252D\61\0Y\12}\235R\22%YX\7Z\11}\215!\314\32\207\30[\10}"
+    "\235)l\234\1\134\7}\335\356 \0]\7}\235\261\323\14^\10}\255,\311Y\1_\7}}\306"
+    "!\6`\7}\235\64g\6a\13}}p\211\222DJb\0b\12}\215\60\234*Q\262\3c\10"
+    "}}P\12S\35d\13}\275\60Z\242$\212f\0e\11}}PJ\214:\0f\13}\255,\211"
+    "\262-\314\21\0g\12}}p\211\42)]\0h\13}\215\60\234*Q\22\305\0i\11}\255\34\21"
+    "k\63\0j\12}\275\34\12KI\26\1k\12}\215\260\22iIT\6l\7}\35\261\333\14m\13"
+    "}},\211\206$J\242\30n\12}}l\252DI\24\3o\11}}P\252D\221\16p\12}}"
+    "l\252D\311\24\2q\13}}p\211\222(\32\23\0r\11}}l\252\204\71\4s\10}}pQ"
+    "\225\35t\11}\235\60\333\302T\6u\13}},J\242$\212f\0v\13}}\60\211\222(\311r"
+    "\0w\13}},J\242dH\206\30x\11}},\212\64\251\14y\12}},J\242JV\3z"
+    "\11}}l\310jC\14{\10}\275\232X\215\1|\7}\255\260w\0}\12}\235\64\324\302,G"
+    "\0~\7}\235\356\314\0\240\5}}\36\241\10}\255\34\12\333\1\242\12}\375l)%\331\26\1\243"
+    "\11}})\333*\62\0\244\13}\335,\231\222h\311R\0\245\12}\235R\222e[\230\3\246\10}"
+    "\375\60\207\302\34\247\13}-)\324\222L\214$\0\250\7}\235:\67\0\251\14}\235%S\222II"
+    "\264d\1\252\11}\35)\311t\26\0\253\11}}\60\222\332\61\0\254\11}}\312\20\346 \0\255\7"
+    "}}\322N\2\256\13}\235%\33\222i\322\222\5\257\7}\215!\347\6\260\11}\255,\311r\26\0"
+    "\261\13}\255\60\32\244\60\32T\0\262\11}\35\61\13u\32\0\263\11}\35M\314t\32\0\264\7}"
+    "\255,\347\0\265\13}},J\242$J\246\20\266\15}\235EI\224(\211\222(\211\1\267\10}}"
+    "P\323i\0\270\7}}\346,\3\271\11}\255L\314v\22\0\272\11}\235,\311r&\0\273\11}"
+    "},*I\71\10\274\11}\215\260)\223\306\4\275\11}\215\260E\314B\5\276\13}\15M\314\224L"
+    "\32\23\0\277\12}\255\34\312\302$\313\1\300\14}\35\251\22%C\22%Q\14\301\14}\35\251\22%"
+    "C\22%Q\14\302\14}\35\251\22%C\22%Q\14\303\14}\35\251\22%C\22%Q\14\304\14}"
+    "\215(\222*C\22%Q\14\305\14}\35M\252\14I\224D\61\0\306\14}\235\245\224(S\22%\62"
+    "\0\307\12}\35\251\22\226\42-\3\310\13}\215!\11\247\60\34b\0\311\13}\215!\11\247\60\34b"
+    "\0\312\13}\215!\11\247\60\34b\0\313\13}\215!\11\247\60\34b\0\314\10}\235-l\233\1\315"
+    "\10}\235-l\233\1\316\10}\235-l\233\1\317\10}\235-l\233\1\320\14}\215-I\224(\211"
+    "\222d\7\321\15}\215D\211\22\245\242$J\24\3\322\14}\35\251\22%Q\22E:\0\323\14}\35"
+    "\251\22%Q\22E:\0\324\14}\35\251\22%Q\22E:\0\325\14}\35\251\22%Q\22E:\0"
+    "\326\14}\215(\222*Q\22E:\0\327\11}},\212\64\251\14\330\14}\235%Q\22K\242$;"
+    "\0\331\15}\215(\211\222(\211\222(\322\1\332\15}\215(\211\222(\211\222(\322\1\333\15}\215("
+    "\211\222(\211\222(\322\1\334\13}\215(\216\222(\211\42\35\335\12}\235R\22%YX\7\336\12}"
+    "\215p\252La\16\1\337\12}\35\251R\252DI\35\340\13}\235\64[\242$\221\222\30\341\13}\255"
+    ",\134\242$\221\222\30\342\14}\255,\211\226(I\244$\6\343\13}\235nK\224$R\22\3\344\13"
+    "}\235\372\22%\211\224\304\0\345\13}\35M[\242$\221\222\30\346\11}}pI\224\332\14\347\11}"
+    "}T\12S-\2\350\11}\235\64\223\22\243\16\351\11}\255,\224\22\243\16\352\12}\235,\311\244\304"
+    "\250\3\353\12}\215$G\244\304\250\3\354\11}\235\64\23k\63\0\355\11}\255,\24k\63\0\356\11"
+    "}\255,\211\304\332\14\357\10}\235\272X\233\1\360\12}\235T\222*Q\244\3\361\12}\235NS%"
+    "J\242\30\362\12}\235\64\223*Q\244\3\363\12}\255,\224*Q\244\3\364\12}\35\35\221*Q\244"
+    "\3\365\12}\235nR%\212t\0\366\11}\235\272T\211\42\35\367\10}m}\310u\0\370\11}}"
+    "pI,\311\16\371\13}\235\64\252DI\24\315\0\372\13}\255\254\224DI\24\315\0\373\13}\35="
+    "J\242$\212f\0\374\13}\235r\224DI\24\315\0\375\12}\255\254\224D\225\254\6\376\12}\335p"
+    "\252D\311\24\2\377\12}\235r\224D\225\254\6\0\0\0\4\377\377\0";
+
+#endif /* U8G2_FONT_5X7_H */

+ 587 - 0
views/boilerplate_scene_1.c

@@ -0,0 +1,587 @@
+#include "../malveke_gb_photo.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <dolphin/dolphin.h>
+
+extern const uint8_t u8g2_font_5x7_mf[1911]; // Declare as extern
+
+const uint8_t _I_DolphinCommon_56x48_0[] = {
+    0x01,
+    0x00,
+    0xdf,
+    0x00,
+    0x00,
+    0x1f,
+    0xfe,
+    0x0e,
+    0x05,
+    0x3f,
+    0x04,
+    0x06,
+    0x78,
+    0x06,
+    0x30,
+    0x20,
+    0xf8,
+    0x00,
+    0xc6,
+    0x12,
+    0x1c,
+    0x04,
+    0x0c,
+    0x0a,
+    0x38,
+    0x08,
+    0x08,
+    0x0c,
+    0x60,
+    0xc0,
+    0x21,
+    0xe0,
+    0x04,
+    0x0a,
+    0x18,
+    0x02,
+    0x1b,
+    0x00,
+    0x18,
+    0xa3,
+    0x00,
+    0x21,
+    0x90,
+    0x01,
+    0x8a,
+    0x20,
+    0x02,
+    0x19,
+    0x80,
+    0x18,
+    0x80,
+    0x64,
+    0x09,
+    0x20,
+    0x89,
+    0x81,
+    0x8c,
+    0x3e,
+    0x41,
+    0xe2,
+    0x80,
+    0x50,
+    0x00,
+    0x43,
+    0x08,
+    0x01,
+    0x0c,
+    0xfc,
+    0x68,
+    0x40,
+    0x61,
+    0xc0,
+    0x50,
+    0x30,
+    0x00,
+    0x63,
+    0xa0,
+    0x7f,
+    0x80,
+    0xc4,
+    0x41,
+    0x19,
+    0x07,
+    0xff,
+    0x02,
+    0x06,
+    0x18,
+    0x24,
+    0x03,
+    0x41,
+    0xf3,
+    0x2b,
+    0x10,
+    0x19,
+    0x38,
+    0x10,
+    0x30,
+    0x31,
+    0x7f,
+    0xe0,
+    0x34,
+    0x08,
+    0x30,
+    0x19,
+    0x60,
+    0x80,
+    0x65,
+    0x86,
+    0x0a,
+    0x4c,
+    0x0c,
+    0x30,
+    0x81,
+    0xb9,
+    0x41,
+    0xa0,
+    0x54,
+    0x08,
+    0xc7,
+    0xe2,
+    0x06,
+    0x8a,
+    0x18,
+    0x25,
+    0x02,
+    0x21,
+    0x0f,
+    0x19,
+    0x88,
+    0xd8,
+    0x6e,
+    0x1b,
+    0x01,
+    0xd1,
+    0x1b,
+    0x86,
+    0x39,
+    0x66,
+    0x3a,
+    0xa4,
+    0x1a,
+    0x50,
+    0x06,
+    0x48,
+    0x18,
+    0x18,
+    0xd0,
+    0x03,
+    0x01,
+    0x41,
+    0x98,
+    0xcc,
+    0x60,
+    0x39,
+    0x01,
+    0x49,
+    0x2d,
+    0x06,
+    0x03,
+    0x50,
+    0xf8,
+    0x40,
+    0x3e,
+    0x02,
+    0xc1,
+    0x82,
+    0x86,
+    0xc7,
+    0xfe,
+    0x0f,
+    0x28,
+    0x2c,
+    0x91,
+    0xd2,
+    0x90,
+    0x9a,
+    0x18,
+    0x19,
+    0x3e,
+    0x6d,
+    0x73,
+    0x12,
+    0x16,
+    0x00,
+    0x32,
+    0x49,
+    0x72,
+    0xc0,
+    0x7e,
+    0x5d,
+    0x44,
+    0xba,
+    0x2c,
+    0x08,
+    0xa4,
+    0xc8,
+    0x82,
+    0x06,
+    0x17,
+    0xe0,
+    0x81,
+    0x90,
+    0x2a,
+    0x40,
+    0x61,
+    0xe1,
+    0xa2,
+    0x44,
+    0x0c,
+    0x76,
+    0x2b,
+    0xe8,
+    0x89,
+    0x26,
+    0x43,
+    0x83,
+    0x31,
+    0x8c,
+    0x78,
+    0x0c,
+    0xb0,
+    0x48,
+    0x10,
+    0x1a,
+    0xe0,
+    0x00,
+    0x63,
+};
+const uint8_t *const _I_DolphinCommon_56x48[] = {_I_DolphinCommon_56x48_0};
+const Icon I_DolphinCommon_56x48 = {.width = 56, .height = 48, .frame_count = 1, .frame_rate = 0, .frames = _I_DolphinCommon_56x48};
+
+struct BoilerplateScene1
+{
+    View *view;
+    BoilerplateScene1Callback callback;
+    void *context;
+};
+
+typedef struct
+{
+    BoilerplateScene1 *instance;
+} BoilerplateScene1Model;
+
+void boilerplate_scene_1_set_callback(
+    BoilerplateScene1 *instance,
+    BoilerplateScene1Callback callback,
+    void *context)
+{
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void boilerplate_scene_1_draw(Canvas *canvas, BoilerplateScene1Model *model)
+{
+    UNUSED(model);
+    BoilerplateScene1 *instance = model->instance;
+    Boilerplate *app = instance->context;
+    canvas_clear(canvas);
+    // Prepare canvas
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_frame(canvas, 0, 0, FRAME_WIDTH, FRAME_HEIGTH);
+
+    if (app->show_instructions)
+    {
+        canvas_draw_icon(canvas, 71, 15, &I_DolphinCommon_56x48);
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, (128 / 2), 4, AlignCenter, AlignTop, "INSTRUCTIONS");
+
+        canvas_set_custom_u8g2_font(canvas, u8g2_font_5x7_mf); // 5x7 font, 9 lines
+
+        canvas_draw_icon_ex(canvas, 15, 18, &I_arrow_13x6, IconRotation180); // DOWN
+        canvas_draw_icon(canvas, 28, 18, &I_arrow_13x6);                     // UP
+        canvas_draw_str_aligned(canvas, 47, 21, AlignLeft, AlignCenter, "SCROLL");
+        canvas_draw_icon_ex(canvas, 33, 26, &I_arrow_13x6, IconRotation270); // PREV
+        canvas_draw_icon_ex(canvas, 22, 36, &I_arrow_13x6, IconRotation90);  // NEXT
+        canvas_draw_str_aligned(canvas, 47, 32, AlignLeft, AlignCenter, "LEFT");
+        canvas_draw_str_aligned(canvas, 47, 43, AlignLeft, AlignCenter, "RIGHT");
+
+        canvas_set_font(canvas, FontPrimary);
+        elements_button_center(canvas, "OK");
+    }
+    else
+    {
+        int count = (app->page + 1) * 0x1000;
+        uint8_t status = app->scratchpad1[0x11B2 + app->page];
+
+        storage_file_seek(app->camera_ram_sav, count, true);
+
+        for (int y = app->pos_y; y < 14; y++)
+        {
+            for (int x = app->pos_x; x < 16; x++)
+            {
+                storage_file_read(app->camera_ram_sav, app->tile_data, sizeof(app->tile_data));
+                for (int row = 0; row < 8; row++)
+                {
+                    uint8_t temp1 = app->tile_data[row * 2];
+                    uint8_t temp2 = app->tile_data[row * 2 + 1];
+                    for (int pixel = 7; pixel >= 0; pixel--)
+                    {
+                        if (((temp1 & 1) + ((temp2 & 1) * 2)) >= 2)
+                        {
+                            canvas_draw_dot(canvas, (x * 8) + pixel, (y * 8) + row);
+                        }
+                        temp1 >>= 1;
+                        temp2 >>= 1;
+                    }
+                }
+            }
+        }
+
+        if (app->info)
+        {
+            if (status == 0xFF)
+            {
+                canvas_draw_rbox(canvas, 100, 4, 20, 11, 4);
+                canvas_invert_color(canvas);
+                canvas_draw_str_aligned(canvas, 110, 10, AlignCenter, AlignCenter, "D");
+                canvas_invert_color(canvas);
+            }
+        }
+    }
+}
+
+static void boilerplate_scene_1_model_init(BoilerplateScene1Model *const model, void *context)
+{
+    BoilerplateScene1 *instance = context;
+    UNUSED(model);
+    UNUSED(instance);
+    model->instance = context;
+}
+
+void save_image(void *context)
+{
+    Boilerplate *app = context;
+    furi_assert(app);
+    NotificationApp *notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    //  Create MALVEKE dir
+    if (storage_common_stat(app->storage, MALVEKE_APP_FOLDER, NULL) == FSE_NOT_EXIST)
+    {
+        storage_simply_mkdir(app->storage, MALVEKE_APP_FOLDER);
+    }
+    //  Create MALVEKE Photos dir
+    if (storage_common_stat(app->storage, MALVEKE_APP_FOLDER_PHOTOS, NULL) == FSE_NOT_EXIST)
+    {
+        storage_simply_mkdir(app->storage, MALVEKE_APP_FOLDER_PHOTOS);
+    }
+
+    int count = (app->page + 1) * 0x1000;
+    storage_file_seek(app->camera_ram_sav, count, true);
+    // create file name
+    FuriString *file_name = furi_string_alloc();
+    get_timefilename(file_name, app->page);
+
+    File *file = storage_file_alloc(app->storage);
+    bool result =
+        storage_file_open(file, furi_string_get_cstr(file_name), FSAM_WRITE, FSOM_OPEN_ALWAYS);
+
+    if (result)
+    {
+        static char bmp[BMP_SIZE(WIDTH, HEIGHT)];
+        bmp_init(bmp, WIDTH, HEIGHT);
+
+        //  Selected Palette
+        uint32_t palette[] = {
+            bmp_encode(app->palette_color_hex_a),
+            bmp_encode(app->palette_color_hex_b),
+            bmp_encode(app->palette_color_hex_c),
+            bmp_encode(app->palette_color_hex_d)};
+
+        UNUSED(palette);
+
+        for (int y = 0; y < 14; y++)
+        {
+            for (int x = 0; x < 16; x++)
+            {
+                storage_file_read(app->camera_ram_sav, app->tile_data, sizeof(app->tile_data));
+                for (int row = 0; row < 8; row++)
+                {
+                    uint8_t temp1 = app->tile_data[row * 2];
+                    uint8_t temp2 = app->tile_data[row * 2 + 1];
+
+                    for (int pixel = 7; pixel >= 0; pixel--)
+                    {
+                        bmp_set(
+                            bmp,
+                            (x * 8) + pixel,
+                            (y * 8) + row,
+                            palette[((temp1 & 1) + ((temp2 & 1) * 2))]);
+                        temp1 >>= 1;
+                        temp2 >>= 1;
+                    }
+                }
+            }
+        }
+
+        storage_file_write(file, bmp, sizeof(bmp));
+        storage_file_close(file);
+    }
+
+    // Closing the "file descriptor"
+    storage_file_close(file);
+    // Freeing up memory
+    storage_file_free(file);
+    notification_message(notifications, result ? &sequence_success : &sequence_error);
+}
+bool boilerplate_scene_1_input(InputEvent *event, void *context)
+{
+    furi_assert(context);
+    BoilerplateScene1 *instance = context;
+    Boilerplate *app = instance->context;
+    if (event->type == InputTypeRelease)
+    {
+        switch (event->key)
+        {
+        case InputKeyBack:
+            with_view_model(
+                instance->view,
+                BoilerplateScene1Model * model,
+                {
+                    UNUSED(model);
+                    instance->callback(BoilerplateCustomEventScene1Back, instance->context);
+                },
+                true);
+            break;
+        case InputKeyLeft:
+            with_view_model(
+                instance->view,
+                BoilerplateScene1Model * model,
+                {
+                    UNUSED(model);
+                    app->pos_y = 0;
+                    app->pos_x = 0;
+
+                    if (app->page > 0)
+                    {
+                        app->page--;
+                    }
+                    else
+                    {
+                        app->page = 29;
+                    }
+                },
+                true);
+            break;
+        case InputKeyRight:
+            with_view_model(
+                instance->view,
+                BoilerplateScene1Model * model,
+                {
+                    UNUSED(model);
+                    app->pos_y = 0;
+                    app->pos_x = 0;
+                    if (app->page < 29)
+                    {
+                        app->page++;
+                    }
+                    else
+                    {
+                        app->page = 0;
+                    }
+                },
+                true);
+            break;
+        case InputKeyUp:
+            with_view_model(
+                instance->view,
+                BoilerplateScene1Model * model,
+                {
+                    UNUSED(model);
+                    if (app->pos_y < 0)
+                    {
+                        app->pos_y++;
+                    }
+                },
+                true);
+            break;
+        case InputKeyDown:
+            with_view_model(
+                instance->view,
+                BoilerplateScene1Model * model,
+                {
+                    UNUSED(model);
+                    if (app->pos_y > -6)
+                    {
+                        app->pos_y--;
+                    }
+                },
+                true);
+            break;
+        case InputKeyOk:
+            with_view_model(
+                instance->view,
+                BoilerplateScene1Model * model,
+                {
+                    UNUSED(model);
+                    if (app->show_instructions)
+                    {
+                        app->show_instructions = false;
+                    }
+                    else
+                    {
+                        save_image(app);
+                    }
+                },
+                true);
+            break;
+        case InputKeyMAX:
+            break;
+        }
+    }
+    return true;
+}
+
+void boilerplate_scene_1_exit(void *context)
+{
+    furi_assert(context);
+}
+
+void boilerplate_scene_1_enter(void *context)
+{
+    furi_assert(context);
+    BoilerplateScene1 *instance = (BoilerplateScene1 *)context;
+
+    with_view_model(
+        instance->view,
+        BoilerplateScene1Model * model,
+        {
+            boilerplate_scene_1_model_init(model, instance);
+        },
+        true);
+}
+
+BoilerplateScene1 *boilerplate_scene_1_alloc()
+{
+    BoilerplateScene1 *instance = malloc(sizeof(BoilerplateScene1));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(BoilerplateScene1Model));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    view_set_draw_callback(instance->view, (ViewDrawCallback)boilerplate_scene_1_draw);
+    view_set_input_callback(instance->view, boilerplate_scene_1_input);
+    view_set_enter_callback(instance->view, boilerplate_scene_1_enter);
+    view_set_exit_callback(instance->view, boilerplate_scene_1_exit);
+
+    with_view_model(
+        instance->view,
+        BoilerplateScene1Model * model,
+        {
+            boilerplate_scene_1_model_init(model, instance);
+        },
+        true);
+
+    return instance;
+}
+
+void boilerplate_scene_1_free(BoilerplateScene1 *instance)
+{
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        BoilerplateScene1Model * model,
+        {
+            UNUSED(model);
+        },
+        true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View *boilerplate_scene_1_get_view(BoilerplateScene1 *instance)
+{
+    furi_assert(instance);
+    return instance->view;
+}

+ 30 - 0
views/boilerplate_scene_1.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include <gui/view.h>
+#include <gui/icon_i.h>
+#include "../helpers/boilerplate_custom_event.h"
+#include <malveke_gb_photo_icons.h>
+#define BMP_COMPAT // write bottom to top
+#include "../helpers/bmp.h"
+#include "../helpers/malveke_photo.h"
+
+#define WIDTH   128L
+#define HEIGHT  112L
+
+#define FRAME_WIDTH 128
+#define FRAME_HEIGTH 64
+
+typedef struct BoilerplateScene1 BoilerplateScene1;
+
+typedef void (*BoilerplateScene1Callback)(BoilerplateCustomEvent event, void* context);
+
+void boilerplate_scene_1_set_callback(
+    BoilerplateScene1* boilerplate_scene_1,
+    BoilerplateScene1Callback callback,
+    void* context);
+
+View* boilerplate_scene_1_get_view(BoilerplateScene1* boilerplate_static);
+
+BoilerplateScene1* boilerplate_scene_1_alloc();
+
+void boilerplate_scene_1_free(BoilerplateScene1* boilerplate_static);

+ 278 - 0
views/boilerplate_scene_2.c

@@ -0,0 +1,278 @@
+#include "../malveke_gb_photo.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <dolphin/dolphin.h>
+
+struct BoilerplateScene2
+{
+    View *view;
+    BoilerplateScene2Callback callback;
+    void *context;
+};
+
+typedef struct
+{
+    bool in_progress;
+    int page;
+    BoilerplateScene2 *instance;
+} BoilerplateScene2Model;
+
+void boilerplate_scene_2_set_callback(
+    BoilerplateScene2 *instance,
+    BoilerplateScene2Callback callback,
+    void *context)
+{
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void draw_thumbnail(void *context, Canvas *canvas, int page){
+    BoilerplateScene2 *instance = context;
+    Boilerplate *app = instance->context;
+    UNUSED(app);
+    //  Gallery
+    // for (int s=0;s< 8 * (page+1);s++) {
+        int count = ((page + 2) * 0x1000) + 0x0e00;
+        storage_file_seek(app->camera_ram_sav, count, true);
+        for (int y = 0; y < 4; y++) {
+            for (int x = 0; x < 4; x++) {
+
+                storage_file_read(app->camera_ram_sav, app->tile_data, sizeof(app->tile_data));
+                for (int row = 0; row < 8; row++) {
+                    uint8_t temp1 = app->tile_data[row * 2];
+                    uint8_t temp2 = app->tile_data[row * 2 + 1];
+                    for (int pixel = 7; pixel >= 0; pixel--) {
+                        int colorIndex = ((temp1 & 1)+((temp2 & 1)*2));
+                        if (colorIndex >=2) {
+                            canvas_draw_dot(canvas, (x*8)+ (pixel+47), (y*8)+row+17);
+                        }
+                        temp1 >>= 1;
+                        temp2 >>= 1;
+                    }
+                }
+            }
+        }
+    // }
+}
+void boilerplate_scene_2_draw(Canvas *canvas, BoilerplateScene2Model *model)
+{
+    UNUSED(model);
+    BoilerplateScene2 *instance = model->instance;
+    Boilerplate *app = instance->context;
+    UNUSED(app);
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 21, 13, "Export All Photos");
+    canvas_set_font(canvas, FontPrimary);
+
+    char totalText[32];
+    snprintf(totalText, sizeof(totalText), "%02d/29", model->page);
+
+    draw_thumbnail(instance, canvas, model->page);
+    if (!model->in_progress)
+    {
+        
+        elements_button_center(canvas, "OK");
+    }
+    else
+    {
+        float dict_progress = (float)(model->page * 1) / (float)(29);
+        int progress_width = 128 / 2;
+        int position_x = (128 / 2);
+        int position_y = 52;
+        elements_progress_bar_with_text(canvas, position_x - (progress_width / 2), position_y, progress_width, dict_progress, totalText);
+    }
+}
+
+static void boilerplate_scene_2_model_init(BoilerplateScene2Model *const model, void *context)
+{
+    BoilerplateScene2 *instance = context;
+    model->instance = instance;
+    model->page = 0;
+    model->in_progress = false;
+}
+void save_all_image(void *context)
+{
+    BoilerplateScene2 *instance = context;
+    Boilerplate *app = instance->context;
+    UNUSED(app);
+    furi_assert(app);
+    NotificationApp *notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    //  Create MALVEKE dir
+    if (storage_common_stat(app->storage, MALVEKE_APP_FOLDER, NULL) == FSE_NOT_EXIST)
+    {
+        storage_simply_mkdir(app->storage, MALVEKE_APP_FOLDER);
+    }
+    //  Create MALVEKE Photos dir
+    if (storage_common_stat(app->storage, MALVEKE_APP_FOLDER_PHOTOS, NULL) == FSE_NOT_EXIST)
+    {
+        storage_simply_mkdir(app->storage, MALVEKE_APP_FOLDER_PHOTOS);
+    }
+
+    for (int page = 0; page < 30; page++)
+    {
+
+        int count = (page + 1) * 0x1000;
+        storage_file_seek(app->camera_ram_sav, count, true);
+        // create file name
+        FuriString *file_name = furi_string_alloc();
+        get_timefilename(file_name, page);
+
+        File *file = storage_file_alloc(app->storage);
+        bool result =
+            storage_file_open(file, furi_string_get_cstr(file_name), FSAM_WRITE, FSOM_OPEN_ALWAYS);
+
+        if (result)
+        {
+
+            static char bmp[BMP_SIZE(WIDTH, HEIGHT)];
+            bmp_init(bmp, WIDTH, HEIGHT);
+
+            //  Selected Palette
+            uint32_t palette[] = {
+                bmp_encode(app->palette_color_hex_a),
+                bmp_encode(app->palette_color_hex_b),
+                bmp_encode(app->palette_color_hex_c),
+                bmp_encode(app->palette_color_hex_d)};
+
+            UNUSED(palette);
+
+            for (int y = 0; y < 14; y++)
+            {
+                for (int x = 0; x < 16; x++)
+                {
+                    storage_file_read(app->camera_ram_sav, app->tile_data, sizeof(app->tile_data));
+                    for (int row = 0; row < 8; row++)
+                    {
+                        uint8_t temp1 = app->tile_data[row * 2];
+                        uint8_t temp2 = app->tile_data[row * 2 + 1];
+
+                        for (int pixel = 7; pixel >= 0; pixel--)
+                        {
+                            bmp_set(
+                                bmp,
+                                (x * 8) + pixel,
+                                (y * 8) + row,
+                                palette[((temp1 & 1) + ((temp2 & 1) * 2))]);
+                            temp1 >>= 1;
+                            temp2 >>= 1;
+                        }
+                    }
+                }
+            }
+
+            storage_file_write(file, bmp, sizeof(bmp));
+
+            with_view_model(
+                instance->view,
+                BoilerplateScene2Model * model,
+                {
+                    // UNUSED(model);
+                    model->page = page;
+                },
+                true);
+            furi_delay_ms(120);
+        }
+
+        // Closing the "file descriptor"
+        storage_file_close(file);
+        // Freeing up memory
+        storage_file_free(file);
+    }
+    notification_message(notifications, &sequence_success);
+    with_view_model(
+        instance->view,
+        BoilerplateScene2Model * model,
+        {
+            model->in_progress = false;
+        },
+        true);
+}
+bool boilerplate_scene_2_input(InputEvent *event, void *context)
+{
+    furi_assert(context);
+    BoilerplateScene2 *instance = context;
+    // Boilerplate *app = instance->context;
+
+    if (event->type == InputTypeRelease)
+    {
+        switch (event->key)
+        {
+        case InputKeyOk:
+            with_view_model(
+                instance->view,
+                BoilerplateScene2Model * model,
+                {
+                    model->in_progress = true;
+                },
+                true);
+
+            save_all_image(instance);
+            break;
+        case InputKeyBack:
+            instance->callback(BoilerplateCustomEventScene2Back, instance->context);
+            break;
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyMAX:
+            break;
+        }
+    }
+
+    return true;
+}
+
+void boilerplate_scene_2_exit(void *context)
+{
+    furi_assert(context);
+    Boilerplate *app = context;
+    UNUSED(app);
+    // boilerplate_stop_all_sound(app);
+    // boilerplate_led_reset(app);
+}
+
+
+
+BoilerplateScene2 *boilerplate_scene_2_alloc()
+{
+    BoilerplateScene2 *instance = malloc(sizeof(BoilerplateScene2));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(BoilerplateScene2Model));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)boilerplate_scene_2_draw);
+    view_set_input_callback(instance->view, boilerplate_scene_2_input);
+    view_set_exit_callback(instance->view, boilerplate_scene_2_exit);
+
+    with_view_model(
+        instance->view,
+        BoilerplateScene2Model * model,
+        {
+            boilerplate_scene_2_model_init(model, instance);
+        },
+        true);
+
+    return instance;
+}
+
+void boilerplate_scene_2_free(BoilerplateScene2 *instance)
+{
+    furi_assert(instance);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View *boilerplate_scene_2_get_view(BoilerplateScene2 *instance)
+{
+    furi_assert(instance);
+
+    return instance->view;
+}

+ 22 - 0
views/boilerplate_scene_2.h

@@ -0,0 +1,22 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/boilerplate_custom_event.h"
+#define BMP_COMPAT // write bottom to top
+#include "../helpers/bmp.h"
+#include "../helpers/malveke_photo.h"
+
+typedef struct BoilerplateScene2 BoilerplateScene2;
+
+typedef void (*BoilerplateScene2Callback)(BoilerplateCustomEvent event, void* context);
+
+void boilerplate_scene_2_set_callback(
+    BoilerplateScene2* instance,
+    BoilerplateScene2Callback callback,
+    void * context);
+
+BoilerplateScene2* boilerplate_scene_2_alloc();
+
+void boilerplate_scene_2_free(BoilerplateScene2* boilerplate_static);
+
+View* boilerplate_scene_2_get_view(BoilerplateScene2* boilerpate_static);

+ 138 - 0
views/boilerplate_startscreen.c

@@ -0,0 +1,138 @@
+#include "../malveke_gb_photo.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+
+extern const uint8_t u8g2_font_5x7_mf[1911]; // Declare as extern
+
+struct BoilerplateStartscreen
+{
+    View *view;
+    BoilerplateStartscreenCallback callback;
+    void *context;
+};
+
+typedef struct
+{
+    int some_value;
+} BoilerplateStartscreenModel;
+
+void boilerplate_startscreen_set_callback(
+    BoilerplateStartscreen *instance,
+    BoilerplateStartscreenCallback callback,
+    void *context)
+{
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void boilerplate_startscreen_draw(Canvas *canvas, BoilerplateStartscreenModel *model)
+{
+    UNUSED(model);
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "GAME BOY PHOTO");
+    canvas_set_custom_u8g2_font(canvas, u8g2_font_5x7_mf);
+    canvas_draw_str_aligned(canvas, 64, 28, AlignCenter, AlignTop, "SELECT A '.SAV' FILE ");
+    canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignTop, "FROM GB CAMERA TO PROCEED");
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_center(canvas, "Start");
+}
+
+static void boilerplate_startscreen_model_init(BoilerplateStartscreenModel *const model)
+{
+    model->some_value = 1;
+}
+
+bool boilerplate_startscreen_input(InputEvent *event, void *context)
+{
+    furi_assert(context);
+    BoilerplateStartscreen *instance = context;
+    if (event->type == InputTypeRelease)
+    {
+        switch (event->key)
+        {
+        case InputKeyBack:
+            with_view_model(
+                instance->view,
+                BoilerplateStartscreenModel * model,
+                {
+                    UNUSED(model);
+                    instance->callback(BoilerplateCustomEventStartscreenBack, instance->context);
+                },
+                true);
+            break;
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyOk:
+            instance->callback(BoilerplateCustomEventStartscreenOk, instance->context);
+            break;
+        case InputKeyMAX:
+            break;
+        }
+    }
+    return true;
+}
+
+void boilerplate_startscreen_exit(void *context)
+{
+    furi_assert(context);
+}
+
+void boilerplate_startscreen_enter(void *context)
+{
+    furi_assert(context);
+    BoilerplateStartscreen *instance = (BoilerplateStartscreen *)context;
+    with_view_model(
+        instance->view,
+        BoilerplateStartscreenModel * model,
+        {
+            boilerplate_startscreen_model_init(model);
+        },
+        true);
+}
+
+BoilerplateStartscreen *boilerplate_startscreen_alloc()
+{
+    BoilerplateStartscreen *instance = malloc(sizeof(BoilerplateStartscreen));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(BoilerplateStartscreenModel));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    view_set_draw_callback(instance->view, (ViewDrawCallback)boilerplate_startscreen_draw);
+    view_set_input_callback(instance->view, boilerplate_startscreen_input);
+    with_view_model(
+        instance->view,
+        BoilerplateStartscreenModel * model,
+        {
+            boilerplate_startscreen_model_init(model);
+        },
+        true);
+
+    return instance;
+}
+
+void boilerplate_startscreen_free(BoilerplateStartscreen *instance)
+{
+    furi_assert(instance);
+    with_view_model(
+        instance->view,
+        BoilerplateStartscreenModel * model,
+        {
+            UNUSED(model);
+        },
+        true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View *boilerplate_startscreen_get_view(BoilerplateStartscreen *instance)
+{
+    furi_assert(instance);
+    return instance->view;
+}

+ 19 - 0
views/boilerplate_startscreen.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/boilerplate_custom_event.h"
+
+typedef struct BoilerplateStartscreen BoilerplateStartscreen;
+
+typedef void (*BoilerplateStartscreenCallback)(BoilerplateCustomEvent event, void* context);
+
+void boilerplate_startscreen_set_callback(
+    BoilerplateStartscreen* boilerplate_startscreen,
+    BoilerplateStartscreenCallback callback,
+    void* context);
+
+View* boilerplate_startscreen_get_view(BoilerplateStartscreen* boilerplate_static);
+
+BoilerplateStartscreen* boilerplate_startscreen_alloc();
+
+void boilerplate_startscreen_free(BoilerplateStartscreen* boilerplate_static);