Bläddra i källkod

Merge branch 'jblanked:dev_0.1' into dev_0.1

pr3! 1 år sedan
förälder
incheckning
382460b51d

+ 0 - 0
assets/chest_closed_16x13px.png → assets/icon_chest_closed_16x13px.png


+ 0 - 0
assets/chest_open_16x16px.png → assets/icon_chest_open_16x16px.png


+ 0 - 0
assets/fence_16x8px.png → assets/icon_fence_16x8px.png


+ 0 - 0
assets/fence_end_16x8px.png → assets/icon_fence_end_16x8px.png


+ 0 - 0
assets/house_3d_34x45px.png → assets/icon_house_3d_34x45px.png


BIN
assets/icon_house_48x48px.png


BIN
assets/icon_lake_bottom_31x31px.png


BIN
assets/icon_lake_bottom_left_31x31px.png


BIN
assets/icon_lake_bottom_right_31x31px.png


BIN
assets/icon_lake_left_31x31.png


BIN
assets/icon_lake_right_31x31px.png


BIN
assets/icon_lake_top_31x31px.png


BIN
assets/icon_lake_top_left_31x31px.png


BIN
assets/icon_lake_top_right_31x31px.png


+ 0 - 0
assets/plant_fern_18x16px.png → assets/icon_plant_fern_18x16px.png


+ 0 - 0
assets/plant_pointy_13x16px.png → assets/icon_plant_pointy_13x16px.png


+ 0 - 0
assets/rock_large_18x19px.png → assets/icon_rock_large_18x19px.png


+ 0 - 0
assets/rock_medium_16x14px.png → assets/icon_rock_medium_16x14px.png


+ 0 - 0
assets/rock_small_10x8px.png → assets/icon_rock_small_10x8px.png


+ 0 - 0
assets/tree_29x30px.png → assets/icon_tree_29x30px.png


+ 0 - 0
assets/tree_48x48px.png → assets/icon_tree_48x48px.png


+ 24 - 4
callback/callback.c

@@ -780,26 +780,46 @@ static char *flip_world_parse_worlds(DataLoaderModel *model)
         FURI_LOG_E(TAG, "Failed to load world data");
         return "Failed to load world data";
     }
+    // first save list of names
+    FuriString *names = get_json_value_furi("names", world_data);
+    if (!names)
+    {
+        FURI_LOG_E(TAG, "Failed to get names");
+        furi_string_free(world_data);
+        return "Failed to get names";
+    }
+    if (!save_world_names(names))
+    {
+        FURI_LOG_E(TAG, "Failed to save world names");
+        furi_string_free(names);
+        furi_string_free(world_data);
+        return "Failed to save world names";
+    }
+    furi_string_free(names);
     // we used 10 since we passed 10 in the request
     for (int i = 0; i < 10; i++)
     {
-        char *json = get_json_array_value("worlds", i, furi_string_get_cstr(world_data), 1024);
-        if (!json)
+        FuriString *worlds = get_json_array_value_furi("worlds", i, world_data);
+        if (!worlds)
         {
             FURI_LOG_E(TAG, "Failed to get worlds. Data likely empty");
             break;
         }
-        char *world_name = get_json_value("name", json, 1024);
+        FuriString *world_name = get_json_array_value_furi("names", i, world_data);
         if (!world_name)
         {
             FURI_LOG_E(TAG, "Failed to get world name");
+            furi_string_free(worlds);
             break;
         }
-        if (!save_world(world_name, json))
+        if (!save_world_furi(world_name, worlds))
         {
             FURI_LOG_E(TAG, "Failed to save world");
         }
+        furi_string_free(world_name);
+        furi_string_free(worlds);
     }
+    furi_string_free(world_data);
     return "World Pack Installed";
 }
 static void flip_world_switch_to_view_get_worlds(FlipWorldApp *app)

+ 0 - 80
draw/draw.c

@@ -1,80 +0,0 @@
-#include <draw/draw.h>
-
-// Global variables to store camera position
-int camera_x = 0;
-int camera_y = 0;
-
-// Draw a line of icons (16 width)
-void draw_icon_line(Canvas *canvas, Vector pos, int amount, bool horizontal, const Icon *icon)
-{
-    for (int i = 0; i < amount; i++)
-    {
-        if (horizontal)
-        {
-            // check if element is outside the world
-            if (pos.x + (i * 17) > WORLD_WIDTH)
-            {
-                break;
-            }
-
-            canvas_draw_icon(canvas, pos.x + (i * 17) - camera_x, pos.y - camera_y, icon);
-        }
-        else
-        {
-            // check if element is outside the world
-            if (pos.y + (i * 17) > WORLD_HEIGHT)
-            {
-                break;
-            }
-
-            canvas_draw_icon(canvas, pos.x - camera_x, pos.y + (i * 17) - camera_y, icon);
-        }
-    }
-}
-// Draw a half section of icons (16 width)
-void draw_icon_half_world(Canvas *canvas, bool right, const Icon *icon)
-{
-    for (int i = 0; i < 10; i++)
-    {
-        if (right)
-        {
-            draw_icon_line(canvas, (Vector){WORLD_WIDTH / 2 + 6, i * 19 + 2}, 11, true, icon);
-        }
-        else
-        {
-            draw_icon_line(canvas, (Vector){0, i * 19 + 2}, 11, true, icon);
-        }
-    }
-}
-const Icon *get_icon(char *name)
-{
-    if (strcmp(name, "earth") == 0)
-    {
-        return &I_icon_earth;
-    }
-    if (strcmp(name, "home") == 0)
-    {
-        return &I_icon_home;
-    }
-    if (strcmp(name, "info") == 0)
-    {
-        return &I_icon_info;
-    }
-    if (strcmp(name, "man") == 0)
-    {
-        return &I_icon_man;
-    }
-    if (strcmp(name, "plant") == 0)
-    {
-        return &I_icon_plant;
-    }
-    if (strcmp(name, "tree") == 0)
-    {
-        return &I_icon_tree;
-    }
-    if (strcmp(name, "woman") == 0)
-    {
-        return &I_icon_woman;
-    }
-    return NULL;
-}

+ 0 - 33
draw/draw.h

@@ -1,33 +0,0 @@
-#pragma once
-#include "engine/engine.h"
-#include "flip_world.h"
-#include "flip_world_icons.h"
-
-typedef enum
-{
-    // system draw objects
-    DRAW_DOT,        // canvas_draw_dot
-    DRAW_LINE,       // canvas_draw_line
-    DRAW_BOX,        // canvas_draw_box
-    DRAW_FRAME,      // canvas_draw_frame
-    DRAW_CIRCLE,     // canvas_draw_circle
-    DRAW_XBM,        // canvas_draw_xbm
-                     // custom draw objects
-    DRAW_ICON_EARTH, // 	canvas_draw_icon
-    DRAW_ICON_HOME,  // 	canvas_draw_icon
-    DRAW_ICON_INFO,  // 	canvas_draw_icon
-    DRAW_ICON_MAN,   // 	canvas_draw_man
-    DRAW_ICON_PLANT, // 	canvas_draw_icon
-    DRAW_ICON_TREE,  // 	canvas_draw_icon
-    DRAW_ICON_WOMAN, // 	canvas_draw_icon
-} FlipWorldDrawObjects;
-
-// Global variables to store camera position
-extern int camera_x;
-extern int camera_y;
-
-void draw_icon_line(Canvas *canvas, Vector pos, int amount, bool horizontal, const Icon *icon);
-void draw_icon_half_world(Canvas *canvas, bool right, const Icon *icon);
-const Icon *get_icon(char *name);
-
-// create custom icons at https://lopaka.app/sandbox

+ 0 - 214
draw/world.c

@@ -1,214 +0,0 @@
-#include <draw/world.h>
-
-void draw_bounds(Canvas *canvas)
-{
-    // Draw the outer bounds adjusted by camera offset
-    // we draw this last to ensure users can see the bounds
-    canvas_draw_frame(canvas, -camera_x, -camera_y, WORLD_WIDTH, WORLD_HEIGHT);
-}
-
-void draw_example_world(Level *level)
-{
-    spawn_icon(level, &I_icon_earth, 112, 56, 15, 16);
-    spawn_icon(level, &I_icon_home, 128, 24, 15, 16);
-    spawn_icon(level, &I_icon_info, 144, 24, 15, 16);
-    spawn_icon(level, &I_icon_man, 160, 56, 7, 16);
-    spawn_icon(level, &I_icon_woman, 168, 56, 9, 16);
-    spawn_icon(level, &I_icon_plant, 168, 32, 16, 16);
-}
-
-/* JSON of the draw_example_world with fields icon, x, y, width, height
-{
-    "name": "Example World",
-    "author": "JBlanked",
-    "json_data": [
-        {
-            "icon": "earth",
-            "x": 112,
-            "y": 56,
-            "width": 15,
-            "height": 16
-        },
-        {
-            "icon": "home",
-            "x": 128,
-            "y": 24,
-            "width": 15,
-            "height": 16
-        },
-        {
-            "icon": "info",
-            "x": 144,
-            "y": 24,
-            "width": 15,
-            "height": 16
-        },
-        {
-            "icon": "man",
-            "x": 160,
-            "y": 56,
-            "width": 7,
-            "height": 16
-        },
-        {
-            "icon": "woman",
-            "x": 168,
-            "y": 56,
-            "width": 9,
-            "height": 16
-        },
-        {
-            "icon": "plant",
-            "x": 168,
-            "y": 32,
-            "width": 16,
-            "height": 16
-        }
-    ]
-}
-
-*/
-
-bool draw_json_world(Level *level, FuriString *json_data)
-{
-    for (int i = 0; i < MAX_WORLD_OBJECTS; i++)
-    {
-        char *data = get_json_array_value("json_data", i, (char *)furi_string_get_cstr(json_data), MAX_WORLD_TOKENS);
-        if (data == NULL)
-        {
-            break;
-        }
-        char *icon = get_json_value("icon", data, 64);
-        char *x = get_json_value("x", data, 64);
-        char *y = get_json_value("y", data, 64);
-        char *width = get_json_value("width", data, 64);
-        char *height = get_json_value("height", data, 64);
-        if (icon == NULL || x == NULL || y == NULL || width == NULL || height == NULL)
-        {
-            return false;
-        }
-        spawn_icon(level, get_icon(icon), atoi(x), atoi(y), atoi(width), atoi(height));
-    }
-    return true;
-}
-
-void draw_tree_world(Level *level)
-{
-    // Spawn two full left/up tree lines
-    for (int i = 0; i < 2; i++)
-    {
-        for (int j = 0; j < 22; j++)
-        {
-            spawn_icon(level, &I_icon_tree, 5 + j * 17, 2 + i * 17, 16, 16); // Horizontal lines
-        }
-        for (int j = 0; j < 11; j++)
-        {
-            spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + j * 17, 16, 16); // Vertical lines
-        }
-    }
-
-    // Spawn two full down tree lines
-    for (int i = 9; i < 11; i++)
-    {
-        for (int j = 0; j < 22; j++)
-        {
-            spawn_icon(level, &I_icon_tree, 5 + j * 17, 2 + i * 17, 16, 16); // Horizontal lines
-        }
-    }
-
-    // Spawn two full right tree lines
-    for (int i = 20; i < 22; i++)
-    {
-        for (int j = 0; j < 8; j++)
-        {
-            spawn_icon(level, &I_icon_tree, 5 + i * 17, 50 + j * 17, 16, 16); // Vertical lines
-        }
-    }
-
-    // Spawn labyrinth lines
-    // Third line (14 left, 3 middle, 0 right) - exit line
-    for (int i = 0; i < 14; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 2 * 17, 16, 16);
-    }
-    for (int i = 0; i < 3; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 16 * 17 + i * 17, 2 + 2 * 17, 16, 16);
-    }
-
-    // Fourth line (3 left, 6 middle, 4 right)
-    for (int i = 0; i < 3; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 3 * 17, 16, 16); // 3 left
-    }
-    for (int i = 0; i < 6; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 7 * 17 + i * 17, 2 + 3 * 17, 16, 16); // 6 middle
-    }
-    for (int i = 0; i < 4; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 15 * 17 + i * 17, 2 + 3 * 17, 16, 16); // 4 right
-    }
-
-    // Fifth line (6 left, 7 middle, 0 right)
-    for (int i = 0; i < 6; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 4 * 17, 16, 16); // 6 left
-    }
-    for (int i = 0; i < 7; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 7 * 17 + i * 17, 2 + 4 * 17, 16, 16); // 7 middle
-    }
-
-    // Sixth line (5 left, 6 middle, 7 right)
-    for (int i = 0; i < 5; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 5 * 17, 16, 16); // 5 left
-    }
-    for (int i = 0; i < 3; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 7 * 17 + i * 17, 2 + 5 * 17, 16, 16); // 3 middle
-    }
-    for (int i = 0; i < 7; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 15 * 17 + i * 17, 2 + 5 * 17, 16, 16); // 7 right
-    }
-
-    // Seventh line (0 left, 7 middle, 4 right)
-    for (int i = 0; i < 7; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 6 * 17 + i * 17, 2 + 6 * 17, 16, 16); // 7 middle
-    }
-    for (int i = 0; i < 4; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 14 * 17 + i * 17, 2 + 6 * 17, 16, 16); // 4 right
-    }
-
-    // Eighth line (4 left, 3 middle, 4 right)
-    for (int i = 0; i < 4; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 7 * 17, 16, 16); // 4 left
-    }
-    for (int i = 0; i < 3; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 7 * 17 + i * 17, 2 + 7 * 17, 16, 16); // 3 middle
-    }
-    for (int i = 0; i < 4; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 15 * 17 + i * 17, 2 + 7 * 17, 16, 16); // 4 right
-    }
-
-    // Ninth line (3 left, 2 middle, 3 right)
-    for (int i = 0; i < 3; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 8 * 17, 16, 16); // 3 left
-    }
-    for (int i = 0; i < 1; i++) // 2 middle
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 5 * 17 + i * 17, 2 + 8 * 17, 16, 16);
-    }
-    for (int i = 0; i < 3; i++)
-    {
-        spawn_icon(level, &I_icon_tree, 5 + 11 * 17 + i * 17, 2 + 8 * 17, 16, 16); // 3 right
-    }
-}

+ 0 - 8
draw/world.h

@@ -1,8 +0,0 @@
-#pragma once
-#include <draw/draw.h>
-#include <game.h>
-#include <flip_world.h>
-void draw_bounds(Canvas *canvas);
-void draw_example_world(Level *level);
-void draw_tree_world(Level *level);
-bool draw_json_world(Level *level, FuriString *json_data);

+ 1 - 0
easy_flipper/easy_flipper.h

@@ -24,6 +24,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <jsmn/jsmn.h>
+#include <jsmn/jsmn_furi.h>
 
 #define EASY_TAG "EasyFlipper"
 

+ 85 - 1
flip_storage/storage.c

@@ -229,6 +229,48 @@ bool save_world(
     return true;
 }
 
+bool save_world_furi(FuriString *name, FuriString *world_data)
+{
+    // Create the directory for saving settings
+    char directory_path[256];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
+
+    // Create the directory
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    storage_common_mkdir(storage, directory_path);
+
+    // Open the settings file
+    File *file = storage_file_alloc(storage);
+    char file_path[256];
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s.json", furi_string_get_cstr(name));
+
+    // Open the file in write mode
+    if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to open file for writing: %s", file_path);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Write the data to the file
+    size_t data_size = furi_string_size(world_data) + 1; // Include null terminator
+    if (storage_file_write(file, furi_string_get_cstr(world_data), data_size) != data_size)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return true;
+}
+
 bool load_world(
     const char *name,
     char *json_data,
@@ -274,7 +316,7 @@ FuriString *load_furi_world(
 {
     Storage *storage = furi_record_open(RECORD_STORAGE);
     File *file = storage_file_alloc(storage);
-    char file_path[256];
+    char file_path[128];
     snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s.json", name);
     // Open the file for reading
     if (!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))
@@ -360,3 +402,45 @@ bool world_exists(const char *name)
     furi_record_close(RECORD_STORAGE);
     return does_exist;
 }
+
+bool save_world_names(const FuriString *json)
+{
+    // Create the directory for saving settings
+    char directory_path[256];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
+
+    // Create the directory
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    storage_common_mkdir(storage, directory_path);
+
+    // Open the settings file
+    File *file = storage_file_alloc(storage);
+    char file_path[128];
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
+
+    // Open the file in write mode
+    if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to open file for writing: %s", file_path);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Write the data to the file
+    size_t data_size = furi_string_size(json) + 1; // Include null terminator
+    if (storage_file_write(file, furi_string_get_cstr(json), data_size) != data_size)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return true;
+}

+ 5 - 1
flip_storage/storage.h

@@ -28,6 +28,8 @@ bool save_world(
     const char *name,
     const char *world_data);
 
+bool save_world_furi(FuriString *name, FuriString *world_data);
+
 bool load_world(
     const char *name,
     char *json_data,
@@ -37,4 +39,6 @@ FuriString *load_furi_world(
     const char *name);
 
 bool world_exists(
-    const char *name);
+    const char *name);
+
+bool save_world_names(const FuriString *json);

+ 0 - 252
game.c

@@ -1,252 +0,0 @@
-#include "game.h"
-#include "flip_world.h"
-#include "flip_world_icons.h"
-
-// Background rendering function
-// TODO: each object needs a collision box so we can detect collisions and prevent movement through walls.
-static void background_render(Canvas *canvas, Vector pos)
-{
-    // Clear the canvas
-    canvas_clear(canvas);
-
-    // Calculate camera offset to center the player
-    camera_x = pos.x - (SCREEN_WIDTH / 2);
-    camera_y = pos.y - (SCREEN_HEIGHT / 2);
-
-    // Clamp camera position to prevent showing areas outside the world
-    camera_x = CLAMP(camera_x, WORLD_WIDTH - SCREEN_WIDTH, 0);
-    camera_y = CLAMP(camera_y, WORLD_HEIGHT - SCREEN_HEIGHT, 0);
-
-    // Draw the outer bounds adjusted by camera offset
-    draw_bounds(canvas);
-}
-
-/****** Entities: Player ******/
-
-typedef struct
-{
-    Vector trajectory; // Direction player would like to move.
-    float radius;      // collision radius
-    int8_t dx;         // x direction
-    int8_t dy;         // y direction
-    Sprite *sprite;    // player sprite
-} PlayerContext;
-
-// Forward declaration of player_desc, because it's used in player_spawn function.
-static const EntityDescription player_desc;
-
-static void player_spawn(Level *level, GameManager *manager)
-{
-    Entity *player = level_add_entity(level, &player_desc);
-
-    // Set player position.
-    // Depends on your game logic, it can be done in start entity function, but also can be done here.
-    entity_pos_set(player, (Vector){WORLD_WIDTH / 2, WORLD_HEIGHT / 2});
-
-    // Add collision box to player entity
-    // Box is centered in player x and y, and it's size is 10x10
-    entity_collider_add_rect(player, 10, 10);
-
-    // Get player context
-    PlayerContext *player_context = entity_context_get(player);
-
-    // Load player sprite
-    player_context->sprite = game_manager_sprite_load(manager, "player.fxbm");
-}
-
-// Modify player_update to track direction
-static void player_update(Entity *self, GameManager *manager, void *context)
-{
-    PlayerContext *player = (PlayerContext *)context;
-    InputState input = game_manager_input_get(manager);
-    Vector pos = entity_pos_get(self);
-
-    // Reset direction each frame
-    player->dx = 0;
-    player->dy = 0;
-
-    if (input.held & GameKeyUp)
-    {
-        pos.y -= 2;
-        player->dy = -1;
-    }
-    if (input.held & GameKeyDown)
-    {
-        pos.y += 2;
-        player->dy = 1;
-    }
-    if (input.held & GameKeyLeft)
-    {
-        pos.x -= 2;
-        player->dx = -1;
-    }
-    if (input.held & GameKeyRight)
-    {
-        pos.x += 2;
-        player->dx = 1;
-    }
-
-    pos.x = CLAMP(pos.x, WORLD_WIDTH - 5, 5);
-    pos.y = CLAMP(pos.y, WORLD_HEIGHT - 5, 5);
-
-    entity_pos_set(self, pos);
-
-    if (input.pressed & GameKeyBack)
-    {
-        game_manager_game_stop(manager);
-    }
-}
-
-static void player_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
-{
-    // Get player context
-    UNUSED(manager);
-    PlayerContext *player = context;
-
-    // Get player position
-    Vector pos = entity_pos_get(self);
-
-    // Draw background (updates camera_x and camera_y)
-    background_render(canvas, pos);
-
-    // Draw player sprite relative to camera
-    canvas_draw_sprite(canvas, player->sprite, pos.x - camera_x - 5, pos.y - camera_y - 5);
-}
-
-static const EntityDescription player_desc = {
-    .start = NULL,                         // called when entity is added to the level
-    .stop = NULL,                          // called when entity is removed from the level
-    .update = player_update,               // called every frame
-    .render = player_render,               // called every frame, after update
-    .collision = NULL,                     // called when entity collides with another entity
-    .event = NULL,                         // called when entity receives an event
-    .context_size = sizeof(PlayerContext), // size of entity context, will be automatically allocated and freed
-};
-
-/****** Level ******/
-
-static void level_alloc(Level *level, GameManager *manager, void *context)
-{
-    UNUSED(manager);
-    UNUSED(context);
-
-    // Add player entity to the level
-    player_spawn(level, manager);
-
-    draw_tree_world(level);
-    // draw_example_world(level);
-}
-
-static const LevelBehaviour level = {
-    .alloc = level_alloc, // called once, when level allocated
-    .free = NULL,         // called once, when level freed
-    .start = NULL,        // called when level is changed to this level
-    .stop = NULL,         // called when level is changed from this level
-    .context_size = 0,    // size of level context, will be automatically allocated and freed
-};
-
-// Forward declaration of icon_desc
-static const EntityDescription icon_desc;
-
-static void icon_collision(Entity *self, Entity *other, GameManager *manager, void *context)
-{
-    UNUSED(manager);
-    UNUSED(self);
-    IconContext *icon = (IconContext *)context;
-    UNUSED(icon);
-    if (entity_description_get(other) == &player_desc)
-    {
-        PlayerContext *player = (PlayerContext *)entity_context_get(other);
-        if (player)
-        {
-            Vector pos = entity_pos_get(other);
-            // Bounce the player back by 3 units opposite their last movement direction
-            pos.x -= player->dx * 3;
-            pos.y -= player->dy * 3;
-            entity_pos_set(other, pos);
-        }
-    }
-}
-
-static void icon_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
-{
-    UNUSED(manager);
-    IconContext *icon_ctx = (IconContext *)context;
-    Vector pos = entity_pos_get(self);
-    canvas_draw_icon(canvas, pos.x - camera_x - 8, pos.y - camera_y - 8, icon_ctx->icon);
-}
-
-static void icon_start(Entity *self, GameManager *manager, void *context)
-{
-    UNUSED(manager);
-    UNUSED(context);
-    // Just add the collision rectangle for 16x16 icon
-    entity_collider_add_rect(self, 16, 16);
-}
-
-static const EntityDescription icon_desc = {
-    .start = icon_start,
-    .stop = NULL,
-    .update = NULL,
-    .render = icon_render,
-    .collision = icon_collision,
-    .event = NULL,
-    .context_size = sizeof(IconContext),
-};
-
-// Helper function to spawn an icon entity at a given position
-void spawn_icon(Level *level, const Icon *icon, float x, float y, uint8_t width, uint8_t height)
-{
-    Entity *e = level_add_entity(level, &icon_desc);
-    IconContext *icon_ctx = entity_context_get(e);
-    icon_ctx->icon = icon;
-    icon_ctx->width = width;
-    icon_ctx->height = height;
-    // Set the entity position to the center of the icon
-    entity_pos_set(e, (Vector){x + 8, y + 8});
-}
-
-/****** Game ******/
-
-/*
-    Write here the start code for your game, for example: creating a level and so on.
-    Game context is allocated (game.context_size) and passed to this function, you can use it to store your game data.
-*/
-static void game_start(GameManager *game_manager, void *ctx)
-{
-    UNUSED(game_manager);
-
-    // Do some initialization here, for example you can load score from storage.
-    // For simplicity, we will just set it to 0.
-    GameContext *game_context = ctx;
-    game_context->score = 0;
-
-    // Add level to the game
-    game_manager_add_level(game_manager, &level);
-}
-
-/*
-    Write here the stop code for your game, for example, freeing memory, if it was allocated.
-    You don't need to free level, sprites or entities, it will be done automatically.
-    Also, you don't need to free game_context, it will be done automatically, after this function.
-*/
-static void game_stop(void *ctx)
-{
-    UNUSED(ctx);
-    // GameContext *game_context = ctx;
-    //  Do some deinitialization here, for example you can save score to storage.
-    //  For simplicity, we will just print it.
-    // FURI_LOG_I("Game", "Your score: %lu", game_context->score);
-}
-
-/*
-    Your game configuration, do not rename this variable, but you can change its content here.
-*/
-const Game game = {
-    .target_fps = 60,                    // target fps, game will try to keep this value
-    .show_fps = false,                   // show fps counter on the screen
-    .always_backlight = true,            // keep display backlight always on
-    .start = game_start,                 // will be called once, when game starts
-    .stop = game_stop,                   // will be called once, when game stops
-    .context_size = sizeof(GameContext), // size of game context
-};

+ 0 - 17
game.h

@@ -1,17 +0,0 @@
-#pragma once
-#include "engine/engine.h"
-#include <draw/world.h>
-
-void spawn_icon(Level *level, const Icon *icon, float x, float y, uint8_t width, uint8_t height);
-
-typedef struct
-{
-    uint32_t score;
-} GameContext;
-
-typedef struct
-{
-    const Icon *icon;
-    uint8_t width;
-    uint8_t height;
-} IconContext;

+ 3 - 12
game/game.c

@@ -68,7 +68,7 @@ static void player_update(Entity *self, GameManager *manager, void *context)
     if (input.held & GameKeyOk)
     {
         game_manager_next_level_set(manager, game_manager_current_level_get(manager) == level_tree ? level_example : level_tree);
-        furi_delay_ms(1000);
+        furi_delay_ms(500);
         return;
     }
 
@@ -138,21 +138,12 @@ static void game_start(GameManager *game_manager, void *ctx)
     game_context->score = 0;
 
     // load all levels
-    // if (level_load_all())
-    // {
-    //     // loop through all levels and add them to the game
-    //     for (int i = level_count; i > 0; i--)
-    //     {
-    //         levels[i] = game_manager_add_level(game_manager, level_behaviors[i]);
-    //     }
-    // }
-    // else
+    // if (!level_load_all(game_manager))
     // {
     //     FURI_LOG_E("Game", "Failed to load levels");
     //     easy_flipper_dialog("[LEVEL ERROR]", "No level data installed.\n\n\nSettings -> Game ->\nInstall Official Level Pack");
-    //     game_manager_add_level(game_manager, &example_level);
+    //     game_manager_add_level(game_manager, &tree_level);
     // }
-
     level_tree = game_manager_add_level(game_manager, &tree_level);
     level_example = game_manager_add_level(game_manager, &example_level);
 }

+ 245 - 33
game/icon.c

@@ -1,36 +1,5 @@
 #include "game/icon.h"
-const Icon *get_icon(char *name)
-{
-    if (strcmp(name, "earth") == 0)
-    {
-        return &I_icon_earth;
-    }
-    if (strcmp(name, "home") == 0)
-    {
-        return &I_icon_home;
-    }
-    if (strcmp(name, "info") == 0)
-    {
-        return &I_icon_info;
-    }
-    if (strcmp(name, "man") == 0)
-    {
-        return &I_icon_man;
-    }
-    if (strcmp(name, "plant") == 0)
-    {
-        return &I_icon_plant;
-    }
-    if (strcmp(name, "tree") == 0)
-    {
-        return &I_icon_tree;
-    }
-    if (strcmp(name, "woman") == 0)
-    {
-        return &I_icon_woman;
-    }
-    return NULL;
-}
+
 // Icon entity description
 
 static void icon_collision(Entity *self, Entity *other, GameManager *manager, void *context)
@@ -83,4 +52,247 @@ const EntityDescription icon_desc = {
     .collision = icon_collision,
     .event = NULL,
     .context_size = sizeof(IconContext),
-};
+};
+
+const Icon *get_icon(char *name)
+{
+    if (strcmp(name, "earth") == 0)
+    {
+        return &I_icon_earth_15x16;
+    }
+    if (strcmp(name, "home") == 0)
+    {
+        return &I_icon_home_15x16;
+    }
+    if (strcmp(name, "house") == 0)
+    {
+        return &I_icon_house_48x48px;
+    }
+    if (strcmp(name, "house_3d") == 0)
+    {
+        return &I_icon_house_3d_34x45px;
+    }
+    if (strcmp(name, "info") == 0)
+    {
+        return &I_icon_info_15x16;
+    }
+    if (strcmp(name, "man") == 0)
+    {
+        return &I_icon_man_7x16;
+    }
+    if (strcmp(name, "plant") == 0)
+    {
+        return &I_icon_plant_16x16;
+    }
+    if (strcmp(name, "plant_fern") == 0)
+    {
+        return &I_icon_plant_fern_18x16px;
+    }
+    if (strcmp(name, "plant_pointy") == 0)
+    {
+        return &I_icon_plant_pointy_13x16px;
+    }
+    if (strcmp(name, "tree") == 0)
+    {
+        return &I_icon_tree_16x16;
+    }
+    if (strcmp(name, "tree_29x30") == 0)
+    {
+        return &I_icon_tree_29x30px;
+    }
+    if (strcmp(name, "tree_48x48") == 0)
+    {
+        return &I_icon_tree_48x48px;
+    }
+    if (strcmp(name, "woman") == 0)
+    {
+        return &I_icon_woman_9x16;
+    }
+    if (strcmp(name, "chest_closed") == 0)
+    {
+        return &I_icon_chest_closed_16x13px;
+    }
+    if (strcmp(name, "chest_open") == 0)
+    {
+        return &I_icon_chest_open_16x16px;
+    }
+    if (strcmp(name, "fence") == 0)
+    {
+        return &I_icon_fence_16x8px;
+    }
+    if (strcmp(name, "fence_end") == 0)
+    {
+        return &I_icon_fence_end_16x8px;
+    }
+    if (strcmp(name, "flower") == 0)
+    {
+        return &I_icon_flower_16x16;
+    }
+    if (strcmp(name, "lake_bottom") == 0)
+    {
+        return &I_icon_lake_bottom_31x31px;
+    }
+    if (strcmp(name, "lake_bottom_left") == 0)
+    {
+        return &I_icon_lake_bottom_left_31x31px;
+    }
+    if (strcmp(name, "lake_bottom_right") == 0)
+    {
+        return &I_icon_lake_bottom_right_31x31px;
+    }
+    if (strcmp(name, "lake_left") == 0)
+    {
+        return &I_icon_lake_left_31x31;
+    }
+    if (strcmp(name, "lake_right") == 0)
+    {
+        return &I_icon_lake_right_31x31px;
+    }
+    if (strcmp(name, "lake_top") == 0)
+    {
+        return &I_icon_lake_top_31x31px;
+    }
+    if (strcmp(name, "lake_top_left") == 0)
+    {
+        return &I_icon_lake_top_left_31x31px;
+    }
+    if (strcmp(name, "lake_top_right") == 0)
+    {
+        return &I_icon_lake_top_right_31x31px;
+    }
+    if (strcmp(name, "rock_large") == 0)
+    {
+        return &I_icon_rock_large_18x19px;
+    }
+    if (strcmp(name, "rock_medium") == 0)
+    {
+        return &I_icon_rock_medium_16x14px;
+    }
+    if (strcmp(name, "rock_small") == 0)
+    {
+        return &I_icon_rock_small_10x8px;
+    }
+
+    // If no match is found
+    return NULL;
+}
+const Icon *get_icon_furi(FuriString *name)
+{
+    if (furi_string_cmp(name, "earth") == 0)
+    {
+        return &I_icon_earth_15x16;
+    }
+    if (furi_string_cmp(name, "home") == 0)
+    {
+        return &I_icon_home_15x16;
+    }
+    if (furi_string_cmp(name, "house") == 0)
+    {
+        return &I_icon_house_48x48px;
+    }
+    if (furi_string_cmp(name, "house_3d") == 0)
+    {
+        return &I_icon_house_3d_34x45px;
+    }
+    if (furi_string_cmp(name, "info") == 0)
+    {
+        return &I_icon_info_15x16;
+    }
+    if (furi_string_cmp(name, "man") == 0)
+    {
+        return &I_icon_man_7x16;
+    }
+    if (furi_string_cmp(name, "plant") == 0)
+    {
+        return &I_icon_plant_16x16;
+    }
+    if (furi_string_cmp(name, "plant_fern") == 0)
+    {
+        return &I_icon_plant_fern_18x16px;
+    }
+    if (furi_string_cmp(name, "plant_pointy") == 0)
+    {
+        return &I_icon_plant_pointy_13x16px;
+    }
+    if (furi_string_cmp(name, "tree") == 0)
+    {
+        return &I_icon_tree_16x16;
+    }
+    if (furi_string_cmp(name, "tree_29x30") == 0)
+    {
+        return &I_icon_tree_29x30px;
+    }
+    if (furi_string_cmp(name, "tree_48x48") == 0)
+    {
+        return &I_icon_tree_48x48px;
+    }
+    if (furi_string_cmp(name, "woman") == 0)
+    {
+        return &I_icon_woman_9x16;
+    }
+    if (furi_string_cmp(name, "chest_closed") == 0)
+    {
+        return &I_icon_chest_closed_16x13px;
+    }
+    if (furi_string_cmp(name, "chest_open") == 0)
+    {
+        return &I_icon_chest_open_16x16px;
+    }
+    if (furi_string_cmp(name, "fence") == 0)
+    {
+        return &I_icon_fence_16x8px;
+    }
+    if (furi_string_cmp(name, "fence_end") == 0)
+    {
+        return &I_icon_fence_end_16x8px;
+    }
+    if (furi_string_cmp(name, "flower") == 0)
+    {
+        return &I_icon_flower_16x16;
+    }
+    if (furi_string_cmp(name, "lake_bottom") == 0)
+    {
+        return &I_icon_lake_bottom_31x31px;
+    }
+    if (furi_string_cmp(name, "lake_bottom_left") == 0)
+    {
+        return &I_icon_lake_bottom_left_31x31px;
+    }
+    if (furi_string_cmp(name, "lake_bottom_right") == 0)
+    {
+        return &I_icon_lake_bottom_right_31x31px;
+    }
+    if (furi_string_cmp(name, "lake_left") == 0)
+    {
+        return &I_icon_lake_left_31x31;
+    }
+    if (furi_string_cmp(name, "lake_right") == 0)
+    {
+        return &I_icon_lake_right_31x31px;
+    }
+    if (furi_string_cmp(name, "lake_top") == 0)
+    {
+        return &I_icon_lake_top_31x31px;
+    }
+    if (furi_string_cmp(name, "lake_top_left") == 0)
+    {
+        return &I_icon_lake_top_left_31x31px;
+    }
+    if (furi_string_cmp(name, "lake_top_right") == 0)
+    {
+        return &I_icon_lake_top_right_31x31px;
+    }
+    if (furi_string_cmp(name, "rock_large") == 0)
+    {
+        return &I_icon_rock_large_18x19px;
+    }
+    if (furi_string_cmp(name, "rock_medium") == 0)
+    {
+        return &I_icon_rock_medium_16x14px;
+    }
+    if (furi_string_cmp(name, "rock_small") == 0)
+    {
+        return &I_icon_rock_small_10x8px;
+    }
+    return NULL;
+}

+ 2 - 1
game/icon.h

@@ -12,4 +12,5 @@ typedef struct
 } IconContext;
 
 extern const EntityDescription icon_desc;
-const Icon *get_icon(char *name);
+const Icon *get_icon(char *name);
+const Icon *get_icon_furi(FuriString *name);

+ 53 - 58
game/level.c

@@ -7,19 +7,31 @@ static void level_start(Level *level, GameManager *manager, void *context)
     UNUSED(manager);
     LevelContext *level_context = context;
     // check if the world exists
-    if (!world_exists(level_context->id))
-    {
-        FURI_LOG_E("Game", "World does not exist");
-        easy_flipper_dialog("[WORLD ERROR]", "No world data installed.\n\n\nSettings -> Game ->\nInstall Official World Pack");
-        draw_example_world(level);
-        return;
-    }
+    // if (!world_exists(level_context->id))
+    // {
+    //     FURI_LOG_E("Game", "World does not exist");
+    //     easy_flipper_dialog("[WORLD ERROR]", "No world data installed.\n\n\nSettings -> Game ->\nInstall Official World Pack");
+    //     draw_example_world(level);
+    //     return;
+    // }
     // draw the world
-    if (!draw_json_world_furi(level, load_furi_world(level_context->id)))
-    {
-        FURI_LOG_E("Game", "World exists but failed to draw.");
+    // FuriString *world_data = load_furi_world(level_context->id);
+    // if (!world_data)
+    // {
+    //     FURI_LOG_E("Game", "Failed to load world data");
+    //     draw_example_world(level);
+    //     return;
+    // }
+    // if (!draw_json_world_furi(level, load_furi_world(level_context->id)))
+    // {
+    //     FURI_LOG_E("Game", "World exists but failed to draw.");
+    //     draw_example_world(level);
+    // }
+    // furi_string_free(world_data);
+    if (level_context->index == 0)
+        draw_tree_world(level);
+    else
         draw_example_world(level);
-    }
 }
 
 static void level_alloc_tree_world(Level *level, GameManager *manager, void *context)
@@ -52,11 +64,11 @@ const LevelBehaviour tree_level = {
     .context_size = sizeof(LevelContext), // size of level context, will be automatically allocated and freed
 };
 const LevelBehaviour example_level = {
-    .alloc = level_alloc_example_world,   // called once, when level allocated
-    .free = NULL,                         // called once, when level freed
-    .start = level_start,                 // called when level is changed to this level
-    .stop = NULL,                         // called when level is changed from this level
-    .context_size = sizeof(LevelContext), // size of level context, will be automatically allocated and freed
+    .alloc = level_alloc_example_world,
+    .free = NULL,
+    .start = level_start,
+    .stop = NULL,
+    .context_size = sizeof(LevelContext),
 };
 
 void level_alloc_world(Level *level, GameManager *manager, void *context)
@@ -69,70 +81,53 @@ void level_alloc_world(Level *level, GameManager *manager, void *context)
     player_spawn(level, manager);
 }
 
-bool level_load_all()
+bool level_load_all(GameManager *game_manager)
 {
     char file_path[128];
     snprintf(
         file_path,
         sizeof(file_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds.json");
-
-    FuriString *world_data = flipper_http_load_from_file(file_path);
-    if (!world_data)
-    {
-        FURI_LOG_E(TAG, "Failed to load world data");
-        return false;
-    }
+        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
 
-    const char *json_data = furi_string_get_cstr(world_data);
-    if (!json_data)
+    FuriString *world_list = flipper_http_load_from_file(file_path);
+    if (!world_list)
     {
-        FURI_LOG_E(TAG, "Failed to get world data");
-        furi_string_free(world_data);
+        FURI_LOG_E(TAG, "Failed to load world list");
         return false;
     }
-
-    const LevelBehaviour new_behavior = {
-        .alloc = level_alloc_world,
-        .free = NULL,
-        .start = level_start,
-        .stop = NULL,
-        .context_size = sizeof(LevelContext),
-    };
-
     for (int i = 0; i < 10; i++)
     {
-        char *json = get_json_array_value("worlds", i, json_data, 1024);
-        if (!json)
+        FuriString *world_id = get_json_array_value_furi("names", i, world_list);
+        if (!world_id)
         {
-            FURI_LOG_E(TAG, "Failed to get worlds. Data likely empty");
+            FURI_LOG_E(TAG, "Failed to get world id");
             break;
         }
-
-        char *world_id = get_json_value("name", json, 1024);
-        if (!world_id)
+        snprintf(level_contexts[i].id, sizeof(level_contexts[i].id), "%s", furi_string_get_cstr(world_id));
+        level_contexts[i].index = i;
+        LevelBehaviour *new_behavior = malloc(sizeof(LevelBehaviour));
+        if (!new_behavior)
         {
-            FURI_LOG_E(TAG, "Failed to get world id");
-            furi_string_free(world_data);
-            free(json);
+            FURI_LOG_E(TAG, "Failed to allocate memory for level behavior");
+            furi_string_free(world_id);
+            furi_string_free(world_list);
             return false;
         }
-
-        snprintf(level_contexts[i].id, sizeof(level_contexts[i].id), "%s", "example_world");
-        // safely copy the i value to the index
-        level_contexts[i].index = i;
-        level_behaviors[i] = &new_behavior;
+        *new_behavior = (LevelBehaviour){
+            .alloc = level_alloc_world,
+            .free = NULL,
+            .start = level_start,
+            .stop = NULL,
+            .context_size = sizeof(LevelContext),
+        };
         level_count++;
-        free(json);
-        free(world_id);
+        game_manager_add_level(game_manager, new_behavior);
+        furi_string_free(world_id);
     }
-
-    furi_string_free(world_data);
+    furi_string_free(world_list);
     return true;
 }
 
-// array of LevelBehaviour structures
-const LevelBehaviour *level_behaviors[10] = {0};
 LevelContext level_contexts[] = {0};
 Level *levels[] = {0};
 int level_count = 0;

+ 2 - 3
game/level.h

@@ -8,12 +8,11 @@ typedef struct
 } LevelContext;
 
 extern Level *level_tree;
-extern Level *level_example;
 extern const LevelBehaviour tree_level;
+extern Level *level_example;
 extern const LevelBehaviour example_level;
-extern const LevelBehaviour *level_behaviors[10];
 extern LevelContext level_contexts[];
 extern Level *levels[];
 extern int level_count;
 
-bool level_load_all();
+bool level_load_all(GameManager *game_manager);

+ 79 - 64
game/world.c

@@ -11,18 +11,18 @@ bool draw_json_world(Level *level, const char *json_data)
 {
     for (int i = 0; i < MAX_WORLD_OBJECTS; i++)
     {
-        char *data = get_json_array_value("json_data", i, json_data, MAX_WORLD_TOKENS);
+        char *data = get_json_array_value("json_data", i, json_data);
         if (data == NULL)
         {
             break;
         }
-        char *icon = get_json_value("icon", data, 64);
-        char *x = get_json_value("x", data, 64);
-        char *y = get_json_value("y", data, 64);
-        char *width = get_json_value("width", data, 64);
-        char *height = get_json_value("height", data, 64);
-        char *amount = get_json_value("amount", data, 64);
-        char *horizontal = get_json_value("horizontal", data, 64);
+        char *icon = get_json_value("icon", data);
+        char *x = get_json_value("x", data);
+        char *y = get_json_value("y", data);
+        char *width = get_json_value("width", data);
+        char *height = get_json_value("height", data);
+        char *amount = get_json_value("amount", data);
+        char *horizontal = get_json_value("horizontal", data);
         if (icon == NULL || x == NULL || y == NULL || width == NULL || height == NULL || amount == NULL || horizontal == NULL)
         {
             return false;
@@ -57,57 +57,72 @@ bool draw_json_world_furi(Level *level, FuriString *json_data)
 {
     for (int i = 0; i < MAX_WORLD_OBJECTS; i++)
     {
-        char *data = get_json_array_value("json_data", i, furi_string_get_cstr(json_data), MAX_WORLD_TOKENS);
+        FuriString *data = get_json_array_value_furi("json_data", i, json_data);
         if (data == NULL)
         {
             break;
         }
-        char *icon = get_json_value("icon", data, 64);
-        char *x = get_json_value("x", data, 64);
-        char *y = get_json_value("y", data, 64);
-        char *width = get_json_value("width", data, 64);
-        char *height = get_json_value("height", data, 64);
-        char *amount = get_json_value("amount", data, 64);
-        char *horizontal = get_json_value("horizontal", data, 64);
-        if (icon == NULL || x == NULL || y == NULL || width == NULL || height == NULL || amount == NULL || horizontal == NULL)
+        FuriString *icon = get_json_value_furi("icon", data);
+        FuriString *x = get_json_value_furi("x", data);
+        FuriString *y = get_json_value_furi("y", data);
+        FuriString *width = get_json_value_furi("width", data);
+        FuriString *height = get_json_value_furi("height", data);
+        FuriString *amount = get_json_value_furi("amount", data);
+        FuriString *horizontal = get_json_value_furi("horizontal", data);
+        if (!icon || !x || !y || !width || !height || !amount || !horizontal)
         {
+            furi_string_free(data);
             return false;
         }
         // if amount is less than 2, we spawn a single icon
-        if (atoi(amount) < 2)
+        if (atoi(furi_string_get_cstr(amount)) < 2)
         {
-            spawn_icon(level, get_icon(icon), atoi(x), atoi(y), atoi(width), atoi(height));
-            free(data);
-            free(icon);
-            free(x);
-            free(y);
-            free(width);
-            free(height);
-            free(amount);
-            free(horizontal);
+            spawn_icon(
+                level,
+                get_icon_furi(icon),
+                atoi(furi_string_get_cstr(x)),
+                atoi(furi_string_get_cstr(y)),
+                atoi(furi_string_get_cstr(width)),
+                atoi(furi_string_get_cstr(height)));
+            furi_string_free(data);
+            furi_string_free(icon);
+            furi_string_free(x);
+            furi_string_free(y);
+            furi_string_free(width);
+            furi_string_free(height);
+            furi_string_free(amount);
+            furi_string_free(horizontal);
             continue;
         }
-        spawn_icon_line(level, get_icon(icon), atoi(x), atoi(y), atoi(width), atoi(height), atoi(amount), strcmp(horizontal, "true") == 0);
-        free(data);
-        free(icon);
-        free(x);
-        free(y);
-        free(width);
-        free(height);
-        free(amount);
-        free(horizontal);
+        spawn_icon_line(
+            level,
+            get_icon_furi(icon),
+            atoi(furi_string_get_cstr(x)),
+            atoi(furi_string_get_cstr(y)),
+            atoi(furi_string_get_cstr(width)),
+            atoi(furi_string_get_cstr(height)),
+            atoi(furi_string_get_cstr(amount)),
+            furi_string_cmp(horizontal, "true") == 0);
+        furi_string_free(data);
+        furi_string_free(icon);
+        furi_string_free(x);
+        furi_string_free(y);
+        furi_string_free(width);
+        furi_string_free(height);
+        furi_string_free(amount);
+        furi_string_free(horizontal);
     }
     return true;
 }
 
 void draw_example_world(Level *level)
 {
-    spawn_icon(level, &I_icon_earth, 112, 56, 15, 16);
-    spawn_icon(level, &I_icon_home, 128, 24, 15, 16);
-    spawn_icon(level, &I_icon_info, 144, 24, 15, 16);
-    spawn_icon(level, &I_icon_man, 160, 56, 7, 16);
-    spawn_icon(level, &I_icon_woman, 168, 56, 9, 16);
-    spawn_icon(level, &I_icon_plant, 168, 32, 16, 16);
+    spawn_icon(level, get_icon("earth"), 112, 56, 15, 16);
+    spawn_icon(level, get_icon("home"), 128, 24, 15, 16);
+    spawn_icon(level, get_icon("info"), 144, 24, 15, 16);
+    spawn_icon(level, get_icon("man"), 160, 56, 7, 16);
+    spawn_icon(level, get_icon("woman"), 168, 56, 9, 16);
+    spawn_icon(level, get_icon("plant"), 168, 32, 16, 16);
 }
 
 void draw_tree_world(Level *level)
@@ -116,55 +131,55 @@ void draw_tree_world(Level *level)
     for (int i = 0; i < 2; i++)
     {
         // Horizontal line of 22 icons
-        spawn_icon_line(level, &I_icon_tree, 5, 2 + i * 17, 16, 16, 22, true);
+        spawn_icon_line(level, get_icon("tree"), 5, 2 + i * 17, 16, 16, 22, true);
         // Vertical line of 11 icons
-        spawn_icon_line(level, &I_icon_tree, 5 + i * 17, 2, 16, 16, 11, false);
+        spawn_icon_line(level, get_icon("tree"), 5 + i * 17, 2, 16, 16, 11, false);
     }
 
     // Spawn two full down tree lines
     for (int i = 9; i < 11; i++)
     {
         // Horizontal line of 22 icons
-        spawn_icon_line(level, &I_icon_tree, 5, 2 + i * 17, 16, 16, 22, true);
+        spawn_icon_line(level, get_icon("tree"), 5, 2 + i * 17, 16, 16, 22, true);
     }
 
     // Spawn two full right tree lines
     for (int i = 20; i < 22; i++)
     {
         // Vertical line of 8 icons starting further down (y=50)
-        spawn_icon_line(level, &I_icon_tree, 5 + i * 17, 50, 16, 16, 8, false);
+        spawn_icon_line(level, get_icon("tree"), 5 + i * 17, 50, 16, 16, 8, false);
     }
 
     // Labyrinth lines
     // Third line (14 left, then a gap, then 3 middle)
-    spawn_icon_line(level, &I_icon_tree, 5, 2 + 2 * 17, 16, 16, 14, true);
-    spawn_icon_line(level, &I_icon_tree, 5 + 16 * 17, 2 + 2 * 17, 16, 16, 3, true);
+    spawn_icon_line(level, get_icon("tree"), 5, 2 + 2 * 17, 16, 16, 14, true);
+    spawn_icon_line(level, get_icon("tree"), 5 + 16 * 17, 2 + 2 * 17, 16, 16, 3, true);
 
     // Fourth line (3 left, 6 middle, 4 right)
-    spawn_icon_line(level, &I_icon_tree, 5, 2 + 3 * 17, 16, 16, 3, true);           // 3 left
-    spawn_icon_line(level, &I_icon_tree, 5 + 7 * 17, 2 + 3 * 17, 16, 16, 6, true);  // 6 middle
-    spawn_icon_line(level, &I_icon_tree, 5 + 15 * 17, 2 + 3 * 17, 16, 16, 4, true); // 4 right
+    spawn_icon_line(level, get_icon("tree"), 5, 2 + 3 * 17, 16, 16, 3, true);           // 3 left
+    spawn_icon_line(level, get_icon("tree"), 5 + 7 * 17, 2 + 3 * 17, 16, 16, 6, true);  // 6 middle
+    spawn_icon_line(level, get_icon("tree"), 5 + 15 * 17, 2 + 3 * 17, 16, 16, 4, true); // 4 right
 
     // Fifth line (6 left, 7 middle)
-    spawn_icon_line(level, &I_icon_tree, 5, 2 + 4 * 17, 16, 16, 6, true);
-    spawn_icon_line(level, &I_icon_tree, 5 + 7 * 17, 2 + 4 * 17, 16, 16, 7, true);
+    spawn_icon_line(level, get_icon("tree"), 5, 2 + 4 * 17, 16, 16, 6, true);
+    spawn_icon_line(level, get_icon("tree"), 5 + 7 * 17, 2 + 4 * 17, 16, 16, 7, true);
 
     // Sixth line (5 left, 3 middle, 7 right)
-    spawn_icon_line(level, &I_icon_tree, 5, 2 + 5 * 17, 16, 16, 5, true);           // 5 left
-    spawn_icon_line(level, &I_icon_tree, 5 + 7 * 17, 2 + 5 * 17, 16, 16, 3, true);  // 3 middle
-    spawn_icon_line(level, &I_icon_tree, 5 + 15 * 17, 2 + 5 * 17, 16, 16, 7, true); // 7 right
+    spawn_icon_line(level, get_icon("tree"), 5, 2 + 5 * 17, 16, 16, 5, true);           // 5 left
+    spawn_icon_line(level, get_icon("tree"), 5 + 7 * 17, 2 + 5 * 17, 16, 16, 3, true);  // 3 middle
+    spawn_icon_line(level, get_icon("tree"), 5 + 15 * 17, 2 + 5 * 17, 16, 16, 7, true); // 7 right
 
     // Seventh line (0 left, 7 middle, 4 right)
-    spawn_icon_line(level, &I_icon_tree, 5 + 6 * 17, 2 + 6 * 17, 16, 16, 7, true);  // 7 middle
-    spawn_icon_line(level, &I_icon_tree, 5 + 14 * 17, 2 + 6 * 17, 16, 16, 4, true); // 4 right
+    spawn_icon_line(level, get_icon("tree"), 5 + 6 * 17, 2 + 6 * 17, 16, 16, 7, true);  // 7 middle
+    spawn_icon_line(level, get_icon("tree"), 5 + 14 * 17, 2 + 6 * 17, 16, 16, 4, true); // 4 right
 
     // Eighth line (4 left, 3 middle, 4 right)
-    spawn_icon_line(level, &I_icon_tree, 5, 2 + 7 * 17, 16, 16, 4, true);           // 4 left
-    spawn_icon_line(level, &I_icon_tree, 5 + 7 * 17, 2 + 7 * 17, 16, 16, 3, true);  // 3 middle
-    spawn_icon_line(level, &I_icon_tree, 5 + 15 * 17, 2 + 7 * 17, 16, 16, 4, true); // 4 right
+    spawn_icon_line(level, get_icon("tree"), 5, 2 + 7 * 17, 16, 16, 4, true);           // 4 left
+    spawn_icon_line(level, get_icon("tree"), 5 + 7 * 17, 2 + 7 * 17, 16, 16, 3, true);  // 3 middle
+    spawn_icon_line(level, get_icon("tree"), 5 + 15 * 17, 2 + 7 * 17, 16, 16, 4, true); // 4 right
 
     // Ninth line (3 left, 1 middle, 3 right)
-    spawn_icon_line(level, &I_icon_tree, 5, 2 + 8 * 17, 16, 16, 3, true);           // 3 left
-    spawn_icon_line(level, &I_icon_tree, 5 + 5 * 17, 2 + 8 * 17, 16, 16, 1, true);  // 1 middle
-    spawn_icon_line(level, &I_icon_tree, 5 + 11 * 17, 2 + 8 * 17, 16, 16, 3, true); // 3 right
+    spawn_icon_line(level, get_icon("tree"), 5, 2 + 8 * 17, 16, 16, 3, true);           // 3 left
+    spawn_icon_line(level, get_icon("tree"), 5 + 5 * 17, 2 + 8 * 17, 16, 16, 1, true);  // 1 middle
+    spawn_icon_line(level, get_icon("tree"), 5 + 11 * 17, 2 + 8 * 17, 16, 16, 3, true); // 3 right
 }

+ 23 - 10
jsmn/jsmn.c

@@ -7,8 +7,6 @@
  */
 
 #include <jsmn/jsmn.h>
-#include <stdlib.h>
-#include <string.h>
 
 /**
  * Allocates a fresh unused token from the token pool.
@@ -448,14 +446,14 @@ int jsoneq(const char *json, jsmntok_t *tok, const char *s)
 }
 
 // Return the value of the key in the JSON data
-char *get_json_value(char *key, const char *json_data, uint32_t max_tokens)
+char *get_json_value(char *key, const char *json_data)
 {
     // Parse the JSON feed
     if (json_data != NULL)
     {
         jsmn_parser parser;
         jsmn_init(&parser);
-
+        uint32_t max_tokens = json_token_count(json_data);
         // Allocate tokens array on the heap
         jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
         if (tokens == NULL)
@@ -562,19 +560,19 @@ static int skip_token(const jsmntok_t *tokens, int start, int total)
 }
 
 // Revised get_json_array_value
-char *get_json_array_value(char *key, uint32_t index, const char *json_data, uint32_t max_tokens)
+char *get_json_array_value(char *key, uint32_t index, const char *json_data)
 {
     // Always extract the full array each time from the original json_data
-    char *array_str = get_json_value(key, json_data, max_tokens);
+    char *array_str = get_json_value(key, json_data);
     if (array_str == NULL)
     {
         FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
         return NULL;
     }
+    uint32_t max_tokens = json_token_count(array_str);
 
     jsmn_parser parser;
     jsmn_init(&parser);
-
     jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
     if (tokens == NULL)
     {
@@ -644,16 +642,16 @@ char *get_json_array_value(char *key, uint32_t index, const char *json_data, uin
 }
 
 // Revised get_json_array_values function with correct token skipping
-char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values)
+char **get_json_array_values(char *key, char *json_data, int *num_values)
 {
     // Retrieve the array string for the given key
-    char *array_str = get_json_value(key, json_data, max_tokens);
+    char *array_str = get_json_value(key, json_data);
     if (array_str == NULL)
     {
         FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
         return NULL;
     }
-
+    uint32_t max_tokens = json_token_count(array_str);
     // Initialize the JSON parser
     jsmn_parser parser;
     jsmn_init(&parser);
@@ -769,3 +767,18 @@ char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, in
     free(array_str);
     return values;
 }
+
+int json_token_count(const char *json)
+{
+    if (json == NULL)
+    {
+        return JSMN_ERROR_INVAL;
+    }
+
+    jsmn_parser parser;
+    jsmn_init(&parser);
+
+    // Pass NULL for tokens and 0 for num_tokens to get the token count only
+    int ret = jsmn_parse(&parser, json, strlen(json), NULL, 0);
+    return ret; // If ret >= 0, it represents the number of tokens needed.
+}

+ 6 - 58
jsmn/jsmn.h

@@ -17,6 +17,7 @@
 #define JSMN_H
 
 #include <stddef.h>
+#include <jsmn/jsmn_h.h>
 
 #ifdef __cplusplus
 extern "C"
@@ -28,61 +29,6 @@ extern "C"
 #else
 #define JSMN_API extern
 #endif
-
-    /**
-     * JSON type identifier. Basic types are:
-     * 	o Object
-     * 	o Array
-     * 	o String
-     * 	o Other primitive: number, boolean (true/false) or null
-     */
-    typedef enum
-    {
-        JSMN_UNDEFINED = 0,
-        JSMN_OBJECT = 1 << 0,
-        JSMN_ARRAY = 1 << 1,
-        JSMN_STRING = 1 << 2,
-        JSMN_PRIMITIVE = 1 << 3
-    } jsmntype_t;
-
-    enum jsmnerr
-    {
-        /* Not enough tokens were provided */
-        JSMN_ERROR_NOMEM = -1,
-        /* Invalid character inside JSON string */
-        JSMN_ERROR_INVAL = -2,
-        /* The string is not a full JSON packet, more bytes expected */
-        JSMN_ERROR_PART = -3
-    };
-
-    /**
-     * JSON token description.
-     * type		type (object, array, string etc.)
-     * start	start position in JSON data string
-     * end		end position in JSON data string
-     */
-    typedef struct
-    {
-        jsmntype_t type;
-        int start;
-        int end;
-        int size;
-#ifdef JSMN_PARENT_LINKS
-        int parent;
-#endif
-    } jsmntok_t;
-
-    /**
-     * JSON parser. Contains an array of token blocks available. Also stores
-     * the string being parsed now and current position in that string.
-     */
-    typedef struct
-    {
-        unsigned int pos;     /* offset in the JSON string */
-        unsigned int toknext; /* next token to allocate */
-        int toksuper;         /* superior token node, e.g. parent object or array */
-    } jsmn_parser;
-
     /**
      * Create JSON parser over an array of tokens
      */
@@ -122,11 +68,13 @@ char *jsmn(const char *key, const char *value);
 int jsoneq(const char *json, jsmntok_t *tok, const char *s);
 
 // Return the value of the key in the JSON data
-char *get_json_value(char *key, const char *json_data, uint32_t max_tokens);
+char *get_json_value(char *key, const char *json_data);
 
 // Revised get_json_array_value function
-char *get_json_array_value(char *key, uint32_t index, const char *json_data, uint32_t max_tokens);
+char *get_json_array_value(char *key, uint32_t index, const char *json_data);
 
 // Revised get_json_array_values function with correct token skipping
-char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values);
+char **get_json_array_values(char *key, char *json_data, int *num_values);
+
+int json_token_count(const char *json);
 #endif /* JB_JSMN_EDIT */

+ 720 - 0
jsmn/jsmn_furi.c

@@ -0,0 +1,720 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * [License text continues...]
+ */
+
+#include <jsmn/jsmn_furi.h>
+
+// Forward declarations of helper functions
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s);
+static int skip_token(const jsmntok_t *tokens, int start, int total);
+
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens)
+{
+    if (parser->toknext >= num_tokens)
+    {
+        return NULL;
+    }
+    jsmntok_t *tok = &tokens[parser->toknext++];
+    tok->start = tok->end = -1;
+    tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+    tok->parent = -1;
+#endif
+    return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end)
+{
+    token->type = type;
+    token->start = start;
+    token->end = end;
+    token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const FuriString *js,
+                                jsmntok_t *tokens, const size_t num_tokens)
+{
+    size_t len = furi_string_size(js);
+    int start = parser->pos;
+
+    for (; parser->pos < len; parser->pos++)
+    {
+        char c = furi_string_get_char(js, parser->pos);
+        switch (c)
+        {
+#ifndef JSMN_STRICT
+        case ':':
+#endif
+        case '\t':
+        case '\r':
+        case '\n':
+        case ' ':
+        case ',':
+        case ']':
+        case '}':
+            goto found;
+        default:
+            break;
+        }
+        if (c < 32 || c >= 127)
+        {
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+        }
+    }
+
+#ifdef JSMN_STRICT
+    // In strict mode primitive must be followed by a comma/object/array
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+#endif
+
+found:
+    if (tokens == NULL)
+    {
+        parser->pos--;
+        return 0;
+    }
+    jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
+    if (token == NULL)
+    {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+    }
+    jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+    token->parent = parser->toksuper;
+#endif
+    parser->pos--;
+    return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const FuriString *js,
+                             jsmntok_t *tokens, const size_t num_tokens)
+{
+    size_t len = furi_string_size(js);
+    int start = parser->pos;
+    parser->pos++;
+
+    for (; parser->pos < len; parser->pos++)
+    {
+        char c = furi_string_get_char(js, parser->pos);
+        if (c == '\"')
+        {
+            if (tokens == NULL)
+            {
+                return 0;
+            }
+            jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
+            if (token == NULL)
+            {
+                parser->pos = start;
+                return JSMN_ERROR_NOMEM;
+            }
+            jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+            token->parent = parser->toksuper;
+#endif
+            return 0;
+        }
+
+        if (c == '\\' && (parser->pos + 1) < len)
+        {
+            parser->pos++;
+            char esc = furi_string_get_char(js, parser->pos);
+            switch (esc)
+            {
+            case '\"':
+            case '/':
+            case '\\':
+            case 'b':
+            case 'f':
+            case 'r':
+            case 'n':
+            case 't':
+                break;
+            case 'u':
+            {
+                parser->pos++;
+                for (int i = 0; i < 4 && parser->pos < len; i++)
+                {
+                    char hex = furi_string_get_char(js, parser->pos);
+                    if (!((hex >= '0' && hex <= '9') ||
+                          (hex >= 'A' && hex <= 'F') ||
+                          (hex >= 'a' && hex <= 'f')))
+                    {
+                        parser->pos = start;
+                        return JSMN_ERROR_INVAL;
+                    }
+                    parser->pos++;
+                }
+                parser->pos--;
+                break;
+            }
+            default:
+                parser->pos = start;
+                return JSMN_ERROR_INVAL;
+            }
+        }
+    }
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+}
+
+/**
+ * Create JSON parser
+ */
+void jsmn_init_furi(jsmn_parser *parser)
+{
+    parser->pos = 0;
+    parser->toknext = 0;
+    parser->toksuper = -1;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ * Now uses FuriString for the input JSON.
+ */
+int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+                    jsmntok_t *tokens, const unsigned int num_tokens)
+{
+    size_t len = furi_string_size(js);
+    int r;
+    int i;
+    int count = parser->toknext;
+
+    for (; parser->pos < len; parser->pos++)
+    {
+        char c = furi_string_get_char(js, parser->pos);
+        jsmntype_t type;
+
+        switch (c)
+        {
+        case '{':
+        case '[':
+        {
+            count++;
+            if (tokens == NULL)
+            {
+                break;
+            }
+            jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
+            if (token == NULL)
+                return JSMN_ERROR_NOMEM;
+            if (parser->toksuper != -1)
+            {
+                jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+                if (t->type == JSMN_OBJECT)
+                    return JSMN_ERROR_INVAL;
+#endif
+                t->size++;
+#ifdef JSMN_PARENT_LINKS
+                token->parent = parser->toksuper;
+#endif
+            }
+            token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+            token->start = parser->pos;
+            parser->toksuper = parser->toknext - 1;
+            break;
+        }
+        case '}':
+        case ']':
+            if (tokens == NULL)
+            {
+                break;
+            }
+            type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+            if (parser->toknext < 1)
+            {
+                return JSMN_ERROR_INVAL;
+            }
+            {
+                jsmntok_t *token = &tokens[parser->toknext - 1];
+                for (;;)
+                {
+                    if (token->start != -1 && token->end == -1)
+                    {
+                        if (token->type != type)
+                            return JSMN_ERROR_INVAL;
+                        token->end = parser->pos + 1;
+                        parser->toksuper = token->parent;
+                        break;
+                    }
+                    if (token->parent == -1)
+                    {
+                        if (token->type != type || parser->toksuper == -1)
+                        {
+                            return JSMN_ERROR_INVAL;
+                        }
+                        break;
+                    }
+                    token = &tokens[token->parent];
+                }
+            }
+#else
+            {
+                jsmntok_t *token;
+                for (i = parser->toknext - 1; i >= 0; i--)
+                {
+                    token = &tokens[i];
+                    if (token->start != -1 && token->end == -1)
+                    {
+                        if (token->type != type)
+                            return JSMN_ERROR_INVAL;
+                        parser->toksuper = -1;
+                        token->end = parser->pos + 1;
+                        break;
+                    }
+                }
+                if (i == -1)
+                    return JSMN_ERROR_INVAL;
+                for (; i >= 0; i--)
+                {
+                    token = &tokens[i];
+                    if (token->start != -1 && token->end == -1)
+                    {
+                        parser->toksuper = i;
+                        break;
+                    }
+                }
+            }
+#endif
+            break;
+        case '\"':
+            r = jsmn_parse_string(parser, js, tokens, num_tokens);
+            if (r < 0)
+                return r;
+            count++;
+            if (parser->toksuper != -1 && tokens != NULL)
+            {
+                tokens[parser->toksuper].size++;
+            }
+            break;
+        case '\t':
+        case '\r':
+        case '\n':
+        case ' ':
+            // Whitespace - ignore
+            break;
+        case ':':
+            parser->toksuper = parser->toknext - 1;
+            break;
+        case ',':
+            if (tokens != NULL && parser->toksuper != -1 &&
+                tokens[parser->toksuper].type != JSMN_ARRAY &&
+                tokens[parser->toksuper].type != JSMN_OBJECT)
+            {
+#ifdef JSMN_PARENT_LINKS
+                parser->toksuper = tokens[parser->toksuper].parent;
+#else
+                for (i = parser->toknext - 1; i >= 0; i--)
+                {
+                    if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT)
+                    {
+                        if (tokens[i].start != -1 && tokens[i].end == -1)
+                        {
+                            parser->toksuper = i;
+                            break;
+                        }
+                    }
+                }
+#endif
+            }
+            break;
+#ifdef JSMN_STRICT
+        case '-':
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+        case 't':
+        case 'f':
+        case 'n':
+            if (tokens != NULL && parser->toksuper != -1)
+            {
+                const jsmntok_t *t = &tokens[parser->toksuper];
+                if (t->type == JSMN_OBJECT ||
+                    (t->type == JSMN_STRING && t->size != 0))
+                {
+                    return JSMN_ERROR_INVAL;
+                }
+            }
+#else
+        default:
+#endif
+            r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
+            if (r < 0)
+                return r;
+            count++;
+            if (parser->toksuper != -1 && tokens != NULL)
+            {
+                tokens[parser->toksuper].size++;
+            }
+            break;
+#ifdef JSMN_STRICT
+        default:
+            return JSMN_ERROR_INVAL;
+#endif
+        }
+    }
+
+    if (tokens != NULL)
+    {
+        for (i = parser->toknext - 1; i >= 0; i--)
+        {
+            if (tokens[i].start != -1 && tokens[i].end == -1)
+            {
+                return JSMN_ERROR_PART;
+            }
+        }
+    }
+
+    return count;
+}
+
+// The rest of your code (e.g., get_json_value_furi, get_json_array_value_furi, etc.)
+// remains unchanged and can still rely on these updated parsing functions.
+
+// Helper function to create a JSON object: {"key":"value"}
+FuriString *jsmn_create_object(const FuriString *key, const FuriString *value)
+{
+    FuriString *result = furi_string_alloc();
+    furi_string_printf(result, "{\"%s\":\"%s\"}",
+                       furi_string_get_cstr(key),
+                       furi_string_get_cstr(value));
+    return result; // Caller responsible for furi_string_free
+}
+
+// Helper function to compare JSON keys
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s)
+{
+    size_t s_len = furi_string_size(s);
+    size_t tok_len = tok->end - tok->start;
+
+    if (tok->type != JSMN_STRING)
+        return -1;
+    if (s_len != tok_len)
+        return -1;
+
+    FuriString *sub = furi_string_alloc_set(json);
+    furi_string_mid(sub, tok->start, tok_len);
+
+    int res = furi_string_cmp(sub, s);
+    furi_string_free(sub);
+
+    return (res == 0) ? 0 : -1;
+}
+
+// Skip a token and its descendants
+static int skip_token(const jsmntok_t *tokens, int start, int total)
+{
+    if (start < 0 || start >= total)
+        return -1;
+
+    int i = start;
+    if (tokens[i].type == JSMN_OBJECT)
+    {
+        int pairs = tokens[i].size;
+        i++;
+        for (int p = 0; p < pairs; p++)
+        {
+            i++; // skip key
+            if (i >= total)
+                return -1;
+            i = skip_token(tokens, i, total); // skip value
+            if (i == -1)
+                return -1;
+        }
+        return i;
+    }
+    else if (tokens[i].type == JSMN_ARRAY)
+    {
+        int elems = tokens[i].size;
+        i++;
+        for (int e = 0; e < elems; e++)
+        {
+            i = skip_token(tokens, i, total);
+            if (i == -1)
+                return -1;
+        }
+        return i;
+    }
+    else
+    {
+        return i + 1;
+    }
+}
+
+/**
+ * Parse JSON and return the value associated with a given char* key.
+ */
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data)
+{
+    if (json_data == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "JSON data is NULL");
+        return NULL;
+    }
+    uint32_t max_tokens = json_token_count_furi(json_data);
+    // Create a temporary FuriString from key
+    FuriString *key_str = furi_string_alloc();
+    furi_string_cat_str(key_str, key);
+
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+    if (tokens == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        furi_string_free(key_str);
+        return NULL;
+    }
+
+    int ret = jsmn_parse_furi(&parser, json_data, tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
+        free(tokens);
+        furi_string_free(key_str);
+        return NULL;
+    }
+
+    if (ret < 1 || tokens[0].type != JSMN_OBJECT)
+    {
+        FURI_LOG_E("JSMM.H", "Root element is not an object.");
+        free(tokens);
+        furi_string_free(key_str);
+        return NULL;
+    }
+
+    for (int i = 1; i < ret; i++)
+    {
+        if (jsoneq_furi(json_data, &tokens[i], key_str) == 0)
+        {
+            int length = tokens[i + 1].end - tokens[i + 1].start;
+            FuriString *value = furi_string_alloc_set(json_data);
+            furi_string_mid(value, tokens[i + 1].start, length);
+            free(tokens);
+            furi_string_free(key_str);
+            return value;
+        }
+    }
+
+    free(tokens);
+    furi_string_free(key_str);
+    FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
+    return NULL;
+}
+
+/**
+ * Return the value at a given index in a JSON array for a given char* key.
+ */
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data)
+{
+    FuriString *array_str = get_json_value_furi(key, json_data);
+    if (array_str == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to get array for key");
+        return NULL;
+    }
+    uint32_t max_tokens = json_token_count_furi(array_str);
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+    if (tokens == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    if (ret < 1 || tokens[0].type != JSMN_ARRAY)
+    {
+        FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    if (index >= (uint32_t)tokens[0].size)
+    {
+        FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %u.", index, tokens[0].size);
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int elem_token = 1;
+    for (uint32_t i = 0; i < index; i++)
+    {
+        elem_token = skip_token(tokens, elem_token, ret);
+        if (elem_token == -1 || elem_token >= ret)
+        {
+            FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
+            free(tokens);
+            furi_string_free(array_str);
+            return NULL;
+        }
+    }
+
+    jsmntok_t element = tokens[elem_token];
+    int length = element.end - element.start;
+
+    FuriString *value = furi_string_alloc_set(array_str);
+    furi_string_mid(value, element.start, length);
+
+    free(tokens);
+    furi_string_free(array_str);
+
+    return value;
+}
+
+/**
+ * Extract all object values from a JSON array associated with a given char* key.
+ */
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values)
+{
+    *num_values = 0;
+    // Convert key to FuriString and call get_json_value_furi
+    FuriString *array_str = get_json_value_furi(key, json_data);
+    if (array_str == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to get array for key");
+        return NULL;
+    }
+
+    uint32_t max_tokens = json_token_count_furi(array_str);
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+    if (tokens == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    if (tokens[0].type != JSMN_ARRAY)
+    {
+        FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int array_size = tokens[0].size;
+    FuriString **values = (FuriString **)malloc(array_size * sizeof(FuriString *));
+    if (values == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int actual_num_values = 0;
+    int current_token = 1;
+    for (int i = 0; i < array_size; i++)
+    {
+        if (current_token >= ret)
+        {
+            FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+            break;
+        }
+
+        jsmntok_t element = tokens[current_token];
+
+        int length = element.end - element.start;
+        FuriString *value = furi_string_alloc_set(array_str);
+        furi_string_mid(value, element.start, length);
+
+        values[actual_num_values] = value;
+        actual_num_values++;
+
+        // Skip this element and its descendants
+        current_token = skip_token(tokens, current_token, ret);
+        if (current_token == -1)
+        {
+            FURI_LOG_E("JSMM.H", "Error skipping tokens after element %d.", i);
+            break;
+        }
+    }
+
+    *num_values = actual_num_values;
+    if (actual_num_values < array_size)
+    {
+        FuriString **reduced_values = (FuriString **)realloc(values, actual_num_values * sizeof(FuriString *));
+        if (reduced_values != NULL)
+        {
+            values = reduced_values;
+        }
+    }
+
+    free(tokens);
+    furi_string_free(array_str);
+    return values;
+}
+
+uint32_t json_token_count_furi(const FuriString *json)
+{
+    if (json == NULL)
+    {
+        return JSMN_ERROR_INVAL;
+    }
+
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    // Pass NULL for tokens and 0 for num_tokens to get the token count only
+    int ret = jsmn_parse_furi(&parser, json, NULL, 0);
+    return ret; // If ret >= 0, it represents the number of tokens needed.
+}

+ 74 - 0
jsmn/jsmn_furi.h

@@ -0,0 +1,74 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * [License text continues...]
+ */
+
+#ifndef JSMN_FURI_H
+#define JSMN_FURI_H
+
+#include <jsmn/jsmn_h.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+    JSMN_API void jsmn_init_furi(jsmn_parser *parser);
+    JSMN_API int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+                                 jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/* Implementation in jsmn_furi.c */
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_FURI_H */
+
+#ifndef JB_JSMN_FURI_EDIT
+#define JB_JSMN_FURI_EDIT
+
+// Helper function to create a JSON object
+FuriString *jsmn_create_object(const FuriString *key, const FuriString *value);
+
+// Updated signatures to accept const char* key
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data);
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data);
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values);
+
+uint32_t json_token_count_furi(const FuriString *json);
+/* Example usage:
+char *json = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+FuriString *json_data = char_to_furi_string(json);
+if (!json_data)
+{
+    FURI_LOG_E(TAG, "Failed to allocate FuriString");
+    return -1;
+}
+FuriString *value = get_json_value_furi("key1", json_data, json_token_count_furi(json_data));
+if (value)
+{
+    FURI_LOG_I(TAG, "Value: %s", furi_string_get_cstr(value));
+    furi_string_free(value);
+}
+furi_string_free(json_data);
+*/
+#endif /* JB_JSMN_EDIT */

+ 14 - 0
jsmn/jsmn_h.c

@@ -0,0 +1,14 @@
+#include <jsmn/jsmn_h.h>
+FuriString *char_to_furi_string(const char *str)
+{
+    FuriString *furi_str = furi_string_alloc();
+    if (!furi_str)
+    {
+        return NULL;
+    }
+    for (size_t i = 0; i < strlen(str); i++)
+    {
+        furi_string_push_back(furi_str, str[i]);
+    }
+    return furi_str;
+}

+ 41 - 0
jsmn/jsmn_h.h

@@ -0,0 +1,41 @@
+#pragma once
+#include <furi.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+typedef enum
+{
+    JSMN_UNDEFINED = 0,
+    JSMN_OBJECT = 1 << 0,
+    JSMN_ARRAY = 1 << 1,
+    JSMN_STRING = 1 << 2,
+    JSMN_PRIMITIVE = 1 << 3
+} jsmntype_t;
+
+enum jsmnerr
+{
+    JSMN_ERROR_NOMEM = -1,
+    JSMN_ERROR_INVAL = -2,
+    JSMN_ERROR_PART = -3
+};
+
+typedef struct
+{
+    jsmntype_t type;
+    int start;
+    int end;
+    int size;
+#ifdef JSMN_PARENT_LINKS
+    int parent;
+#endif
+} jsmntok_t;
+
+typedef struct
+{
+    unsigned int pos;     /* offset in the JSON string */
+    unsigned int toknext; /* next token to allocate */
+    int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+FuriString *char_to_furi_string(const char *str);