瀏覽代碼

Update companion to 0.6.5

docs/changelog.md seems outdated, so it is deleted
eried 2 年之前
父節點
當前提交
d2428ad048

+ 38 - 35
ReadMe.md

@@ -1,35 +1,38 @@
-[![FAP Build](https://github.com/0xchocolate/flipperzero-wifi-marauder/actions/workflows/build.yml/badge.svg)](https://github.com/0xchocolate/flipperzero-wifi-marauder/actions/workflows/build.yml)
-
-# WiFi Marauder companion app for Flipper Zero
-
-Requires a connected dev board running Marauder FW. [See install instructions from UberGuidoZ here.](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard#marauder-install-information)
-
-<img src="https://github.com/0xchocolate/flipperzero-wifi-marauder/blob/feature_wifi_marauder_app/screenshots/marauder-topmenu.png?raw=true" width=20% height=20% /> <img src="https://github.com/0xchocolate/flipperzero-wifi-marauder/blob/feature_wifi_marauder_app/screenshots/marauder-script-demo.png?raw=true" width=20% height=20% /> <img src="https://github.com/0xchocolate/flipperzero-wifi-marauder/blob/feature_wifi_marauder_app/screenshots/marauder-save-pcaps.png?raw=true" width=20% height=20% />
-
-## Get the app
-1. Make sure you're logged in with a github account (otherwise the downloads in step 2 won't work)
-2. Navigate to the [FAP Build](https://github.com/0xchocolate/flipperzero-wifi-marauder/actions/workflows/build.yml)
-   GitHub action workflow, and select the most recent run, scroll down to artifacts.
-3. The FAP is built for the `dev` and `release` channels of both official and unleashed
-   firmware. Download the artifact corresponding to your firmware version.
-4. (Optional step to avoid confusion) Go to "Apps/GPIO" on the Flipper SD Card, delete any existing Marauder app, on some firmwares there will be a `ESP32CAM_Marauder.fap` or similar.
-5. Extract `esp32_wifi_marauder.fap` from the ZIP file downloaded in step 3 to your Flipper Zero SD card, preferably under Apps/GPIO along with the rest of the GPIO apps. (If you're using qFlipper to transfer files you need to extract the content of the ZIP file to your computer before you drag it to qFlipper, as qFlipper does not support direct dragging from a ZIP file (at least on Windows)).
-
-From a local clone of this repo, you can also build the app yourself using ufbt.
-
-### FYI - the ESP flasher is now its own app: https://github.com/0xchocolate/flipperzero-esp-flasher
-
-
-## Support
-
-For app feedback, bugs, and feature requests, please [create an issue here](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion/issues).
-
-You can find me (0xchocolate) on discord as @cococode#6011.
-
-If you'd like to donate to the app development effort:  
-**ETH**: `0xf32A1F0CD6122C97d8953183E53cB889cc087C9b`  
-**BTC**: `bc1qtw7s25cwdkuaups22yna8sttfxn0usm2f35wc3`
-
-Find more info about Marauder and support its developer (justcallmekoko aka WillStunForFood) here: https://github.com/justcallmekoko/ESP32Marauder
-
-If you found the app preinstalled in a firmware release, consider supporting the maintainers!
+[![FAP Build](https://github.com/0xchocolate/flipperzero-wifi-marauder/actions/workflows/build.yml/badge.svg)](https://github.com/0xchocolate/flipperzero-wifi-marauder/actions/workflows/build.yml)
+
+# WiFi Marauder companion app for Flipper Zero
+
+Requires a connected dev board running Marauder FW. [See install instructions from UberGuidoZ here.](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard#marauder-install-information)
+
+<img src="https://github.com/0xchocolate/flipperzero-wifi-marauder/blob/feature_wifi_marauder_app/screenshots/marauder-topmenu.png?raw=true" width=20% height=20% /> <img src="https://github.com/0xchocolate/flipperzero-wifi-marauder/blob/feature_wifi_marauder_app/screenshots/marauder-script-demo.png?raw=true" width=20% height=20% /> <img src="https://github.com/0xchocolate/flipperzero-wifi-marauder/blob/feature_wifi_marauder_app/screenshots/marauder-save-pcaps.png?raw=true" width=20% height=20% />
+
+## Get the app
+1. Make sure you're logged in with a github account (otherwise the downloads in step 2 won't work)
+2. Navigate to the [FAP Build](https://github.com/0xchocolate/flipperzero-wifi-marauder/actions/workflows/build.yml)
+   GitHub action workflow, and select the most recent run, scroll down to artifacts.
+3. The FAP is built for the `dev` and `release` channels of both official and unleashed
+   firmware. Download the artifact corresponding to your firmware version.
+4. (Optional step to avoid confusion) Go to "Apps/GPIO" on the Flipper SD Card, delete any existing Marauder app, on some firmwares there will be a `ESP32CAM_Marauder.fap` or similar.
+5. Extract `esp32_wifi_marauder.fap` from the ZIP file downloaded in step 3 to your Flipper Zero SD card, preferably under Apps/GPIO along with the rest of the GPIO apps. (If you're using qFlipper to transfer files you need to extract the content of the ZIP file to your computer before you drag it to qFlipper, as qFlipper does not support direct dragging from a ZIP file (at least on Windows)).
+
+From a local clone of this repo, you can also build the app yourself using ufbt.
+
+### FYI - the ESP flasher is now its own app: https://github.com/0xchocolate/flipperzero-esp-flasher
+
+
+## Support
+
+For app feedback, bugs, and feature requests, please [create an issue here](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion/issues).
+
+You can find me (0xchocolate) on discord as @cococode.
+
+If you'd like to donate to the app development effort:
+
+[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/O4O1R7X6K)
+
+**ETH**: `0xf32A1F0CD6122C97d8953183E53cB889cc087C9b`  
+**BTC**: `bc1qtw7s25cwdkuaups22yna8sttfxn0usm2f35wc3`
+
+Find more info about Marauder and support its developer (justcallmekoko aka WillStunForFood) here: https://github.com/justcallmekoko/ESP32Marauder
+
+If you found the app preinstalled in a firmware release, consider supporting the maintainers!

+ 2 - 2
application.fam

@@ -1,7 +1,7 @@
 App(
     appid="mayhem_marauder",
-    name="[MAYHEM] Marauder v0.6.4",
-    fap_version=(6,4),
+    name="[MAYHEM] Marauder v0.6.5",
+    fap_version=(6,5),
     apptype=FlipperAppType.EXTERNAL,
     entry_point="wifi_marauder_app",
     requires=["gui"],

+ 0 - 12
docs/changelog.md

@@ -1,12 +0,0 @@
-## v0.6.0
-
-Evil Portal and LED control! Thanks @justcallmekoko for adding the commands! See Evil Portal workflow here: https://github.com/justcallmekoko/ESP32Marauder/wiki/evil-portal-workflow
-
-This version also removes the in-app ESP flasher, but don't worry! It's grown up into its own app, where development will continue separately: https://github.com/0xchocolate/flipperzero-esp-flasher
-
-
-## v0.4.0
-
-Added Signal Monitor (thanks @justcallmekoko!) to support new sigmon command in Marauder v0.10.5: https://github.com/justcallmekoko/ESP32Marauder/releases
-
-Added keyboard and +5V support from unleashed (thanks @xMasterX!)

+ 8 - 7
scenes/wifi_marauder_scene_start.c

@@ -82,6 +82,13 @@ const WifiMarauderItem items[NUM_MENU_ITEMS] = {
      TOGGLE_ARGS,
      FOCUS_CONSOLE_END,
      SHOW_STOPSCAN_TIP},
+    {"Load Evil Portal HTML file",
+     {""},
+     1,
+     {"evilportal -c sethtmlstr"},
+     NO_ARGS,
+     FOCUS_CONSOLE_END,
+     NO_TIP},
     {"Targeted Deauth",
      {"station", "manual"},
      2,
@@ -158,13 +165,7 @@ const WifiMarauderItem items[NUM_MENU_ITEMS] = {
      TOGGLE_ARGS,
      FOCUS_CONSOLE_START,
      NO_TIP},
-    {"List SD",
-     {""},
-     1,
-     {"ls /"},
-     INPUT_ARGS,
-     FOCUS_CONSOLE_END,
-     NO_TIP},
+    {"List SD", {""}, 1, {"ls /"}, INPUT_ARGS, FOCUS_CONSOLE_END, NO_TIP},
     {"Update", {"sd"}, 1, {"update -s"}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP},
     {"Reboot", {""}, 1, {"reboot"}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP},
     {"Help", {""}, 1, {"help"}, NO_ARGS, FOCUS_CONSOLE_START, SHOW_STOPSCAN_TIP},

+ 117 - 118
script/menu/wifi_marauder_script_stage_menu_sniffpmkid.c

@@ -1,119 +1,118 @@
-#include "../../wifi_marauder_app_i.h"
-
-static void wifi_marauder_sniffpmkid_stage_hop_channels_setup_callback(VariableItem* item) {
-    WifiMarauderApp* app = variable_item_get_context(item);
-    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
-    variable_item_set_current_value_index(item, stage->hop_channels);
-}
-
-static void wifi_marauder_sniffpmkid_stage_hop_channels_change_callback(VariableItem* item) {
-    WifiMarauderApp* app = variable_item_get_context(item);
-
-    uint8_t current_stage_index = variable_item_list_get_selected_item_index(app->var_item_list);
-    const WifiMarauderScriptMenuItem* menu_item =
-            &app->script_stage_menu->items[current_stage_index];
-
-    uint8_t option_index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, menu_item->options[option_index]);
-
-    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
-    stage->hop_channels = option_index;
-}
-
-
-static void wifi_marauder_sniffpmkid_stage_force_deauth_setup_callback(VariableItem* item) {
-    WifiMarauderApp* app = variable_item_get_context(item);
-    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
-    variable_item_set_current_value_index(item, stage->force_deauth);
-}
-
-static void wifi_marauder_sniffpmkid_stage_force_deauth_change_callback(VariableItem* item) {
-    WifiMarauderApp* app = variable_item_get_context(item);
-
-    // Get menu item
-    uint8_t current_stage_index = variable_item_list_get_selected_item_index(app->var_item_list);
-    const WifiMarauderScriptMenuItem* menu_item =
-        &app->script_stage_menu->items[current_stage_index];
-
-    // Defines the text of the selected option
-    uint8_t option_index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, menu_item->options[option_index]);
-
-    // Updates the attribute value of the current stage
-    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
-    stage->force_deauth = option_index;
-}
-
-static void wifi_marauder_sniffpmkid_stage_channel_setup_callback(VariableItem* item) {
-    WifiMarauderApp* app = variable_item_get_context(item);
-    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
-    if(stage->channel >= 0 && stage->channel < 12) {
-        variable_item_set_current_value_index(item, stage->channel);
-    } else {
-        variable_item_set_current_value_index(item, 0);
-    }
-}
-
-static void wifi_marauder_sniffpmkid_stage_channel_change_callback(VariableItem* item) {
-    WifiMarauderApp* app = variable_item_get_context(item);
-
-    // Get menu item
-    uint8_t current_stage_index = variable_item_list_get_selected_item_index(app->var_item_list);
-    const WifiMarauderScriptMenuItem* menu_item =
-        &app->script_stage_menu->items[current_stage_index];
-
-    // Defines the text of the selected option
-    uint8_t option_index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, menu_item->options[option_index]);
-
-    // Updates the attribute value of the current stage
-    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
-    stage->channel = option_index;
-}
-
-static void wifi_marauder_sniffpmkid_stage_timeout_setup_callback(VariableItem* item) {
-    WifiMarauderApp* app = variable_item_get_context(item);
-    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
-    char timeout_str[32];
-    snprintf(timeout_str, sizeof(timeout_str), "%d", stage->timeout);
-    variable_item_set_current_value_text(item, timeout_str);
-}
-
-static void wifi_marauder_sniffpmkid_stage_timeout_select_callback(void* context) {
-    WifiMarauderApp* app = context;
-    WifiMarauderScriptStageSniffPmkid* stage_sniffpmkid = app->script_edit_selected_stage->stage;
-    app->user_input_number_reference = &stage_sniffpmkid->timeout;
-}
-
-void wifi_marauder_script_stage_menu_sniffpmkid_load(WifiMarauderScriptStageMenu* stage_menu) {
-    stage_menu->num_items = 4;
-    stage_menu->items = malloc(4 * sizeof(WifiMarauderScriptMenuItem));
-
-    stage_menu->items[0] = (WifiMarauderScriptMenuItem){
-        .name = strdup("Force deauth"),
-        .type = WifiMarauderScriptMenuItemTypeOptionsString,
-        .num_options = 2,
-        .options = {"no", "yes"},
-        .setup_callback = wifi_marauder_sniffpmkid_stage_force_deauth_setup_callback,
-        .change_callback = wifi_marauder_sniffpmkid_stage_force_deauth_change_callback};
-    stage_menu->items[1] = (WifiMarauderScriptMenuItem){
-        .name = strdup("Channel"),
-        .type = WifiMarauderScriptMenuItemTypeOptionsNumber,
-        .num_options = 12,
-        .options = {"none", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"},
-        .setup_callback = wifi_marauder_sniffpmkid_stage_channel_setup_callback,
-        .change_callback = wifi_marauder_sniffpmkid_stage_channel_change_callback};
-    stage_menu->items[2] = (WifiMarauderScriptMenuItem){
-        .name = strdup("Timeout"),
-        .type = WifiMarauderScriptMenuItemTypeNumber,
-        .num_options = 1,
-        .setup_callback = wifi_marauder_sniffpmkid_stage_timeout_setup_callback,
-        .select_callback = wifi_marauder_sniffpmkid_stage_timeout_select_callback};
-    stage_menu->items[3] = (WifiMarauderScriptMenuItem){
-            .name = strdup("Hop Channels"),
-            .type = WifiMarauderScriptMenuItemTypeOptionsString,
-            .num_options = 2,
-            .options = {"no", "yes"},
-            .setup_callback = wifi_marauder_sniffpmkid_stage_hop_channels_setup_callback,
-            .change_callback = wifi_marauder_sniffpmkid_stage_hop_channels_change_callback};
+#include "../../wifi_marauder_app_i.h"
+
+static void wifi_marauder_sniffpmkid_stage_hop_channels_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
+    variable_item_set_current_value_index(item, stage->hop_channels);
+}
+
+static void wifi_marauder_sniffpmkid_stage_hop_channels_change_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+
+    uint8_t current_stage_index = variable_item_list_get_selected_item_index(app->var_item_list);
+    const WifiMarauderScriptMenuItem* menu_item =
+        &app->script_stage_menu->items[current_stage_index];
+
+    uint8_t option_index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, menu_item->options[option_index]);
+
+    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
+    stage->hop_channels = option_index;
+}
+
+static void wifi_marauder_sniffpmkid_stage_force_deauth_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
+    variable_item_set_current_value_index(item, stage->force_deauth);
+}
+
+static void wifi_marauder_sniffpmkid_stage_force_deauth_change_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+
+    // Get menu item
+    uint8_t current_stage_index = variable_item_list_get_selected_item_index(app->var_item_list);
+    const WifiMarauderScriptMenuItem* menu_item =
+        &app->script_stage_menu->items[current_stage_index];
+
+    // Defines the text of the selected option
+    uint8_t option_index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, menu_item->options[option_index]);
+
+    // Updates the attribute value of the current stage
+    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
+    stage->force_deauth = option_index;
+}
+
+static void wifi_marauder_sniffpmkid_stage_channel_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
+    if(stage->channel >= 0 && stage->channel < 12) {
+        variable_item_set_current_value_index(item, stage->channel);
+    } else {
+        variable_item_set_current_value_index(item, 0);
+    }
+}
+
+static void wifi_marauder_sniffpmkid_stage_channel_change_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+
+    // Get menu item
+    uint8_t current_stage_index = variable_item_list_get_selected_item_index(app->var_item_list);
+    const WifiMarauderScriptMenuItem* menu_item =
+        &app->script_stage_menu->items[current_stage_index];
+
+    // Defines the text of the selected option
+    uint8_t option_index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, menu_item->options[option_index]);
+
+    // Updates the attribute value of the current stage
+    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
+    stage->channel = option_index;
+}
+
+static void wifi_marauder_sniffpmkid_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSniffPmkid* stage = app->script_edit_selected_stage->stage;
+    char timeout_str[32];
+    snprintf(timeout_str, sizeof(timeout_str), "%d", stage->timeout);
+    variable_item_set_current_value_text(item, timeout_str);
+}
+
+static void wifi_marauder_sniffpmkid_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageSniffPmkid* stage_sniffpmkid = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_sniffpmkid->timeout;
+}
+
+void wifi_marauder_script_stage_menu_sniffpmkid_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 4;
+    stage_menu->items = malloc(4 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem){
+        .name = strdup("Force deauth"),
+        .type = WifiMarauderScriptMenuItemTypeOptionsString,
+        .num_options = 2,
+        .options = {"no", "yes"},
+        .setup_callback = wifi_marauder_sniffpmkid_stage_force_deauth_setup_callback,
+        .change_callback = wifi_marauder_sniffpmkid_stage_force_deauth_change_callback};
+    stage_menu->items[1] = (WifiMarauderScriptMenuItem){
+        .name = strdup("Channel"),
+        .type = WifiMarauderScriptMenuItemTypeOptionsNumber,
+        .num_options = 12,
+        .options = {"none", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"},
+        .setup_callback = wifi_marauder_sniffpmkid_stage_channel_setup_callback,
+        .change_callback = wifi_marauder_sniffpmkid_stage_channel_change_callback};
+    stage_menu->items[2] = (WifiMarauderScriptMenuItem){
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_sniffpmkid_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_sniffpmkid_stage_timeout_select_callback};
+    stage_menu->items[3] = (WifiMarauderScriptMenuItem){
+        .name = strdup("Hop Channels"),
+        .type = WifiMarauderScriptMenuItemTypeOptionsString,
+        .num_options = 2,
+        .options = {"no", "yes"},
+        .setup_callback = wifi_marauder_sniffpmkid_stage_hop_channels_setup_callback,
+        .change_callback = wifi_marauder_sniffpmkid_stage_hop_channels_change_callback};
 }

+ 961 - 962
script/wifi_marauder_script.c

@@ -1,963 +1,962 @@
-#include "../wifi_marauder_app_i.h"
-#include "wifi_marauder_script.h"
-
-WifiMarauderScript* wifi_marauder_script_alloc() {
-    WifiMarauderScript* script = (WifiMarauderScript*)malloc(sizeof(WifiMarauderScript));
-    if(script == NULL) {
-        return NULL;
-    }
-    script->name = NULL;
-    script->description = NULL;
-    script->first_stage = NULL;
-    script->last_stage = NULL;
-    script->enable_led = WifiMarauderScriptBooleanUndefined;
-    script->save_pcap = WifiMarauderScriptBooleanUndefined;
-    script->repeat = 1;
-    return script;
-}
-
-WifiMarauderScript* wifi_marauder_script_create(const char* script_name) {
-    WifiMarauderScript* script = wifi_marauder_script_alloc();
-    script->name = strdup(script_name);
-    return script;
-}
-
-void _wifi_marauder_script_load_meta(WifiMarauderScript* script, cJSON* meta_section) {
-    if(meta_section != NULL) {
-        // Script description
-        cJSON* description = cJSON_GetObjectItem(meta_section, "description");
-        if(description != NULL) {
-            script->description = strdup(description->valuestring);
-        }
-        // Enable LED
-        cJSON* enable_led_json = cJSON_GetObjectItem(meta_section, "enableLed");
-        if(cJSON_IsBool(enable_led_json)) {
-            script->enable_led = enable_led_json->valueint;
-        }
-        // Save PCAP
-        cJSON* save_pcap_json = cJSON_GetObjectItem(meta_section, "savePcap");
-        if(cJSON_IsBool(save_pcap_json)) {
-            script->save_pcap = save_pcap_json->valueint;
-        }
-        // Times the script will be repeated
-        cJSON* repeat = cJSON_GetObjectItem(meta_section, "repeat");
-        if(repeat != NULL) {
-            script->repeat = repeat->valueint;
-        }
-    }
-    if(script->description == NULL) {
-        script->description = strdup("My script");
-    }
-}
-
-WifiMarauderScriptStageScan* _wifi_marauder_script_get_stage_scan(cJSON* stages) {
-    cJSON* stage_scan = cJSON_GetObjectItem(stages, "scan");
-    if(stage_scan == NULL) {
-        return NULL;
-    }
-    cJSON* type = cJSON_GetObjectItem(stage_scan, "type");
-    if(type == NULL) {
-        return NULL;
-    }
-    WifiMarauderScriptScanType scan_type;
-    if(strcmp(type->valuestring, "ap") == 0) {
-        scan_type = WifiMarauderScriptScanTypeAp;
-    } else if(strcmp(type->valuestring, "station") == 0) {
-        scan_type = WifiMarauderScriptScanTypeStation;
-    } else {
-        return NULL;
-    }
-    cJSON* channel = cJSON_GetObjectItem(stage_scan, "channel");
-    int scan_channel = channel != NULL ? (int)cJSON_GetNumberValue(channel) : 0;
-    cJSON* timeout = cJSON_GetObjectItem(stage_scan, "timeout");
-    int scan_timeout = timeout != NULL ? (int)cJSON_GetNumberValue(timeout) :
-                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SCAN;
-
-    WifiMarauderScriptStageScan* scan_stage =
-        (WifiMarauderScriptStageScan*)malloc(sizeof(WifiMarauderScriptStageScan));
-    scan_stage->type = scan_type;
-    scan_stage->channel = scan_channel;
-    scan_stage->timeout = scan_timeout;
-
-    return scan_stage;
-}
-
-WifiMarauderScriptStageSelect* _wifi_marauder_script_get_stage_select(cJSON* stages) {
-    cJSON* select_stage_json = cJSON_GetObjectItemCaseSensitive(stages, "select");
-    if(select_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* type_json = cJSON_GetObjectItemCaseSensitive(select_stage_json, "type");
-    cJSON* filter_json = cJSON_GetObjectItemCaseSensitive(select_stage_json, "filter");
-    cJSON* indexes_json = cJSON_GetObjectItemCaseSensitive(select_stage_json, "indexes");
-    cJSON* allow_repeat_json = cJSON_GetObjectItemCaseSensitive(select_stage_json, "allow_repeat");
-
-    if(!cJSON_IsString(type_json)) {
-        return NULL;
-    }
-    WifiMarauderScriptSelectType select_type;
-    if(strcmp(type_json->valuestring, "ap") == 0) {
-        select_type = WifiMarauderScriptSelectTypeAp;
-    } else if(strcmp(type_json->valuestring, "station") == 0) {
-        select_type = WifiMarauderScriptSelectTypeStation;
-    } else if(strcmp(type_json->valuestring, "ssid") == 0) {
-        select_type = WifiMarauderScriptSelectTypeSsid;
-    } else {
-        return NULL;
-    }
-    char* filter_str = cJSON_IsString(filter_json) ? strdup(filter_json->valuestring) : NULL;
-
-    WifiMarauderScriptStageSelect* stage_select =
-        (WifiMarauderScriptStageSelect*)malloc(sizeof(WifiMarauderScriptStageSelect));
-    stage_select->type = select_type;
-    stage_select->allow_repeat = cJSON_IsBool(allow_repeat_json) ? allow_repeat_json->valueint :
-                                                                   true;
-    stage_select->filter = filter_str;
-
-    if(cJSON_IsArray(indexes_json)) {
-        int indexes_size = cJSON_GetArraySize(indexes_json);
-        int* indexes = (int*)malloc(indexes_size * sizeof(int));
-        for(int i = 0; i < indexes_size; i++) {
-            cJSON* index_item = cJSON_GetArrayItem(indexes_json, i);
-            if(cJSON_IsNumber(index_item)) {
-                indexes[i] = index_item->valueint;
-            }
-        }
-        stage_select->indexes = indexes;
-        stage_select->index_count = indexes_size;
-    } else {
-        stage_select->indexes = NULL;
-        stage_select->index_count = 0;
-    }
-
-    return stage_select;
-}
-
-WifiMarauderScriptStageDeauth* _wifi_marauder_script_get_stage_deauth(cJSON* stages) {
-    cJSON* deauth_stage_json = cJSON_GetObjectItemCaseSensitive(stages, "deauth");
-    if(deauth_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* timeout = cJSON_GetObjectItem(deauth_stage_json, "timeout");
-    int deauth_timeout = timeout != NULL ? (int)cJSON_GetNumberValue(timeout) :
-                                           WIFI_MARAUDER_DEFAULT_TIMEOUT_DEAUTH;
-
-    WifiMarauderScriptStageDeauth* deauth_stage =
-        (WifiMarauderScriptStageDeauth*)malloc(sizeof(WifiMarauderScriptStageDeauth));
-    deauth_stage->timeout = deauth_timeout;
-
-    return deauth_stage;
-}
-
-WifiMarauderScriptStageProbe* _wifi_marauder_script_get_stage_probe(cJSON* stages) {
-    cJSON* probe_stage_json = cJSON_GetObjectItemCaseSensitive(stages, "probe");
-    if(probe_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* timeout = cJSON_GetObjectItem(probe_stage_json, "timeout");
-    int probe_timeout = timeout != NULL ? (int)cJSON_GetNumberValue(timeout) :
-                                          WIFI_MARAUDER_DEFAULT_TIMEOUT_PROBE;
-
-    WifiMarauderScriptStageProbe* probe_stage =
-        (WifiMarauderScriptStageProbe*)malloc(sizeof(WifiMarauderScriptStageProbe));
-    probe_stage->timeout = probe_timeout;
-
-    return probe_stage;
-}
-
-WifiMarauderScriptStageSniffRaw* _wifi_marauder_script_get_stage_sniff_raw(cJSON* stages) {
-    cJSON* sniffraw_stage_json = cJSON_GetObjectItem(stages, "sniffraw");
-    if(sniffraw_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* timeout_json = cJSON_GetObjectItem(sniffraw_stage_json, "timeout");
-    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
-                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
-
-    WifiMarauderScriptStageSniffRaw* sniff_raw_stage =
-        (WifiMarauderScriptStageSniffRaw*)malloc(sizeof(WifiMarauderScriptStageSniffRaw));
-    sniff_raw_stage->timeout = timeout;
-
-    return sniff_raw_stage;
-}
-
-WifiMarauderScriptStageSniffBeacon* _wifi_marauder_script_get_stage_sniff_beacon(cJSON* stages) {
-    cJSON* sniffbeacon_stage_json = cJSON_GetObjectItem(stages, "sniffbeacon");
-    if(sniffbeacon_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* timeout_json = cJSON_GetObjectItem(sniffbeacon_stage_json, "timeout");
-    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
-                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
-
-    WifiMarauderScriptStageSniffBeacon* sniff_beacon_stage =
-        (WifiMarauderScriptStageSniffBeacon*)malloc(sizeof(WifiMarauderScriptStageSniffBeacon));
-    sniff_beacon_stage->timeout = timeout;
-
-    return sniff_beacon_stage;
-}
-
-WifiMarauderScriptStageSniffDeauth* _wifi_marauder_script_get_stage_sniff_deauth(cJSON* stages) {
-    cJSON* sniffdeauth_stage_json = cJSON_GetObjectItem(stages, "sniffdeauth");
-    if(sniffdeauth_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* timeout_json = cJSON_GetObjectItem(sniffdeauth_stage_json, "timeout");
-    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
-                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
-
-    WifiMarauderScriptStageSniffDeauth* sniff_deauth_stage =
-        (WifiMarauderScriptStageSniffDeauth*)malloc(sizeof(WifiMarauderScriptStageSniffDeauth));
-    sniff_deauth_stage->timeout = timeout;
-
-    return sniff_deauth_stage;
-}
-
-WifiMarauderScriptStageSniffEsp* _wifi_marauder_script_get_stage_sniff_esp(cJSON* stages) {
-    cJSON* sniffesp_stage_json = cJSON_GetObjectItem(stages, "sniffesp");
-    if(sniffesp_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* timeout_json = cJSON_GetObjectItem(sniffesp_stage_json, "timeout");
-    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
-                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
-
-    WifiMarauderScriptStageSniffEsp* sniff_esp_stage =
-        (WifiMarauderScriptStageSniffEsp*)malloc(sizeof(WifiMarauderScriptStageSniffEsp));
-    sniff_esp_stage->timeout = timeout;
-
-    return sniff_esp_stage;
-}
-
-WifiMarauderScriptStageSniffPmkid* _wifi_marauder_script_get_stage_sniff_pmkid(cJSON* stages) {
-    cJSON* sniffpmkid_stage_json = cJSON_GetObjectItem(stages, "sniffpmkid");
-    if(sniffpmkid_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* channel_json = cJSON_GetObjectItem(sniffpmkid_stage_json, "channel");
-    int channel = channel_json != NULL ? (int)cJSON_GetNumberValue(channel_json) : 0;
-
-    cJSON* timeout_json = cJSON_GetObjectItem(sniffpmkid_stage_json, "timeout");
-    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
-                  WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
-
-    cJSON* force_deauth_json =
-            cJSON_GetObjectItemCaseSensitive(sniffpmkid_stage_json, "forceDeauth");
-    bool force_deauth = cJSON_IsBool(force_deauth_json) ? force_deauth_json->valueint : true;
-
-    cJSON* hop_channels_json =
-            cJSON_GetObjectItemCaseSensitive(sniffpmkid_stage_json, "hopChannels");
-    bool hop_channels = cJSON_IsBool(hop_channels_json) ? hop_channels_json->valueint : false;
-
-    WifiMarauderScriptStageSniffPmkid* sniff_pmkid_stage =
-            (WifiMarauderScriptStageSniffPmkid*)malloc(sizeof(WifiMarauderScriptStageSniffPmkid));
-
-    if (sniff_pmkid_stage == NULL) {
-        // Handle memory allocation error
-        return NULL;
-    }
-    sniff_pmkid_stage->channel = channel;
-    sniff_pmkid_stage->timeout = timeout;
-    sniff_pmkid_stage->force_deauth = force_deauth;
-    sniff_pmkid_stage->hop_channels = hop_channels;
-
-    return sniff_pmkid_stage;
-}
-
-
-WifiMarauderScriptStageSniffPwn* _wifi_marauder_script_get_stage_sniff_pwn(cJSON* stages) {
-    cJSON* sniffpwn_stage_json = cJSON_GetObjectItem(stages, "sniffpwn");
-    if(sniffpwn_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* timeout_json = cJSON_GetObjectItem(sniffpwn_stage_json, "timeout");
-    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
-                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
-
-    WifiMarauderScriptStageSniffPwn* sniff_pwn_stage =
-        (WifiMarauderScriptStageSniffPwn*)malloc(sizeof(WifiMarauderScriptStageSniffPwn));
-    sniff_pwn_stage->timeout = timeout;
-
-    return sniff_pwn_stage;
-}
-
-WifiMarauderScriptStageBeaconList* _wifi_marauder_script_get_stage_beacon_list(cJSON* stages) {
-    cJSON* stage_beaconlist = cJSON_GetObjectItem(stages, "beaconList");
-    if(stage_beaconlist == NULL) {
-        return NULL;
-    }
-    WifiMarauderScriptStageBeaconList* beaconlist_stage =
-        (WifiMarauderScriptStageBeaconList*)malloc(sizeof(WifiMarauderScriptStageBeaconList));
-    if(beaconlist_stage == NULL) {
-        return NULL;
-    }
-    cJSON* ssids = cJSON_GetObjectItem(stage_beaconlist, "ssids");
-    if(ssids == NULL) {
-        return NULL;
-    }
-    // SSID count
-    int ssid_count = cJSON_GetArraySize(ssids);
-    if(ssid_count == 0) {
-        return NULL;
-    }
-    beaconlist_stage->ssid_count = ssid_count;
-    // SSIDs
-    beaconlist_stage->ssids = (char**)malloc(sizeof(char*) * ssid_count);
-    if(beaconlist_stage->ssids == NULL) {
-        return NULL;
-    }
-    for(int i = 0; i < ssid_count; i++) {
-        cJSON* ssid = cJSON_GetArrayItem(ssids, i);
-        if(ssid == NULL) {
-            continue;
-        }
-        char* ssid_string = cJSON_GetStringValue(ssid);
-        if(ssid_string == NULL) {
-            continue;
-        }
-        beaconlist_stage->ssids[i] = (char*)malloc(sizeof(char) * (strlen(ssid_string) + 1));
-        strcpy(beaconlist_stage->ssids[i], ssid_string);
-    }
-    // Timeout
-    cJSON* timeout = cJSON_GetObjectItem(stage_beaconlist, "timeout");
-    beaconlist_stage->timeout = timeout != NULL ? (int)cJSON_GetNumberValue(timeout) :
-                                                  WIFI_MARAUDER_DEFAULT_TIMEOUT_BEACON;
-    // Random SSIDs
-    cJSON* random_ssids = cJSON_GetObjectItem(stage_beaconlist, "generate");
-    beaconlist_stage->random_ssids =
-        random_ssids != NULL ? (int)cJSON_GetNumberValue(random_ssids) : 0;
-
-    return beaconlist_stage;
-}
-
-WifiMarauderScriptStageBeaconAp* _wifi_marauder_script_get_stage_beacon_ap(cJSON* stages) {
-    cJSON* beaconap_stage_json = cJSON_GetObjectItem(stages, "beaconAp");
-    if(beaconap_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* timeout_json = cJSON_GetObjectItem(beaconap_stage_json, "timeout");
-    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
-                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_BEACON;
-
-    WifiMarauderScriptStageBeaconAp* beacon_ap_stage =
-        (WifiMarauderScriptStageBeaconAp*)malloc(sizeof(WifiMarauderScriptStageBeaconAp));
-    beacon_ap_stage->timeout = timeout;
-
-    return beacon_ap_stage;
-}
-
-WifiMarauderScriptStageExec* _wifi_marauder_script_get_stage_exec(cJSON* stages) {
-    cJSON* exec_stage_json = cJSON_GetObjectItem(stages, "exec");
-    if(exec_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* command_json = cJSON_GetObjectItemCaseSensitive(exec_stage_json, "command");
-    char* command_str = cJSON_IsString(command_json) ? strdup(command_json->valuestring) : NULL;
-
-    WifiMarauderScriptStageExec* exec_stage =
-        (WifiMarauderScriptStageExec*)malloc(sizeof(WifiMarauderScriptStageExec));
-    exec_stage->command = command_str;
-
-    return exec_stage;
-}
-
-WifiMarauderScriptStageDelay* _wifi_marauder_script_get_stage_delay(cJSON* stages) {
-    cJSON* delay_stage_json = cJSON_GetObjectItem(stages, "delay");
-    if(delay_stage_json == NULL) {
-        return NULL;
-    }
-
-    cJSON* timeout_json = cJSON_GetObjectItem(delay_stage_json, "timeout");
-    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) : 0;
-
-    WifiMarauderScriptStageDelay* delay_stage =
-        (WifiMarauderScriptStageDelay*)malloc(sizeof(WifiMarauderScriptStageDelay));
-    delay_stage->timeout = timeout;
-
-    return delay_stage;
-}
-
-WifiMarauderScriptStage*
-    _wifi_marauder_script_create_stage(WifiMarauderScriptStageType type, void* stage_data) {
-    WifiMarauderScriptStage* stage =
-        (WifiMarauderScriptStage*)malloc(sizeof(WifiMarauderScriptStage));
-    stage->type = type;
-    stage->stage = stage_data;
-    stage->next_stage = NULL;
-    return stage;
-}
-
-void wifi_marauder_script_add_stage(
-    WifiMarauderScript* script,
-    WifiMarauderScriptStageType stage_type,
-    void* stage_data) {
-    if(script == NULL || stage_data == NULL) {
-        return;
-    }
-    WifiMarauderScriptStage* stage = _wifi_marauder_script_create_stage(stage_type, stage_data);
-    if(script->last_stage != NULL) {
-        script->last_stage->next_stage = stage;
-    } else {
-        script->first_stage = stage;
-    }
-    script->last_stage = stage;
-}
-
-void _wifi_marauder_script_load_stages(WifiMarauderScript* script, cJSON* stages) {
-    // Scan stage
-    wifi_marauder_script_add_stage(
-        script, WifiMarauderScriptStageTypeScan, _wifi_marauder_script_get_stage_scan(stages));
-    // Select stage
-    wifi_marauder_script_add_stage(
-        script, WifiMarauderScriptStageTypeSelect, _wifi_marauder_script_get_stage_select(stages));
-    // Deauth stage
-    wifi_marauder_script_add_stage(
-        script, WifiMarauderScriptStageTypeDeauth, _wifi_marauder_script_get_stage_deauth(stages));
-    // Probe stage
-    wifi_marauder_script_add_stage(
-        script, WifiMarauderScriptStageTypeProbe, _wifi_marauder_script_get_stage_probe(stages));
-    // Sniff raw stage
-    wifi_marauder_script_add_stage(
-        script,
-        WifiMarauderScriptStageTypeSniffRaw,
-        _wifi_marauder_script_get_stage_sniff_raw(stages));
-    // Sniff beacon stage
-    wifi_marauder_script_add_stage(
-        script,
-        WifiMarauderScriptStageTypeSniffBeacon,
-        _wifi_marauder_script_get_stage_sniff_beacon(stages));
-    // Sniff deauth stage
-    wifi_marauder_script_add_stage(
-        script,
-        WifiMarauderScriptStageTypeSniffDeauth,
-        _wifi_marauder_script_get_stage_sniff_deauth(stages));
-    // Sniff esp stage
-    wifi_marauder_script_add_stage(
-        script,
-        WifiMarauderScriptStageTypeSniffEsp,
-        _wifi_marauder_script_get_stage_sniff_esp(stages));
-    // Sniff PMKID stage
-    wifi_marauder_script_add_stage(
-        script,
-        WifiMarauderScriptStageTypeSniffPmkid,
-        _wifi_marauder_script_get_stage_sniff_pmkid(stages));
-    // Sniff pwn stage
-    wifi_marauder_script_add_stage(
-        script,
-        WifiMarauderScriptStageTypeSniffPwn,
-        _wifi_marauder_script_get_stage_sniff_pwn(stages));
-    // Beacon List stage
-    wifi_marauder_script_add_stage(
-        script,
-        WifiMarauderScriptStageTypeBeaconList,
-        _wifi_marauder_script_get_stage_beacon_list(stages));
-    // Beacon Ap stage
-    wifi_marauder_script_add_stage(
-        script,
-        WifiMarauderScriptStageTypeBeaconAp,
-        _wifi_marauder_script_get_stage_beacon_ap(stages));
-    // Exec stage
-    wifi_marauder_script_add_stage(
-        script, WifiMarauderScriptStageTypeExec, _wifi_marauder_script_get_stage_exec(stages));
-    // Delay stage
-    wifi_marauder_script_add_stage(
-        script, WifiMarauderScriptStageTypeDelay, _wifi_marauder_script_get_stage_delay(stages));
-}
-
-WifiMarauderScript* wifi_marauder_script_parse_raw(const char* json_raw) {
-    WifiMarauderScript* script = wifi_marauder_script_alloc();
-    if(script == NULL) {
-        return NULL;
-    }
-    cJSON* json = cJSON_Parse(json_raw);
-    if(json == NULL) {
-        return NULL;
-    }
-    cJSON* meta = cJSON_GetObjectItem(json, "meta");
-    _wifi_marauder_script_load_meta(script, meta);
-
-    cJSON* stages = cJSON_GetObjectItem(json, "stages");
-    if(cJSON_IsArray(stages)) {
-        cJSON* stage_item = NULL;
-        cJSON_ArrayForEach(stage_item, stages) {
-            _wifi_marauder_script_load_stages(script, stage_item);
-        }
-    } else {
-        _wifi_marauder_script_load_stages(script, stages);
-    }
-
-    return script;
-}
-
-WifiMarauderScript* wifi_marauder_script_parse_json(Storage* storage, const char* file_path) {
-    WifiMarauderScript* script = NULL;
-    File* script_file = storage_file_alloc(storage);
-    FuriString* script_name = furi_string_alloc();
-    path_extract_filename_no_ext(file_path, script_name);
-
-    if(storage_file_open(script_file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
-        uint32_t file_size = storage_file_size(script_file);
-        char* json_buffer = (char*)malloc(file_size + 1);
-        uint16_t bytes_read = storage_file_read(script_file, json_buffer, file_size);
-        json_buffer[bytes_read] = '\0';
-
-        script = wifi_marauder_script_parse_raw(json_buffer);
-    }
-    if(script == NULL) {
-        script = wifi_marauder_script_create(furi_string_get_cstr(script_name));
-    }
-    script->name = strdup(furi_string_get_cstr(script_name));
-
-    furi_string_free(script_name);
-    storage_file_close(script_file);
-    storage_file_free(script_file);
-    return script;
-}
-
-cJSON* _wifi_marauder_script_create_json_meta(WifiMarauderScript* script) {
-    cJSON* meta_json = cJSON_CreateObject();
-    if(script->description != NULL) {
-        cJSON_AddStringToObject(meta_json, "description", script->description);
-    } else {
-        cJSON_AddStringToObject(meta_json, "description", "My Script");
-    }
-    if(script->enable_led != WifiMarauderScriptBooleanUndefined) {
-        cJSON_AddBoolToObject(
-            meta_json, "enableLed", (script->enable_led == WifiMarauderScriptBooleanTrue));
-    }
-    if(script->save_pcap != WifiMarauderScriptBooleanUndefined) {
-        cJSON_AddBoolToObject(
-            meta_json, "savePcap", (script->save_pcap == WifiMarauderScriptBooleanTrue));
-    }
-    cJSON_AddNumberToObject(meta_json, "repeat", script->repeat);
-    return meta_json;
-}
-
-cJSON* _wifi_marauder_script_create_json_scan(WifiMarauderScriptStageScan* scan_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "scan", cJSON_CreateObject());
-    cJSON* scan_json = cJSON_GetObjectItem(stage_json, "scan");
-    // Scan type
-    cJSON_AddStringToObject(
-        scan_json, "type", scan_stage->type == WifiMarauderScriptScanTypeAp ? "ap" : "station");
-    // Channel
-    if(scan_stage->channel > 0) {
-        cJSON_AddNumberToObject(scan_json, "channel", scan_stage->channel);
-    }
-    // Timeout
-    if(scan_stage->timeout > 0) {
-        cJSON_AddNumberToObject(scan_json, "timeout", scan_stage->timeout);
-    }
-    return stage_json;
-}
-
-cJSON* _wifi_marauder_script_create_json_select(WifiMarauderScriptStageSelect* select_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "select", cJSON_CreateObject());
-    cJSON* select_json = cJSON_GetObjectItem(stage_json, "select");
-    // Select type
-    cJSON_AddStringToObject(
-        select_json,
-        "type",
-        select_stage->type == WifiMarauderScriptSelectTypeAp      ? "ap" :
-        select_stage->type == WifiMarauderScriptSelectTypeStation ? "station" :
-                                                                    "ssid");
-    if(select_stage->filter != NULL) {
-        cJSON_AddStringToObject(select_json, "filter", select_stage->filter);
-    }
-    // Indexes
-    if(select_stage->indexes != NULL && select_stage->index_count > 0) {
-        cJSON* indexes_json = cJSON_CreateArray();
-        for(int i = 0; i < select_stage->index_count; i++) {
-            cJSON_AddItemToArray(indexes_json, cJSON_CreateNumber(select_stage->indexes[i]));
-        }
-        cJSON_AddItemToObject(select_json, "indexes", indexes_json);
-    }
-    return stage_json;
-}
-
-cJSON* _wifi_marauder_script_create_json_deauth(WifiMarauderScriptStageDeauth* deauth_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "deauth", cJSON_CreateObject());
-    cJSON* deauth_json = cJSON_GetObjectItem(stage_json, "deauth");
-    // Timeout
-    if(deauth_stage->timeout > 0) {
-        cJSON_AddNumberToObject(deauth_json, "timeout", deauth_stage->timeout);
-    }
-    return stage_json;
-}
-
-cJSON* _wifi_marauder_script_create_json_probe(WifiMarauderScriptStageProbe* probe_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "probe", cJSON_CreateObject());
-    cJSON* probe_json = cJSON_GetObjectItem(stage_json, "probe");
-    // Timeout
-    if(probe_stage->timeout > 0) {
-        cJSON_AddNumberToObject(probe_json, "timeout", probe_stage->timeout);
-    }
-    return stage_json;
-}
-
-cJSON*
-    _wifi_marauder_script_create_json_sniffraw(WifiMarauderScriptStageSniffRaw* sniffraw_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "sniffRaw", cJSON_CreateObject());
-    cJSON* sniffraw_json = cJSON_GetObjectItem(stage_json, "sniffRaw");
-    // Timeout
-    if(sniffraw_stage->timeout > 0) {
-        cJSON_AddNumberToObject(sniffraw_json, "timeout", sniffraw_stage->timeout);
-    }
-    return stage_json;
-}
-
-cJSON* _wifi_marauder_script_create_json_sniffbeacon(
-    WifiMarauderScriptStageSniffBeacon* sniffbeacon_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "sniffBeacon", cJSON_CreateObject());
-    cJSON* sniffbeacon_json = cJSON_GetObjectItem(stage_json, "sniffBeacon");
-    // Timeout
-    if(sniffbeacon_stage->timeout > 0) {
-        cJSON_AddNumberToObject(sniffbeacon_json, "timeout", sniffbeacon_stage->timeout);
-    }
-    return stage_json;
-}
-
-cJSON* _wifi_marauder_script_create_json_sniffdeauth(
-    WifiMarauderScriptStageSniffDeauth* sniffdeauth_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "sniffDeauth", cJSON_CreateObject());
-    cJSON* sniffdeauth_json = cJSON_GetObjectItem(stage_json, "sniffDeauth");
-    // Timeout
-    if(sniffdeauth_stage->timeout > 0) {
-        cJSON_AddNumberToObject(sniffdeauth_json, "timeout", sniffdeauth_stage->timeout);
-    }
-    return stage_json;
-}
-
-cJSON*
-    _wifi_marauder_script_create_json_sniffesp(WifiMarauderScriptStageSniffEsp* sniffesp_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "sniffEsp", cJSON_CreateObject());
-    cJSON* sniffesp_json = cJSON_GetObjectItem(stage_json, "sniffEsp");
-    // Timeout
-    if(sniffesp_stage->timeout > 0) {
-        cJSON_AddNumberToObject(sniffesp_json, "timeout", sniffesp_stage->timeout);
-    }
-    return stage_json;
-}
-
-cJSON* _wifi_marauder_script_create_json_sniffpmkid(
-    WifiMarauderScriptStageSniffPmkid* sniffpmkid_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "sniffPmkid", cJSON_CreateObject());
-    cJSON* sniffpmkid_json = cJSON_GetObjectItem(stage_json, "sniffPmkid");
-    // Force deauth
-    cJSON_AddBoolToObject(sniffpmkid_json, "forceDeauth", sniffpmkid_stage->force_deauth);
-    // Channel
-    if(sniffpmkid_stage->channel > 0) {
-        cJSON_AddNumberToObject(sniffpmkid_json, "channel", sniffpmkid_stage->channel);
-    }
-    // Timeout
-    if(sniffpmkid_stage->timeout > 0) {
-        cJSON_AddNumberToObject(sniffpmkid_json, "timeout", sniffpmkid_stage->timeout);
-    }
-    // Hop channels
-    cJSON_AddBoolToObject(sniffpmkid_json, "hopChannels", sniffpmkid_stage->hop_channels);
-
-    return stage_json;
-}
-
-cJSON*
-    _wifi_marauder_script_create_json_sniffpwn(WifiMarauderScriptStageSniffPwn* sniffpwn_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "sniffPwn", cJSON_CreateObject());
-    cJSON* sniffpwn_json = cJSON_GetObjectItem(stage_json, "sniffPwn");
-    // Timeout
-    if(sniffpwn_stage->timeout > 0) {
-        cJSON_AddNumberToObject(sniffpwn_json, "timeout", sniffpwn_stage->timeout);
-    }
-    return stage_json;
-}
-
-cJSON* _wifi_marauder_script_create_json_beaconlist(
-    WifiMarauderScriptStageBeaconList* beaconlist_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "beaconList", cJSON_CreateObject());
-    cJSON* beaconlist_json = cJSON_GetObjectItem(stage_json, "beaconList");
-    // SSIDs
-    if(beaconlist_stage->ssids != NULL) {
-        cJSON* ssids_json = cJSON_CreateStringArray(
-            (const char**)beaconlist_stage->ssids, beaconlist_stage->ssid_count);
-        cJSON_AddItemToObject(beaconlist_json, "ssids", ssids_json);
-    }
-    // Random SSIDs
-    if(beaconlist_stage->random_ssids > 0) {
-        cJSON_AddNumberToObject(beaconlist_json, "generate", beaconlist_stage->random_ssids);
-    }
-    // Timeout
-    if(beaconlist_stage->timeout > 0) {
-        cJSON_AddNumberToObject(beaconlist_json, "timeout", beaconlist_stage->timeout);
-    }
-    return stage_json;
-}
-
-cJSON*
-    _wifi_marauder_script_create_json_beaconap(WifiMarauderScriptStageBeaconAp* beaconap_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "beaconAp", cJSON_CreateObject());
-    cJSON* beaconap_json = cJSON_GetObjectItem(stage_json, "beaconAp");
-    // Timeout
-    if(beaconap_stage->timeout > 0) {
-        cJSON_AddNumberToObject(beaconap_json, "timeout", beaconap_stage->timeout);
-    }
-    return stage_json;
-}
-
-cJSON* _wifi_marauder_script_create_json_exec(WifiMarauderScriptStageExec* exec_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "exec", cJSON_CreateObject());
-    cJSON* exec_json = cJSON_GetObjectItem(stage_json, "exec");
-    // Command
-    cJSON_AddStringToObject(
-        exec_json, "command", exec_stage->command != NULL ? exec_stage->command : "");
-    return stage_json;
-}
-
-cJSON* _wifi_marauder_script_create_json_delay(WifiMarauderScriptStageDelay* delay_stage) {
-    cJSON* stage_json = cJSON_CreateObject();
-    cJSON_AddItemToObject(stage_json, "delay", cJSON_CreateObject());
-    cJSON* delay_json = cJSON_GetObjectItem(stage_json, "delay");
-    // Timeout
-    if(delay_stage->timeout > 0) {
-        cJSON_AddNumberToObject(delay_json, "timeout", delay_stage->timeout);
-    }
-    return stage_json;
-}
-
-void wifi_marauder_script_save_json(
-    Storage* storage,
-    const char* file_path,
-    WifiMarauderScript* script) {
-    File* script_file = storage_file_alloc(storage);
-
-    if(storage_file_open(script_file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
-        cJSON* root_json = cJSON_CreateObject();
-
-        // Meta info
-        cJSON* meta_json = _wifi_marauder_script_create_json_meta(script);
-        cJSON_AddItemToObject(root_json, "meta", meta_json);
-
-        // Create array for stages
-        cJSON* stages_array = cJSON_CreateArray();
-        cJSON_AddItemToObject(root_json, "stages", stages_array);
-
-        // Iterate over each stage and create the corresponding JSON object
-        WifiMarauderScriptStage* stage = script->first_stage;
-        while(stage != NULL) {
-            cJSON* stage_json = NULL;
-
-            switch(stage->type) {
-            case WifiMarauderScriptStageTypeScan: {
-                WifiMarauderScriptStageScan* scan_stage =
-                    (WifiMarauderScriptStageScan*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_scan(scan_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeSelect: {
-                WifiMarauderScriptStageSelect* select_stage =
-                    (WifiMarauderScriptStageSelect*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_select(select_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeDeauth: {
-                WifiMarauderScriptStageDeauth* deauth_stage =
-                    (WifiMarauderScriptStageDeauth*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_deauth(deauth_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeProbe: {
-                WifiMarauderScriptStageProbe* probe_stage =
-                    (WifiMarauderScriptStageProbe*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_probe(probe_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeSniffRaw: {
-                WifiMarauderScriptStageSniffRaw* sniffraw_stage =
-                    (WifiMarauderScriptStageSniffRaw*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_sniffraw(sniffraw_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeSniffBeacon: {
-                WifiMarauderScriptStageSniffBeacon* sniffbeacon_stage =
-                    (WifiMarauderScriptStageSniffBeacon*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_sniffbeacon(sniffbeacon_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeSniffDeauth: {
-                WifiMarauderScriptStageSniffDeauth* sniffdeauth_stage =
-                    (WifiMarauderScriptStageSniffDeauth*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_sniffdeauth(sniffdeauth_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeSniffEsp: {
-                WifiMarauderScriptStageSniffEsp* sniffesp_stage =
-                    (WifiMarauderScriptStageSniffEsp*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_sniffesp(sniffesp_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeSniffPmkid: {
-                WifiMarauderScriptStageSniffPmkid* sniffpmkid_stage =
-                    (WifiMarauderScriptStageSniffPmkid*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_sniffpmkid(sniffpmkid_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeSniffPwn: {
-                WifiMarauderScriptStageSniffPwn* sniffpwn_stage =
-                    (WifiMarauderScriptStageSniffPwn*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_sniffpwn(sniffpwn_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeBeaconList: {
-                WifiMarauderScriptStageBeaconList* beaconlist_stage =
-                    (WifiMarauderScriptStageBeaconList*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_beaconlist(beaconlist_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeBeaconAp: {
-                WifiMarauderScriptStageBeaconAp* beaconap_stage =
-                    (WifiMarauderScriptStageBeaconAp*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_beaconap(beaconap_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeExec: {
-                WifiMarauderScriptStageExec* exec_stage =
-                    (WifiMarauderScriptStageExec*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_exec(exec_stage);
-                break;
-            }
-            case WifiMarauderScriptStageTypeDelay: {
-                WifiMarauderScriptStageDelay* delay_stage =
-                    (WifiMarauderScriptStageDelay*)stage->stage;
-                stage_json = _wifi_marauder_script_create_json_delay(delay_stage);
-                break;
-            }
-            }
-
-            // Add the stage JSON object to the "stages" array
-            if(stage_json != NULL) {
-                cJSON_AddItemToArray(stages_array, stage_json);
-            }
-
-            stage = stage->next_stage;
-        }
-
-        // Write JSON to file
-        char* json_str = cJSON_Print(root_json);
-        storage_file_write(script_file, json_str, strlen(json_str));
-
-        //free(json_str);
-        storage_file_close(script_file);
-    }
-    storage_file_free(script_file);
-}
-
-bool wifi_marauder_script_has_stage(
-    WifiMarauderScript* script,
-    WifiMarauderScriptStageType stage_type) {
-    if(script == NULL) {
-        return false;
-    }
-    WifiMarauderScriptStage* current_stage = script->first_stage;
-    while(current_stage != NULL) {
-        if(current_stage->type == stage_type) {
-            return true;
-        }
-        current_stage = current_stage->next_stage;
-    }
-    return false;
-}
-
-void wifi_marauder_script_free(WifiMarauderScript* script) {
-    if(script == NULL) {
-        return;
-    }
-    WifiMarauderScriptStage* current_stage = script->first_stage;
-    while(current_stage != NULL) {
-        WifiMarauderScriptStage* next_stage = current_stage->next_stage;
-        switch(current_stage->type) {
-        case WifiMarauderScriptStageTypeScan:
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeSelect:
-            if(((WifiMarauderScriptStageSelect*)current_stage->stage)->filter != NULL) {
-                free(((WifiMarauderScriptStageSelect*)current_stage->stage)->filter);
-            }
-            if(((WifiMarauderScriptStageSelect*)current_stage->stage)->indexes != NULL) {
-                free(((WifiMarauderScriptStageSelect*)current_stage->stage)->indexes);
-            }
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeDeauth:
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeProbe:
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeSniffRaw:
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeSniffBeacon:
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeSniffDeauth:
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeSniffEsp:
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeSniffPmkid:
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeSniffPwn:
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeBeaconList:
-            for(int i = 0;
-                i < ((WifiMarauderScriptStageBeaconList*)current_stage->stage)->ssid_count;
-                i++) {
-                free(((WifiMarauderScriptStageBeaconList*)current_stage->stage)->ssids[i]);
-            }
-            free(((WifiMarauderScriptStageBeaconList*)current_stage->stage)->ssids);
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeBeaconAp:
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeExec:
-            if(((WifiMarauderScriptStageExec*)current_stage->stage)->command != NULL) {
-                free(((WifiMarauderScriptStageExec*)current_stage->stage)->command);
-            }
-            free(current_stage->stage);
-            break;
-        case WifiMarauderScriptStageTypeDelay:
-            free(current_stage->stage);
-            break;
-        }
-        free(current_stage);
-        current_stage = next_stage;
-    }
-    free(script->name);
-    free(script->description);
-    free(script);
+#include "../wifi_marauder_app_i.h"
+#include "wifi_marauder_script.h"
+
+WifiMarauderScript* wifi_marauder_script_alloc() {
+    WifiMarauderScript* script = (WifiMarauderScript*)malloc(sizeof(WifiMarauderScript));
+    if(script == NULL) {
+        return NULL;
+    }
+    script->name = NULL;
+    script->description = NULL;
+    script->first_stage = NULL;
+    script->last_stage = NULL;
+    script->enable_led = WifiMarauderScriptBooleanUndefined;
+    script->save_pcap = WifiMarauderScriptBooleanUndefined;
+    script->repeat = 1;
+    return script;
+}
+
+WifiMarauderScript* wifi_marauder_script_create(const char* script_name) {
+    WifiMarauderScript* script = wifi_marauder_script_alloc();
+    script->name = strdup(script_name);
+    return script;
+}
+
+void _wifi_marauder_script_load_meta(WifiMarauderScript* script, cJSON* meta_section) {
+    if(meta_section != NULL) {
+        // Script description
+        cJSON* description = cJSON_GetObjectItem(meta_section, "description");
+        if(description != NULL) {
+            script->description = strdup(description->valuestring);
+        }
+        // Enable LED
+        cJSON* enable_led_json = cJSON_GetObjectItem(meta_section, "enableLed");
+        if(cJSON_IsBool(enable_led_json)) {
+            script->enable_led = enable_led_json->valueint;
+        }
+        // Save PCAP
+        cJSON* save_pcap_json = cJSON_GetObjectItem(meta_section, "savePcap");
+        if(cJSON_IsBool(save_pcap_json)) {
+            script->save_pcap = save_pcap_json->valueint;
+        }
+        // Times the script will be repeated
+        cJSON* repeat = cJSON_GetObjectItem(meta_section, "repeat");
+        if(repeat != NULL) {
+            script->repeat = repeat->valueint;
+        }
+    }
+    if(script->description == NULL) {
+        script->description = strdup("My script");
+    }
+}
+
+WifiMarauderScriptStageScan* _wifi_marauder_script_get_stage_scan(cJSON* stages) {
+    cJSON* stage_scan = cJSON_GetObjectItem(stages, "scan");
+    if(stage_scan == NULL) {
+        return NULL;
+    }
+    cJSON* type = cJSON_GetObjectItem(stage_scan, "type");
+    if(type == NULL) {
+        return NULL;
+    }
+    WifiMarauderScriptScanType scan_type;
+    if(strcmp(type->valuestring, "ap") == 0) {
+        scan_type = WifiMarauderScriptScanTypeAp;
+    } else if(strcmp(type->valuestring, "station") == 0) {
+        scan_type = WifiMarauderScriptScanTypeStation;
+    } else {
+        return NULL;
+    }
+    cJSON* channel = cJSON_GetObjectItem(stage_scan, "channel");
+    int scan_channel = channel != NULL ? (int)cJSON_GetNumberValue(channel) : 0;
+    cJSON* timeout = cJSON_GetObjectItem(stage_scan, "timeout");
+    int scan_timeout = timeout != NULL ? (int)cJSON_GetNumberValue(timeout) :
+                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SCAN;
+
+    WifiMarauderScriptStageScan* scan_stage =
+        (WifiMarauderScriptStageScan*)malloc(sizeof(WifiMarauderScriptStageScan));
+    scan_stage->type = scan_type;
+    scan_stage->channel = scan_channel;
+    scan_stage->timeout = scan_timeout;
+
+    return scan_stage;
+}
+
+WifiMarauderScriptStageSelect* _wifi_marauder_script_get_stage_select(cJSON* stages) {
+    cJSON* select_stage_json = cJSON_GetObjectItemCaseSensitive(stages, "select");
+    if(select_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* type_json = cJSON_GetObjectItemCaseSensitive(select_stage_json, "type");
+    cJSON* filter_json = cJSON_GetObjectItemCaseSensitive(select_stage_json, "filter");
+    cJSON* indexes_json = cJSON_GetObjectItemCaseSensitive(select_stage_json, "indexes");
+    cJSON* allow_repeat_json = cJSON_GetObjectItemCaseSensitive(select_stage_json, "allow_repeat");
+
+    if(!cJSON_IsString(type_json)) {
+        return NULL;
+    }
+    WifiMarauderScriptSelectType select_type;
+    if(strcmp(type_json->valuestring, "ap") == 0) {
+        select_type = WifiMarauderScriptSelectTypeAp;
+    } else if(strcmp(type_json->valuestring, "station") == 0) {
+        select_type = WifiMarauderScriptSelectTypeStation;
+    } else if(strcmp(type_json->valuestring, "ssid") == 0) {
+        select_type = WifiMarauderScriptSelectTypeSsid;
+    } else {
+        return NULL;
+    }
+    char* filter_str = cJSON_IsString(filter_json) ? strdup(filter_json->valuestring) : NULL;
+
+    WifiMarauderScriptStageSelect* stage_select =
+        (WifiMarauderScriptStageSelect*)malloc(sizeof(WifiMarauderScriptStageSelect));
+    stage_select->type = select_type;
+    stage_select->allow_repeat = cJSON_IsBool(allow_repeat_json) ? allow_repeat_json->valueint :
+                                                                   true;
+    stage_select->filter = filter_str;
+
+    if(cJSON_IsArray(indexes_json)) {
+        int indexes_size = cJSON_GetArraySize(indexes_json);
+        int* indexes = (int*)malloc(indexes_size * sizeof(int));
+        for(int i = 0; i < indexes_size; i++) {
+            cJSON* index_item = cJSON_GetArrayItem(indexes_json, i);
+            if(cJSON_IsNumber(index_item)) {
+                indexes[i] = index_item->valueint;
+            }
+        }
+        stage_select->indexes = indexes;
+        stage_select->index_count = indexes_size;
+    } else {
+        stage_select->indexes = NULL;
+        stage_select->index_count = 0;
+    }
+
+    return stage_select;
+}
+
+WifiMarauderScriptStageDeauth* _wifi_marauder_script_get_stage_deauth(cJSON* stages) {
+    cJSON* deauth_stage_json = cJSON_GetObjectItemCaseSensitive(stages, "deauth");
+    if(deauth_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* timeout = cJSON_GetObjectItem(deauth_stage_json, "timeout");
+    int deauth_timeout = timeout != NULL ? (int)cJSON_GetNumberValue(timeout) :
+                                           WIFI_MARAUDER_DEFAULT_TIMEOUT_DEAUTH;
+
+    WifiMarauderScriptStageDeauth* deauth_stage =
+        (WifiMarauderScriptStageDeauth*)malloc(sizeof(WifiMarauderScriptStageDeauth));
+    deauth_stage->timeout = deauth_timeout;
+
+    return deauth_stage;
+}
+
+WifiMarauderScriptStageProbe* _wifi_marauder_script_get_stage_probe(cJSON* stages) {
+    cJSON* probe_stage_json = cJSON_GetObjectItemCaseSensitive(stages, "probe");
+    if(probe_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* timeout = cJSON_GetObjectItem(probe_stage_json, "timeout");
+    int probe_timeout = timeout != NULL ? (int)cJSON_GetNumberValue(timeout) :
+                                          WIFI_MARAUDER_DEFAULT_TIMEOUT_PROBE;
+
+    WifiMarauderScriptStageProbe* probe_stage =
+        (WifiMarauderScriptStageProbe*)malloc(sizeof(WifiMarauderScriptStageProbe));
+    probe_stage->timeout = probe_timeout;
+
+    return probe_stage;
+}
+
+WifiMarauderScriptStageSniffRaw* _wifi_marauder_script_get_stage_sniff_raw(cJSON* stages) {
+    cJSON* sniffraw_stage_json = cJSON_GetObjectItem(stages, "sniffraw");
+    if(sniffraw_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* timeout_json = cJSON_GetObjectItem(sniffraw_stage_json, "timeout");
+    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
+                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+
+    WifiMarauderScriptStageSniffRaw* sniff_raw_stage =
+        (WifiMarauderScriptStageSniffRaw*)malloc(sizeof(WifiMarauderScriptStageSniffRaw));
+    sniff_raw_stage->timeout = timeout;
+
+    return sniff_raw_stage;
+}
+
+WifiMarauderScriptStageSniffBeacon* _wifi_marauder_script_get_stage_sniff_beacon(cJSON* stages) {
+    cJSON* sniffbeacon_stage_json = cJSON_GetObjectItem(stages, "sniffbeacon");
+    if(sniffbeacon_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* timeout_json = cJSON_GetObjectItem(sniffbeacon_stage_json, "timeout");
+    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
+                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+
+    WifiMarauderScriptStageSniffBeacon* sniff_beacon_stage =
+        (WifiMarauderScriptStageSniffBeacon*)malloc(sizeof(WifiMarauderScriptStageSniffBeacon));
+    sniff_beacon_stage->timeout = timeout;
+
+    return sniff_beacon_stage;
+}
+
+WifiMarauderScriptStageSniffDeauth* _wifi_marauder_script_get_stage_sniff_deauth(cJSON* stages) {
+    cJSON* sniffdeauth_stage_json = cJSON_GetObjectItem(stages, "sniffdeauth");
+    if(sniffdeauth_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* timeout_json = cJSON_GetObjectItem(sniffdeauth_stage_json, "timeout");
+    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
+                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+
+    WifiMarauderScriptStageSniffDeauth* sniff_deauth_stage =
+        (WifiMarauderScriptStageSniffDeauth*)malloc(sizeof(WifiMarauderScriptStageSniffDeauth));
+    sniff_deauth_stage->timeout = timeout;
+
+    return sniff_deauth_stage;
+}
+
+WifiMarauderScriptStageSniffEsp* _wifi_marauder_script_get_stage_sniff_esp(cJSON* stages) {
+    cJSON* sniffesp_stage_json = cJSON_GetObjectItem(stages, "sniffesp");
+    if(sniffesp_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* timeout_json = cJSON_GetObjectItem(sniffesp_stage_json, "timeout");
+    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
+                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+
+    WifiMarauderScriptStageSniffEsp* sniff_esp_stage =
+        (WifiMarauderScriptStageSniffEsp*)malloc(sizeof(WifiMarauderScriptStageSniffEsp));
+    sniff_esp_stage->timeout = timeout;
+
+    return sniff_esp_stage;
+}
+
+WifiMarauderScriptStageSniffPmkid* _wifi_marauder_script_get_stage_sniff_pmkid(cJSON* stages) {
+    cJSON* sniffpmkid_stage_json = cJSON_GetObjectItem(stages, "sniffpmkid");
+    if(sniffpmkid_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* channel_json = cJSON_GetObjectItem(sniffpmkid_stage_json, "channel");
+    int channel = channel_json != NULL ? (int)cJSON_GetNumberValue(channel_json) : 0;
+
+    cJSON* timeout_json = cJSON_GetObjectItem(sniffpmkid_stage_json, "timeout");
+    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
+                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+
+    cJSON* force_deauth_json =
+        cJSON_GetObjectItemCaseSensitive(sniffpmkid_stage_json, "forceDeauth");
+    bool force_deauth = cJSON_IsBool(force_deauth_json) ? force_deauth_json->valueint : true;
+
+    cJSON* hop_channels_json =
+        cJSON_GetObjectItemCaseSensitive(sniffpmkid_stage_json, "hopChannels");
+    bool hop_channels = cJSON_IsBool(hop_channels_json) ? hop_channels_json->valueint : false;
+
+    WifiMarauderScriptStageSniffPmkid* sniff_pmkid_stage =
+        (WifiMarauderScriptStageSniffPmkid*)malloc(sizeof(WifiMarauderScriptStageSniffPmkid));
+
+    if(sniff_pmkid_stage == NULL) {
+        // Handle memory allocation error
+        return NULL;
+    }
+    sniff_pmkid_stage->channel = channel;
+    sniff_pmkid_stage->timeout = timeout;
+    sniff_pmkid_stage->force_deauth = force_deauth;
+    sniff_pmkid_stage->hop_channels = hop_channels;
+
+    return sniff_pmkid_stage;
+}
+
+WifiMarauderScriptStageSniffPwn* _wifi_marauder_script_get_stage_sniff_pwn(cJSON* stages) {
+    cJSON* sniffpwn_stage_json = cJSON_GetObjectItem(stages, "sniffpwn");
+    if(sniffpwn_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* timeout_json = cJSON_GetObjectItem(sniffpwn_stage_json, "timeout");
+    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
+                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+
+    WifiMarauderScriptStageSniffPwn* sniff_pwn_stage =
+        (WifiMarauderScriptStageSniffPwn*)malloc(sizeof(WifiMarauderScriptStageSniffPwn));
+    sniff_pwn_stage->timeout = timeout;
+
+    return sniff_pwn_stage;
+}
+
+WifiMarauderScriptStageBeaconList* _wifi_marauder_script_get_stage_beacon_list(cJSON* stages) {
+    cJSON* stage_beaconlist = cJSON_GetObjectItem(stages, "beaconList");
+    if(stage_beaconlist == NULL) {
+        return NULL;
+    }
+    WifiMarauderScriptStageBeaconList* beaconlist_stage =
+        (WifiMarauderScriptStageBeaconList*)malloc(sizeof(WifiMarauderScriptStageBeaconList));
+    if(beaconlist_stage == NULL) {
+        return NULL;
+    }
+    cJSON* ssids = cJSON_GetObjectItem(stage_beaconlist, "ssids");
+    if(ssids == NULL) {
+        return NULL;
+    }
+    // SSID count
+    int ssid_count = cJSON_GetArraySize(ssids);
+    if(ssid_count == 0) {
+        return NULL;
+    }
+    beaconlist_stage->ssid_count = ssid_count;
+    // SSIDs
+    beaconlist_stage->ssids = (char**)malloc(sizeof(char*) * ssid_count);
+    if(beaconlist_stage->ssids == NULL) {
+        return NULL;
+    }
+    for(int i = 0; i < ssid_count; i++) {
+        cJSON* ssid = cJSON_GetArrayItem(ssids, i);
+        if(ssid == NULL) {
+            continue;
+        }
+        char* ssid_string = cJSON_GetStringValue(ssid);
+        if(ssid_string == NULL) {
+            continue;
+        }
+        beaconlist_stage->ssids[i] = (char*)malloc(sizeof(char) * (strlen(ssid_string) + 1));
+        strcpy(beaconlist_stage->ssids[i], ssid_string);
+    }
+    // Timeout
+    cJSON* timeout = cJSON_GetObjectItem(stage_beaconlist, "timeout");
+    beaconlist_stage->timeout = timeout != NULL ? (int)cJSON_GetNumberValue(timeout) :
+                                                  WIFI_MARAUDER_DEFAULT_TIMEOUT_BEACON;
+    // Random SSIDs
+    cJSON* random_ssids = cJSON_GetObjectItem(stage_beaconlist, "generate");
+    beaconlist_stage->random_ssids =
+        random_ssids != NULL ? (int)cJSON_GetNumberValue(random_ssids) : 0;
+
+    return beaconlist_stage;
+}
+
+WifiMarauderScriptStageBeaconAp* _wifi_marauder_script_get_stage_beacon_ap(cJSON* stages) {
+    cJSON* beaconap_stage_json = cJSON_GetObjectItem(stages, "beaconAp");
+    if(beaconap_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* timeout_json = cJSON_GetObjectItem(beaconap_stage_json, "timeout");
+    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) :
+                                         WIFI_MARAUDER_DEFAULT_TIMEOUT_BEACON;
+
+    WifiMarauderScriptStageBeaconAp* beacon_ap_stage =
+        (WifiMarauderScriptStageBeaconAp*)malloc(sizeof(WifiMarauderScriptStageBeaconAp));
+    beacon_ap_stage->timeout = timeout;
+
+    return beacon_ap_stage;
+}
+
+WifiMarauderScriptStageExec* _wifi_marauder_script_get_stage_exec(cJSON* stages) {
+    cJSON* exec_stage_json = cJSON_GetObjectItem(stages, "exec");
+    if(exec_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* command_json = cJSON_GetObjectItemCaseSensitive(exec_stage_json, "command");
+    char* command_str = cJSON_IsString(command_json) ? strdup(command_json->valuestring) : NULL;
+
+    WifiMarauderScriptStageExec* exec_stage =
+        (WifiMarauderScriptStageExec*)malloc(sizeof(WifiMarauderScriptStageExec));
+    exec_stage->command = command_str;
+
+    return exec_stage;
+}
+
+WifiMarauderScriptStageDelay* _wifi_marauder_script_get_stage_delay(cJSON* stages) {
+    cJSON* delay_stage_json = cJSON_GetObjectItem(stages, "delay");
+    if(delay_stage_json == NULL) {
+        return NULL;
+    }
+
+    cJSON* timeout_json = cJSON_GetObjectItem(delay_stage_json, "timeout");
+    int timeout = timeout_json != NULL ? (int)cJSON_GetNumberValue(timeout_json) : 0;
+
+    WifiMarauderScriptStageDelay* delay_stage =
+        (WifiMarauderScriptStageDelay*)malloc(sizeof(WifiMarauderScriptStageDelay));
+    delay_stage->timeout = timeout;
+
+    return delay_stage;
+}
+
+WifiMarauderScriptStage*
+    _wifi_marauder_script_create_stage(WifiMarauderScriptStageType type, void* stage_data) {
+    WifiMarauderScriptStage* stage =
+        (WifiMarauderScriptStage*)malloc(sizeof(WifiMarauderScriptStage));
+    stage->type = type;
+    stage->stage = stage_data;
+    stage->next_stage = NULL;
+    return stage;
+}
+
+void wifi_marauder_script_add_stage(
+    WifiMarauderScript* script,
+    WifiMarauderScriptStageType stage_type,
+    void* stage_data) {
+    if(script == NULL || stage_data == NULL) {
+        return;
+    }
+    WifiMarauderScriptStage* stage = _wifi_marauder_script_create_stage(stage_type, stage_data);
+    if(script->last_stage != NULL) {
+        script->last_stage->next_stage = stage;
+    } else {
+        script->first_stage = stage;
+    }
+    script->last_stage = stage;
+}
+
+void _wifi_marauder_script_load_stages(WifiMarauderScript* script, cJSON* stages) {
+    // Scan stage
+    wifi_marauder_script_add_stage(
+        script, WifiMarauderScriptStageTypeScan, _wifi_marauder_script_get_stage_scan(stages));
+    // Select stage
+    wifi_marauder_script_add_stage(
+        script, WifiMarauderScriptStageTypeSelect, _wifi_marauder_script_get_stage_select(stages));
+    // Deauth stage
+    wifi_marauder_script_add_stage(
+        script, WifiMarauderScriptStageTypeDeauth, _wifi_marauder_script_get_stage_deauth(stages));
+    // Probe stage
+    wifi_marauder_script_add_stage(
+        script, WifiMarauderScriptStageTypeProbe, _wifi_marauder_script_get_stage_probe(stages));
+    // Sniff raw stage
+    wifi_marauder_script_add_stage(
+        script,
+        WifiMarauderScriptStageTypeSniffRaw,
+        _wifi_marauder_script_get_stage_sniff_raw(stages));
+    // Sniff beacon stage
+    wifi_marauder_script_add_stage(
+        script,
+        WifiMarauderScriptStageTypeSniffBeacon,
+        _wifi_marauder_script_get_stage_sniff_beacon(stages));
+    // Sniff deauth stage
+    wifi_marauder_script_add_stage(
+        script,
+        WifiMarauderScriptStageTypeSniffDeauth,
+        _wifi_marauder_script_get_stage_sniff_deauth(stages));
+    // Sniff esp stage
+    wifi_marauder_script_add_stage(
+        script,
+        WifiMarauderScriptStageTypeSniffEsp,
+        _wifi_marauder_script_get_stage_sniff_esp(stages));
+    // Sniff PMKID stage
+    wifi_marauder_script_add_stage(
+        script,
+        WifiMarauderScriptStageTypeSniffPmkid,
+        _wifi_marauder_script_get_stage_sniff_pmkid(stages));
+    // Sniff pwn stage
+    wifi_marauder_script_add_stage(
+        script,
+        WifiMarauderScriptStageTypeSniffPwn,
+        _wifi_marauder_script_get_stage_sniff_pwn(stages));
+    // Beacon List stage
+    wifi_marauder_script_add_stage(
+        script,
+        WifiMarauderScriptStageTypeBeaconList,
+        _wifi_marauder_script_get_stage_beacon_list(stages));
+    // Beacon Ap stage
+    wifi_marauder_script_add_stage(
+        script,
+        WifiMarauderScriptStageTypeBeaconAp,
+        _wifi_marauder_script_get_stage_beacon_ap(stages));
+    // Exec stage
+    wifi_marauder_script_add_stage(
+        script, WifiMarauderScriptStageTypeExec, _wifi_marauder_script_get_stage_exec(stages));
+    // Delay stage
+    wifi_marauder_script_add_stage(
+        script, WifiMarauderScriptStageTypeDelay, _wifi_marauder_script_get_stage_delay(stages));
+}
+
+WifiMarauderScript* wifi_marauder_script_parse_raw(const char* json_raw) {
+    WifiMarauderScript* script = wifi_marauder_script_alloc();
+    if(script == NULL) {
+        return NULL;
+    }
+    cJSON* json = cJSON_Parse(json_raw);
+    if(json == NULL) {
+        return NULL;
+    }
+    cJSON* meta = cJSON_GetObjectItem(json, "meta");
+    _wifi_marauder_script_load_meta(script, meta);
+
+    cJSON* stages = cJSON_GetObjectItem(json, "stages");
+    if(cJSON_IsArray(stages)) {
+        cJSON* stage_item = NULL;
+        cJSON_ArrayForEach(stage_item, stages) {
+            _wifi_marauder_script_load_stages(script, stage_item);
+        }
+    } else {
+        _wifi_marauder_script_load_stages(script, stages);
+    }
+
+    return script;
+}
+
+WifiMarauderScript* wifi_marauder_script_parse_json(Storage* storage, const char* file_path) {
+    WifiMarauderScript* script = NULL;
+    File* script_file = storage_file_alloc(storage);
+    FuriString* script_name = furi_string_alloc();
+    path_extract_filename_no_ext(file_path, script_name);
+
+    if(storage_file_open(script_file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+        uint32_t file_size = storage_file_size(script_file);
+        char* json_buffer = (char*)malloc(file_size + 1);
+        uint16_t bytes_read = storage_file_read(script_file, json_buffer, file_size);
+        json_buffer[bytes_read] = '\0';
+
+        script = wifi_marauder_script_parse_raw(json_buffer);
+    }
+    if(script == NULL) {
+        script = wifi_marauder_script_create(furi_string_get_cstr(script_name));
+    }
+    script->name = strdup(furi_string_get_cstr(script_name));
+
+    furi_string_free(script_name);
+    storage_file_close(script_file);
+    storage_file_free(script_file);
+    return script;
+}
+
+cJSON* _wifi_marauder_script_create_json_meta(WifiMarauderScript* script) {
+    cJSON* meta_json = cJSON_CreateObject();
+    if(script->description != NULL) {
+        cJSON_AddStringToObject(meta_json, "description", script->description);
+    } else {
+        cJSON_AddStringToObject(meta_json, "description", "My Script");
+    }
+    if(script->enable_led != WifiMarauderScriptBooleanUndefined) {
+        cJSON_AddBoolToObject(
+            meta_json, "enableLed", (script->enable_led == WifiMarauderScriptBooleanTrue));
+    }
+    if(script->save_pcap != WifiMarauderScriptBooleanUndefined) {
+        cJSON_AddBoolToObject(
+            meta_json, "savePcap", (script->save_pcap == WifiMarauderScriptBooleanTrue));
+    }
+    cJSON_AddNumberToObject(meta_json, "repeat", script->repeat);
+    return meta_json;
+}
+
+cJSON* _wifi_marauder_script_create_json_scan(WifiMarauderScriptStageScan* scan_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "scan", cJSON_CreateObject());
+    cJSON* scan_json = cJSON_GetObjectItem(stage_json, "scan");
+    // Scan type
+    cJSON_AddStringToObject(
+        scan_json, "type", scan_stage->type == WifiMarauderScriptScanTypeAp ? "ap" : "station");
+    // Channel
+    if(scan_stage->channel > 0) {
+        cJSON_AddNumberToObject(scan_json, "channel", scan_stage->channel);
+    }
+    // Timeout
+    if(scan_stage->timeout > 0) {
+        cJSON_AddNumberToObject(scan_json, "timeout", scan_stage->timeout);
+    }
+    return stage_json;
+}
+
+cJSON* _wifi_marauder_script_create_json_select(WifiMarauderScriptStageSelect* select_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "select", cJSON_CreateObject());
+    cJSON* select_json = cJSON_GetObjectItem(stage_json, "select");
+    // Select type
+    cJSON_AddStringToObject(
+        select_json,
+        "type",
+        select_stage->type == WifiMarauderScriptSelectTypeAp      ? "ap" :
+        select_stage->type == WifiMarauderScriptSelectTypeStation ? "station" :
+                                                                    "ssid");
+    if(select_stage->filter != NULL) {
+        cJSON_AddStringToObject(select_json, "filter", select_stage->filter);
+    }
+    // Indexes
+    if(select_stage->indexes != NULL && select_stage->index_count > 0) {
+        cJSON* indexes_json = cJSON_CreateArray();
+        for(int i = 0; i < select_stage->index_count; i++) {
+            cJSON_AddItemToArray(indexes_json, cJSON_CreateNumber(select_stage->indexes[i]));
+        }
+        cJSON_AddItemToObject(select_json, "indexes", indexes_json);
+    }
+    return stage_json;
+}
+
+cJSON* _wifi_marauder_script_create_json_deauth(WifiMarauderScriptStageDeauth* deauth_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "deauth", cJSON_CreateObject());
+    cJSON* deauth_json = cJSON_GetObjectItem(stage_json, "deauth");
+    // Timeout
+    if(deauth_stage->timeout > 0) {
+        cJSON_AddNumberToObject(deauth_json, "timeout", deauth_stage->timeout);
+    }
+    return stage_json;
+}
+
+cJSON* _wifi_marauder_script_create_json_probe(WifiMarauderScriptStageProbe* probe_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "probe", cJSON_CreateObject());
+    cJSON* probe_json = cJSON_GetObjectItem(stage_json, "probe");
+    // Timeout
+    if(probe_stage->timeout > 0) {
+        cJSON_AddNumberToObject(probe_json, "timeout", probe_stage->timeout);
+    }
+    return stage_json;
+}
+
+cJSON*
+    _wifi_marauder_script_create_json_sniffraw(WifiMarauderScriptStageSniffRaw* sniffraw_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "sniffRaw", cJSON_CreateObject());
+    cJSON* sniffraw_json = cJSON_GetObjectItem(stage_json, "sniffRaw");
+    // Timeout
+    if(sniffraw_stage->timeout > 0) {
+        cJSON_AddNumberToObject(sniffraw_json, "timeout", sniffraw_stage->timeout);
+    }
+    return stage_json;
+}
+
+cJSON* _wifi_marauder_script_create_json_sniffbeacon(
+    WifiMarauderScriptStageSniffBeacon* sniffbeacon_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "sniffBeacon", cJSON_CreateObject());
+    cJSON* sniffbeacon_json = cJSON_GetObjectItem(stage_json, "sniffBeacon");
+    // Timeout
+    if(sniffbeacon_stage->timeout > 0) {
+        cJSON_AddNumberToObject(sniffbeacon_json, "timeout", sniffbeacon_stage->timeout);
+    }
+    return stage_json;
+}
+
+cJSON* _wifi_marauder_script_create_json_sniffdeauth(
+    WifiMarauderScriptStageSniffDeauth* sniffdeauth_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "sniffDeauth", cJSON_CreateObject());
+    cJSON* sniffdeauth_json = cJSON_GetObjectItem(stage_json, "sniffDeauth");
+    // Timeout
+    if(sniffdeauth_stage->timeout > 0) {
+        cJSON_AddNumberToObject(sniffdeauth_json, "timeout", sniffdeauth_stage->timeout);
+    }
+    return stage_json;
+}
+
+cJSON*
+    _wifi_marauder_script_create_json_sniffesp(WifiMarauderScriptStageSniffEsp* sniffesp_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "sniffEsp", cJSON_CreateObject());
+    cJSON* sniffesp_json = cJSON_GetObjectItem(stage_json, "sniffEsp");
+    // Timeout
+    if(sniffesp_stage->timeout > 0) {
+        cJSON_AddNumberToObject(sniffesp_json, "timeout", sniffesp_stage->timeout);
+    }
+    return stage_json;
+}
+
+cJSON* _wifi_marauder_script_create_json_sniffpmkid(
+    WifiMarauderScriptStageSniffPmkid* sniffpmkid_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "sniffPmkid", cJSON_CreateObject());
+    cJSON* sniffpmkid_json = cJSON_GetObjectItem(stage_json, "sniffPmkid");
+    // Force deauth
+    cJSON_AddBoolToObject(sniffpmkid_json, "forceDeauth", sniffpmkid_stage->force_deauth);
+    // Channel
+    if(sniffpmkid_stage->channel > 0) {
+        cJSON_AddNumberToObject(sniffpmkid_json, "channel", sniffpmkid_stage->channel);
+    }
+    // Timeout
+    if(sniffpmkid_stage->timeout > 0) {
+        cJSON_AddNumberToObject(sniffpmkid_json, "timeout", sniffpmkid_stage->timeout);
+    }
+    // Hop channels
+    cJSON_AddBoolToObject(sniffpmkid_json, "hopChannels", sniffpmkid_stage->hop_channels);
+
+    return stage_json;
+}
+
+cJSON*
+    _wifi_marauder_script_create_json_sniffpwn(WifiMarauderScriptStageSniffPwn* sniffpwn_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "sniffPwn", cJSON_CreateObject());
+    cJSON* sniffpwn_json = cJSON_GetObjectItem(stage_json, "sniffPwn");
+    // Timeout
+    if(sniffpwn_stage->timeout > 0) {
+        cJSON_AddNumberToObject(sniffpwn_json, "timeout", sniffpwn_stage->timeout);
+    }
+    return stage_json;
+}
+
+cJSON* _wifi_marauder_script_create_json_beaconlist(
+    WifiMarauderScriptStageBeaconList* beaconlist_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "beaconList", cJSON_CreateObject());
+    cJSON* beaconlist_json = cJSON_GetObjectItem(stage_json, "beaconList");
+    // SSIDs
+    if(beaconlist_stage->ssids != NULL) {
+        cJSON* ssids_json = cJSON_CreateStringArray(
+            (const char**)beaconlist_stage->ssids, beaconlist_stage->ssid_count);
+        cJSON_AddItemToObject(beaconlist_json, "ssids", ssids_json);
+    }
+    // Random SSIDs
+    if(beaconlist_stage->random_ssids > 0) {
+        cJSON_AddNumberToObject(beaconlist_json, "generate", beaconlist_stage->random_ssids);
+    }
+    // Timeout
+    if(beaconlist_stage->timeout > 0) {
+        cJSON_AddNumberToObject(beaconlist_json, "timeout", beaconlist_stage->timeout);
+    }
+    return stage_json;
+}
+
+cJSON*
+    _wifi_marauder_script_create_json_beaconap(WifiMarauderScriptStageBeaconAp* beaconap_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "beaconAp", cJSON_CreateObject());
+    cJSON* beaconap_json = cJSON_GetObjectItem(stage_json, "beaconAp");
+    // Timeout
+    if(beaconap_stage->timeout > 0) {
+        cJSON_AddNumberToObject(beaconap_json, "timeout", beaconap_stage->timeout);
+    }
+    return stage_json;
+}
+
+cJSON* _wifi_marauder_script_create_json_exec(WifiMarauderScriptStageExec* exec_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "exec", cJSON_CreateObject());
+    cJSON* exec_json = cJSON_GetObjectItem(stage_json, "exec");
+    // Command
+    cJSON_AddStringToObject(
+        exec_json, "command", exec_stage->command != NULL ? exec_stage->command : "");
+    return stage_json;
+}
+
+cJSON* _wifi_marauder_script_create_json_delay(WifiMarauderScriptStageDelay* delay_stage) {
+    cJSON* stage_json = cJSON_CreateObject();
+    cJSON_AddItemToObject(stage_json, "delay", cJSON_CreateObject());
+    cJSON* delay_json = cJSON_GetObjectItem(stage_json, "delay");
+    // Timeout
+    if(delay_stage->timeout > 0) {
+        cJSON_AddNumberToObject(delay_json, "timeout", delay_stage->timeout);
+    }
+    return stage_json;
+}
+
+void wifi_marauder_script_save_json(
+    Storage* storage,
+    const char* file_path,
+    WifiMarauderScript* script) {
+    File* script_file = storage_file_alloc(storage);
+
+    if(storage_file_open(script_file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+        cJSON* root_json = cJSON_CreateObject();
+
+        // Meta info
+        cJSON* meta_json = _wifi_marauder_script_create_json_meta(script);
+        cJSON_AddItemToObject(root_json, "meta", meta_json);
+
+        // Create array for stages
+        cJSON* stages_array = cJSON_CreateArray();
+        cJSON_AddItemToObject(root_json, "stages", stages_array);
+
+        // Iterate over each stage and create the corresponding JSON object
+        WifiMarauderScriptStage* stage = script->first_stage;
+        while(stage != NULL) {
+            cJSON* stage_json = NULL;
+
+            switch(stage->type) {
+            case WifiMarauderScriptStageTypeScan: {
+                WifiMarauderScriptStageScan* scan_stage =
+                    (WifiMarauderScriptStageScan*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_scan(scan_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeSelect: {
+                WifiMarauderScriptStageSelect* select_stage =
+                    (WifiMarauderScriptStageSelect*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_select(select_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeDeauth: {
+                WifiMarauderScriptStageDeauth* deauth_stage =
+                    (WifiMarauderScriptStageDeauth*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_deauth(deauth_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeProbe: {
+                WifiMarauderScriptStageProbe* probe_stage =
+                    (WifiMarauderScriptStageProbe*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_probe(probe_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeSniffRaw: {
+                WifiMarauderScriptStageSniffRaw* sniffraw_stage =
+                    (WifiMarauderScriptStageSniffRaw*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_sniffraw(sniffraw_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeSniffBeacon: {
+                WifiMarauderScriptStageSniffBeacon* sniffbeacon_stage =
+                    (WifiMarauderScriptStageSniffBeacon*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_sniffbeacon(sniffbeacon_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeSniffDeauth: {
+                WifiMarauderScriptStageSniffDeauth* sniffdeauth_stage =
+                    (WifiMarauderScriptStageSniffDeauth*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_sniffdeauth(sniffdeauth_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeSniffEsp: {
+                WifiMarauderScriptStageSniffEsp* sniffesp_stage =
+                    (WifiMarauderScriptStageSniffEsp*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_sniffesp(sniffesp_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeSniffPmkid: {
+                WifiMarauderScriptStageSniffPmkid* sniffpmkid_stage =
+                    (WifiMarauderScriptStageSniffPmkid*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_sniffpmkid(sniffpmkid_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeSniffPwn: {
+                WifiMarauderScriptStageSniffPwn* sniffpwn_stage =
+                    (WifiMarauderScriptStageSniffPwn*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_sniffpwn(sniffpwn_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeBeaconList: {
+                WifiMarauderScriptStageBeaconList* beaconlist_stage =
+                    (WifiMarauderScriptStageBeaconList*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_beaconlist(beaconlist_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeBeaconAp: {
+                WifiMarauderScriptStageBeaconAp* beaconap_stage =
+                    (WifiMarauderScriptStageBeaconAp*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_beaconap(beaconap_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeExec: {
+                WifiMarauderScriptStageExec* exec_stage =
+                    (WifiMarauderScriptStageExec*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_exec(exec_stage);
+                break;
+            }
+            case WifiMarauderScriptStageTypeDelay: {
+                WifiMarauderScriptStageDelay* delay_stage =
+                    (WifiMarauderScriptStageDelay*)stage->stage;
+                stage_json = _wifi_marauder_script_create_json_delay(delay_stage);
+                break;
+            }
+            }
+
+            // Add the stage JSON object to the "stages" array
+            if(stage_json != NULL) {
+                cJSON_AddItemToArray(stages_array, stage_json);
+            }
+
+            stage = stage->next_stage;
+        }
+
+        // Write JSON to file
+        char* json_str = cJSON_Print(root_json);
+        storage_file_write(script_file, json_str, strlen(json_str));
+
+        //free(json_str);
+        storage_file_close(script_file);
+    }
+    storage_file_free(script_file);
+}
+
+bool wifi_marauder_script_has_stage(
+    WifiMarauderScript* script,
+    WifiMarauderScriptStageType stage_type) {
+    if(script == NULL) {
+        return false;
+    }
+    WifiMarauderScriptStage* current_stage = script->first_stage;
+    while(current_stage != NULL) {
+        if(current_stage->type == stage_type) {
+            return true;
+        }
+        current_stage = current_stage->next_stage;
+    }
+    return false;
+}
+
+void wifi_marauder_script_free(WifiMarauderScript* script) {
+    if(script == NULL) {
+        return;
+    }
+    WifiMarauderScriptStage* current_stage = script->first_stage;
+    while(current_stage != NULL) {
+        WifiMarauderScriptStage* next_stage = current_stage->next_stage;
+        switch(current_stage->type) {
+        case WifiMarauderScriptStageTypeScan:
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeSelect:
+            if(((WifiMarauderScriptStageSelect*)current_stage->stage)->filter != NULL) {
+                free(((WifiMarauderScriptStageSelect*)current_stage->stage)->filter);
+            }
+            if(((WifiMarauderScriptStageSelect*)current_stage->stage)->indexes != NULL) {
+                free(((WifiMarauderScriptStageSelect*)current_stage->stage)->indexes);
+            }
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeDeauth:
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeProbe:
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeSniffRaw:
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeSniffBeacon:
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeSniffDeauth:
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeSniffEsp:
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeSniffPmkid:
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeSniffPwn:
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeBeaconList:
+            for(int i = 0;
+                i < ((WifiMarauderScriptStageBeaconList*)current_stage->stage)->ssid_count;
+                i++) {
+                free(((WifiMarauderScriptStageBeaconList*)current_stage->stage)->ssids[i]);
+            }
+            free(((WifiMarauderScriptStageBeaconList*)current_stage->stage)->ssids);
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeBeaconAp:
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeExec:
+            if(((WifiMarauderScriptStageExec*)current_stage->stage)->command != NULL) {
+                free(((WifiMarauderScriptStageExec*)current_stage->stage)->command);
+            }
+            free(current_stage->stage);
+            break;
+        case WifiMarauderScriptStageTypeDelay:
+            free(current_stage->stage);
+            break;
+        }
+        free(current_stage);
+        current_stage = next_stage;
+    }
+    free(script->name);
+    free(script->description);
+    free(script);
 }

+ 323 - 325
script/wifi_marauder_script_executor.c

@@ -1,326 +1,324 @@
-#include "../wifi_marauder_app_i.h"
-#include "wifi_marauder_script_executor.h"
-
-void _wifi_marauder_script_delay(WifiMarauderScriptWorker* worker, uint32_t delay_secs) {
-    for(uint32_t i = 0; i < delay_secs && worker->is_running; i++) furi_delay_ms(1000);
-}
-
-void _send_stop() {
-    const char stop_command[] = "stopscan\n";
-    wifi_marauder_uart_tx((uint8_t*)(stop_command), strlen(stop_command));
-}
-
-void _send_line_break() {
-    wifi_marauder_uart_tx((uint8_t*)("\n"), 1);
-}
-
-
-void _send_channel_select(int channel) {
-    char command[30];
-    _send_line_break();
-    snprintf(command, sizeof(command), "channel -s %d\n", channel);
-    wifi_marauder_uart_tx((uint8_t*)(command), strlen(command));
-}
-
-void _wifi_marauder_script_execute_scan(
-    WifiMarauderScriptStageScan* stage,
-    WifiMarauderScriptWorker* worker) {
-    char command[15];
-    // Set channel
-    if(stage->channel > 0) {
-        _send_channel_select(stage->channel);
-    }
-    // Start scan
-    if(stage->type == WifiMarauderScriptScanTypeAp) {
-        snprintf(command, sizeof(command), "scanap\n");
-    } else {
-        snprintf(command, sizeof(command), "scansta\n");
-    }
-    wifi_marauder_uart_tx((uint8_t*)(command), strlen(command));
-    _wifi_marauder_script_delay(worker, stage->timeout);
-    _send_stop();
-}
-
-void _wifi_marauder_script_execute_select(WifiMarauderScriptStageSelect* stage) {
-    const char* select_type = NULL;
-    switch(stage->type) {
-    case WifiMarauderScriptSelectTypeAp:
-        select_type = "-a";
-        break;
-    case WifiMarauderScriptSelectTypeStation:
-        select_type = "-c";
-        break;
-    case WifiMarauderScriptSelectTypeSsid:
-        select_type = "-s";
-        break;
-    default:
-        return; // invalid stage
-    }
-
-    char command[256];
-    size_t command_length = 0;
-
-    if(stage->indexes != NULL && stage->index_count > 0) {
-        command_length = snprintf(command, sizeof(command), "select %s ", select_type);
-
-        for(int i = 0; i < stage->index_count; i++) {
-            int index = stage->indexes[i];
-            command_length += snprintf(
-                command + command_length, sizeof(command) - command_length, "%d, ", index);
-        }
-
-        // Remove the trailing comma and space
-        command_length -= 2;
-        command[command_length] = '\n';
-        command_length++;
-    } else if(stage->filter == NULL || strcmp(stage->filter, "all") == 0) {
-        command_length = snprintf(command, sizeof(command), "select %s all\n", select_type);
-    } else {
-        command_length = snprintf(
-            command, sizeof(command), "select %s -f \"%s\"\n", select_type, stage->filter);
-    }
-
-    wifi_marauder_uart_tx((uint8_t*)command, command_length);
-}
-
-void _wifi_marauder_script_execute_deauth(
-    WifiMarauderScriptStageDeauth* stage,
-    WifiMarauderScriptWorker* worker) {
-    const char attack_command[] = "attack -t deauth\n";
-    wifi_marauder_uart_tx((uint8_t*)(attack_command), strlen(attack_command));
-    _wifi_marauder_script_delay(worker, stage->timeout);
-    _send_stop();
-}
-
-void _wifi_marauder_script_execute_probe(
-    WifiMarauderScriptStageProbe* stage,
-    WifiMarauderScriptWorker* worker) {
-    const char attack_command[] = "attack -t probe\n";
-    wifi_marauder_uart_tx((uint8_t*)(attack_command), strlen(attack_command));
-    _wifi_marauder_script_delay(worker, stage->timeout);
-    _send_stop();
-}
-
-void _wifi_marauder_script_execute_sniff_raw(
-    WifiMarauderScriptStageSniffRaw* stage,
-    WifiMarauderScriptWorker* worker) {
-    const char sniff_command[] = "sniffraw\n";
-    wifi_marauder_uart_tx((uint8_t*)sniff_command, strlen(sniff_command));
-    _wifi_marauder_script_delay(worker, stage->timeout);
-    _send_stop();
-}
-
-void _wifi_marauder_script_execute_sniff_beacon(
-    WifiMarauderScriptStageSniffBeacon* stage,
-    WifiMarauderScriptWorker* worker) {
-    const char sniff_command[] = "sniffbeacon\n";
-    wifi_marauder_uart_tx((uint8_t*)sniff_command, strlen(sniff_command));
-    _wifi_marauder_script_delay(worker, stage->timeout);
-    _send_stop();
-}
-
-void _wifi_marauder_script_execute_sniff_deauth(
-    WifiMarauderScriptStageSniffDeauth* stage,
-    WifiMarauderScriptWorker* worker) {
-    const char sniff_command[] = "sniffdeauth\n";
-    wifi_marauder_uart_tx((uint8_t*)sniff_command, strlen(sniff_command));
-    _wifi_marauder_script_delay(worker, stage->timeout);
-    _send_stop();
-}
-
-void _wifi_marauder_script_execute_sniff_esp(
-    WifiMarauderScriptStageSniffEsp* stage,
-    WifiMarauderScriptWorker* worker) {
-    const char sniff_command[] = "sniffesp\n";
-    wifi_marauder_uart_tx((uint8_t*)sniff_command, strlen(sniff_command));
-    _wifi_marauder_script_delay(worker, stage->timeout);
-    _send_stop();
-}
-
-void _wifi_marauder_script_execute_sniff_pmkid(
-        WifiMarauderScriptStageSniffPmkid* stage,
-        WifiMarauderScriptWorker* worker) {
-
-    // If channel hopping is enabled, loop through channels 1-11
-    if(stage->hop_channels) {
-        for(int i = 1; i <= 11; i++) {
-            char attack_command[50] = "sniffpmkid";
-            int len = strlen(attack_command);
-
-            len += snprintf(attack_command + len, sizeof(attack_command) - len, " -c %d", i);
-            if(stage->force_deauth) {
-                len += snprintf(attack_command + len, sizeof(attack_command) - len, " -d");
-            }
-
-            len += snprintf(attack_command + len, sizeof(attack_command) - len, "\n");
-            wifi_marauder_uart_tx((uint8_t*)attack_command, len);
-            _wifi_marauder_script_delay(worker, stage->timeout);
-            _send_stop();
-        }
-    } else {
-        char attack_command[50] = "sniffpmkid";
-        int len = strlen(attack_command);
-
-        if(stage->channel > 0) {
-            len +=
-                    snprintf(attack_command + len, sizeof(attack_command) - len, " -c %d", stage->channel);
-        }
-
-        if(stage->force_deauth) {
-            len += snprintf(attack_command + len, sizeof(attack_command) - len, " -d");
-        }
-        len += snprintf(attack_command + len, sizeof(attack_command) - len, "\n");
-        wifi_marauder_uart_tx((uint8_t*)attack_command, len);
-        _wifi_marauder_script_delay(worker, stage->timeout);
-        _send_stop();
-    }
-}
-
-void _wifi_marauder_script_execute_sniff_pwn(
-    WifiMarauderScriptStageSniffPwn* stage,
-    WifiMarauderScriptWorker* worker) {
-    const char sniff_command[] = "sniffpwn\n";
-    wifi_marauder_uart_tx((uint8_t*)sniff_command, strlen(sniff_command));
-    _wifi_marauder_script_delay(worker, stage->timeout);
-    _send_stop();
-}
-
-void _wifi_marauder_script_execute_beacon_list(
-    WifiMarauderScriptStageBeaconList* stage,
-    WifiMarauderScriptWorker* worker) {
-    const char clearlist_command[] = "clearlist -s\n";
-    wifi_marauder_uart_tx((uint8_t*)(clearlist_command), strlen(clearlist_command));
-
-    char command[100];
-    char* ssid;
-
-    for(int i = 0; i < stage->ssid_count; i++) {
-        ssid = stage->ssids[i];
-        snprintf(command, sizeof(command), "ssid -a -n \"%s\"", ssid);
-        wifi_marauder_uart_tx((uint8_t*)(command), strlen(command));
-        _send_line_break();
-    }
-    if(stage->random_ssids > 0) {
-        char add_random_command[50];
-        snprintf(
-            add_random_command,
-            sizeof(add_random_command),
-            "ssid -a -r -g %d\n",
-            stage->random_ssids);
-        wifi_marauder_uart_tx((uint8_t*)add_random_command, strlen(add_random_command));
-    }
-    const char attack_command[] = "attack -t beacon -l\n";
-    wifi_marauder_uart_tx((uint8_t*)(attack_command), strlen(attack_command));
-    _wifi_marauder_script_delay(worker, stage->timeout);
-    _send_stop();
-}
-
-void _wifi_marauder_script_execute_beacon_ap(
-    WifiMarauderScriptStageBeaconAp* stage,
-    WifiMarauderScriptWorker* worker) {
-    const char command[] = "attack -t beacon -a\n";
-    wifi_marauder_uart_tx((uint8_t*)command, strlen(command));
-    _wifi_marauder_script_delay(worker, stage->timeout);
-    _send_stop();
-}
-
-void _wifi_marauder_script_execute_exec(WifiMarauderScriptStageExec* stage) {
-    if(stage->command != NULL) {
-        wifi_marauder_uart_tx((uint8_t*)stage->command, strlen(stage->command));
-        _send_line_break();
-    }
-}
-
-void _wifi_marauder_script_execute_delay(
-    WifiMarauderScriptStageDelay* stage,
-    WifiMarauderScriptWorker* worker) {
-    _wifi_marauder_script_delay(worker, stage->timeout);
-}
-
-void wifi_marauder_script_execute_start(void* context) {
-    furi_assert(context);
-    WifiMarauderScriptWorker* worker = context;
-    WifiMarauderScript* script = worker->script;
-    char command[100];
-
-    // Enables or disables the LED according to script settings
-    if(script->enable_led != WifiMarauderScriptBooleanUndefined) {
-        snprintf(
-            command,
-            sizeof(command),
-            "settings -s EnableLED %s",
-            script->enable_led ? "enable" : "disable");
-        wifi_marauder_uart_tx((uint8_t*)command, strlen(command));
-        _send_line_break();
-    }
-
-    // Enables or disables PCAP saving according to script settings
-    if(script->save_pcap != WifiMarauderScriptBooleanUndefined) {
-        snprintf(
-            command,
-            sizeof(command),
-            "settings -s SavePCAP %s",
-            script->save_pcap ? "enable" : "disable");
-        wifi_marauder_uart_tx((uint8_t*)command, strlen(command));
-        _send_line_break();
-    }
-}
-
-void wifi_marauder_script_execute_stage(WifiMarauderScriptStage* stage, void* context) {
-    furi_assert(context);
-    WifiMarauderScriptWorker* worker = context;
-    void* stage_data = stage->stage;
-
-    switch(stage->type) {
-    case WifiMarauderScriptStageTypeScan:
-        _wifi_marauder_script_execute_scan((WifiMarauderScriptStageScan*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeSelect:
-        _wifi_marauder_script_execute_select((WifiMarauderScriptStageSelect*)stage_data);
-        break;
-    case WifiMarauderScriptStageTypeDeauth:
-        _wifi_marauder_script_execute_deauth((WifiMarauderScriptStageDeauth*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeProbe:
-        _wifi_marauder_script_execute_probe((WifiMarauderScriptStageProbe*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeSniffRaw:
-        _wifi_marauder_script_execute_sniff_raw(
-            (WifiMarauderScriptStageSniffRaw*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeSniffBeacon:
-        _wifi_marauder_script_execute_sniff_beacon(
-            (WifiMarauderScriptStageSniffBeacon*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeSniffDeauth:
-        _wifi_marauder_script_execute_sniff_deauth(
-            (WifiMarauderScriptStageSniffDeauth*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeSniffEsp:
-        _wifi_marauder_script_execute_sniff_esp(
-            (WifiMarauderScriptStageSniffEsp*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeSniffPmkid:
-        _wifi_marauder_script_execute_sniff_pmkid(
-            (WifiMarauderScriptStageSniffPmkid*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeSniffPwn:
-        _wifi_marauder_script_execute_sniff_pwn(
-            (WifiMarauderScriptStageSniffPwn*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeBeaconList:
-        _wifi_marauder_script_execute_beacon_list(
-            (WifiMarauderScriptStageBeaconList*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeBeaconAp:
-        _wifi_marauder_script_execute_beacon_ap(
-            (WifiMarauderScriptStageBeaconAp*)stage_data, worker);
-        break;
-    case WifiMarauderScriptStageTypeExec:
-        _wifi_marauder_script_execute_exec((WifiMarauderScriptStageExec*)stage_data);
-        break;
-    case WifiMarauderScriptStageTypeDelay:
-        _wifi_marauder_script_execute_delay((WifiMarauderScriptStageDelay*)stage_data, worker);
-        break;
-    }
+#include "../wifi_marauder_app_i.h"
+#include "wifi_marauder_script_executor.h"
+
+void _wifi_marauder_script_delay(WifiMarauderScriptWorker* worker, uint32_t delay_secs) {
+    for(uint32_t i = 0; i < delay_secs && worker->is_running; i++) furi_delay_ms(1000);
+}
+
+void _send_stop() {
+    const char stop_command[] = "stopscan\n";
+    wifi_marauder_uart_tx((uint8_t*)(stop_command), strlen(stop_command));
+}
+
+void _send_line_break() {
+    wifi_marauder_uart_tx((uint8_t*)("\n"), 1);
+}
+
+void _send_channel_select(int channel) {
+    char command[30];
+    _send_line_break();
+    snprintf(command, sizeof(command), "channel -s %d\n", channel);
+    wifi_marauder_uart_tx((uint8_t*)(command), strlen(command));
+}
+
+void _wifi_marauder_script_execute_scan(
+    WifiMarauderScriptStageScan* stage,
+    WifiMarauderScriptWorker* worker) {
+    char command[15];
+    // Set channel
+    if(stage->channel > 0) {
+        _send_channel_select(stage->channel);
+    }
+    // Start scan
+    if(stage->type == WifiMarauderScriptScanTypeAp) {
+        snprintf(command, sizeof(command), "scanap\n");
+    } else {
+        snprintf(command, sizeof(command), "scansta\n");
+    }
+    wifi_marauder_uart_tx((uint8_t*)(command), strlen(command));
+    _wifi_marauder_script_delay(worker, stage->timeout);
+    _send_stop();
+}
+
+void _wifi_marauder_script_execute_select(WifiMarauderScriptStageSelect* stage) {
+    const char* select_type = NULL;
+    switch(stage->type) {
+    case WifiMarauderScriptSelectTypeAp:
+        select_type = "-a";
+        break;
+    case WifiMarauderScriptSelectTypeStation:
+        select_type = "-c";
+        break;
+    case WifiMarauderScriptSelectTypeSsid:
+        select_type = "-s";
+        break;
+    default:
+        return; // invalid stage
+    }
+
+    char command[256];
+    size_t command_length = 0;
+
+    if(stage->indexes != NULL && stage->index_count > 0) {
+        command_length = snprintf(command, sizeof(command), "select %s ", select_type);
+
+        for(int i = 0; i < stage->index_count; i++) {
+            int index = stage->indexes[i];
+            command_length += snprintf(
+                command + command_length, sizeof(command) - command_length, "%d, ", index);
+        }
+
+        // Remove the trailing comma and space
+        command_length -= 2;
+        command[command_length] = '\n';
+        command_length++;
+    } else if(stage->filter == NULL || strcmp(stage->filter, "all") == 0) {
+        command_length = snprintf(command, sizeof(command), "select %s all\n", select_type);
+    } else {
+        command_length = snprintf(
+            command, sizeof(command), "select %s -f \"%s\"\n", select_type, stage->filter);
+    }
+
+    wifi_marauder_uart_tx((uint8_t*)command, command_length);
+}
+
+void _wifi_marauder_script_execute_deauth(
+    WifiMarauderScriptStageDeauth* stage,
+    WifiMarauderScriptWorker* worker) {
+    const char attack_command[] = "attack -t deauth\n";
+    wifi_marauder_uart_tx((uint8_t*)(attack_command), strlen(attack_command));
+    _wifi_marauder_script_delay(worker, stage->timeout);
+    _send_stop();
+}
+
+void _wifi_marauder_script_execute_probe(
+    WifiMarauderScriptStageProbe* stage,
+    WifiMarauderScriptWorker* worker) {
+    const char attack_command[] = "attack -t probe\n";
+    wifi_marauder_uart_tx((uint8_t*)(attack_command), strlen(attack_command));
+    _wifi_marauder_script_delay(worker, stage->timeout);
+    _send_stop();
+}
+
+void _wifi_marauder_script_execute_sniff_raw(
+    WifiMarauderScriptStageSniffRaw* stage,
+    WifiMarauderScriptWorker* worker) {
+    const char sniff_command[] = "sniffraw\n";
+    wifi_marauder_uart_tx((uint8_t*)sniff_command, strlen(sniff_command));
+    _wifi_marauder_script_delay(worker, stage->timeout);
+    _send_stop();
+}
+
+void _wifi_marauder_script_execute_sniff_beacon(
+    WifiMarauderScriptStageSniffBeacon* stage,
+    WifiMarauderScriptWorker* worker) {
+    const char sniff_command[] = "sniffbeacon\n";
+    wifi_marauder_uart_tx((uint8_t*)sniff_command, strlen(sniff_command));
+    _wifi_marauder_script_delay(worker, stage->timeout);
+    _send_stop();
+}
+
+void _wifi_marauder_script_execute_sniff_deauth(
+    WifiMarauderScriptStageSniffDeauth* stage,
+    WifiMarauderScriptWorker* worker) {
+    const char sniff_command[] = "sniffdeauth\n";
+    wifi_marauder_uart_tx((uint8_t*)sniff_command, strlen(sniff_command));
+    _wifi_marauder_script_delay(worker, stage->timeout);
+    _send_stop();
+}
+
+void _wifi_marauder_script_execute_sniff_esp(
+    WifiMarauderScriptStageSniffEsp* stage,
+    WifiMarauderScriptWorker* worker) {
+    const char sniff_command[] = "sniffesp\n";
+    wifi_marauder_uart_tx((uint8_t*)sniff_command, strlen(sniff_command));
+    _wifi_marauder_script_delay(worker, stage->timeout);
+    _send_stop();
+}
+
+void _wifi_marauder_script_execute_sniff_pmkid(
+    WifiMarauderScriptStageSniffPmkid* stage,
+    WifiMarauderScriptWorker* worker) {
+    // If channel hopping is enabled, loop through channels 1-11
+    if(stage->hop_channels) {
+        for(int i = 1; i <= 11; i++) {
+            char attack_command[50] = "sniffpmkid";
+            int len = strlen(attack_command);
+
+            len += snprintf(attack_command + len, sizeof(attack_command) - len, " -c %d", i);
+            if(stage->force_deauth) {
+                len += snprintf(attack_command + len, sizeof(attack_command) - len, " -d");
+            }
+
+            len += snprintf(attack_command + len, sizeof(attack_command) - len, "\n");
+            wifi_marauder_uart_tx((uint8_t*)attack_command, len);
+            _wifi_marauder_script_delay(worker, stage->timeout);
+            _send_stop();
+        }
+    } else {
+        char attack_command[50] = "sniffpmkid";
+        int len = strlen(attack_command);
+
+        if(stage->channel > 0) {
+            len += snprintf(
+                attack_command + len, sizeof(attack_command) - len, " -c %d", stage->channel);
+        }
+
+        if(stage->force_deauth) {
+            len += snprintf(attack_command + len, sizeof(attack_command) - len, " -d");
+        }
+        len += snprintf(attack_command + len, sizeof(attack_command) - len, "\n");
+        wifi_marauder_uart_tx((uint8_t*)attack_command, len);
+        _wifi_marauder_script_delay(worker, stage->timeout);
+        _send_stop();
+    }
+}
+
+void _wifi_marauder_script_execute_sniff_pwn(
+    WifiMarauderScriptStageSniffPwn* stage,
+    WifiMarauderScriptWorker* worker) {
+    const char sniff_command[] = "sniffpwn\n";
+    wifi_marauder_uart_tx((uint8_t*)sniff_command, strlen(sniff_command));
+    _wifi_marauder_script_delay(worker, stage->timeout);
+    _send_stop();
+}
+
+void _wifi_marauder_script_execute_beacon_list(
+    WifiMarauderScriptStageBeaconList* stage,
+    WifiMarauderScriptWorker* worker) {
+    const char clearlist_command[] = "clearlist -s\n";
+    wifi_marauder_uart_tx((uint8_t*)(clearlist_command), strlen(clearlist_command));
+
+    char command[100];
+    char* ssid;
+
+    for(int i = 0; i < stage->ssid_count; i++) {
+        ssid = stage->ssids[i];
+        snprintf(command, sizeof(command), "ssid -a -n \"%s\"", ssid);
+        wifi_marauder_uart_tx((uint8_t*)(command), strlen(command));
+        _send_line_break();
+    }
+    if(stage->random_ssids > 0) {
+        char add_random_command[50];
+        snprintf(
+            add_random_command,
+            sizeof(add_random_command),
+            "ssid -a -r -g %d\n",
+            stage->random_ssids);
+        wifi_marauder_uart_tx((uint8_t*)add_random_command, strlen(add_random_command));
+    }
+    const char attack_command[] = "attack -t beacon -l\n";
+    wifi_marauder_uart_tx((uint8_t*)(attack_command), strlen(attack_command));
+    _wifi_marauder_script_delay(worker, stage->timeout);
+    _send_stop();
+}
+
+void _wifi_marauder_script_execute_beacon_ap(
+    WifiMarauderScriptStageBeaconAp* stage,
+    WifiMarauderScriptWorker* worker) {
+    const char command[] = "attack -t beacon -a\n";
+    wifi_marauder_uart_tx((uint8_t*)command, strlen(command));
+    _wifi_marauder_script_delay(worker, stage->timeout);
+    _send_stop();
+}
+
+void _wifi_marauder_script_execute_exec(WifiMarauderScriptStageExec* stage) {
+    if(stage->command != NULL) {
+        wifi_marauder_uart_tx((uint8_t*)stage->command, strlen(stage->command));
+        _send_line_break();
+    }
+}
+
+void _wifi_marauder_script_execute_delay(
+    WifiMarauderScriptStageDelay* stage,
+    WifiMarauderScriptWorker* worker) {
+    _wifi_marauder_script_delay(worker, stage->timeout);
+}
+
+void wifi_marauder_script_execute_start(void* context) {
+    furi_assert(context);
+    WifiMarauderScriptWorker* worker = context;
+    WifiMarauderScript* script = worker->script;
+    char command[100];
+
+    // Enables or disables the LED according to script settings
+    if(script->enable_led != WifiMarauderScriptBooleanUndefined) {
+        snprintf(
+            command,
+            sizeof(command),
+            "settings -s EnableLED %s",
+            script->enable_led ? "enable" : "disable");
+        wifi_marauder_uart_tx((uint8_t*)command, strlen(command));
+        _send_line_break();
+    }
+
+    // Enables or disables PCAP saving according to script settings
+    if(script->save_pcap != WifiMarauderScriptBooleanUndefined) {
+        snprintf(
+            command,
+            sizeof(command),
+            "settings -s SavePCAP %s",
+            script->save_pcap ? "enable" : "disable");
+        wifi_marauder_uart_tx((uint8_t*)command, strlen(command));
+        _send_line_break();
+    }
+}
+
+void wifi_marauder_script_execute_stage(WifiMarauderScriptStage* stage, void* context) {
+    furi_assert(context);
+    WifiMarauderScriptWorker* worker = context;
+    void* stage_data = stage->stage;
+
+    switch(stage->type) {
+    case WifiMarauderScriptStageTypeScan:
+        _wifi_marauder_script_execute_scan((WifiMarauderScriptStageScan*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeSelect:
+        _wifi_marauder_script_execute_select((WifiMarauderScriptStageSelect*)stage_data);
+        break;
+    case WifiMarauderScriptStageTypeDeauth:
+        _wifi_marauder_script_execute_deauth((WifiMarauderScriptStageDeauth*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeProbe:
+        _wifi_marauder_script_execute_probe((WifiMarauderScriptStageProbe*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeSniffRaw:
+        _wifi_marauder_script_execute_sniff_raw(
+            (WifiMarauderScriptStageSniffRaw*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeSniffBeacon:
+        _wifi_marauder_script_execute_sniff_beacon(
+            (WifiMarauderScriptStageSniffBeacon*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeSniffDeauth:
+        _wifi_marauder_script_execute_sniff_deauth(
+            (WifiMarauderScriptStageSniffDeauth*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeSniffEsp:
+        _wifi_marauder_script_execute_sniff_esp(
+            (WifiMarauderScriptStageSniffEsp*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeSniffPmkid:
+        _wifi_marauder_script_execute_sniff_pmkid(
+            (WifiMarauderScriptStageSniffPmkid*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeSniffPwn:
+        _wifi_marauder_script_execute_sniff_pwn(
+            (WifiMarauderScriptStageSniffPwn*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeBeaconList:
+        _wifi_marauder_script_execute_beacon_list(
+            (WifiMarauderScriptStageBeaconList*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeBeaconAp:
+        _wifi_marauder_script_execute_beacon_ap(
+            (WifiMarauderScriptStageBeaconAp*)stage_data, worker);
+        break;
+    case WifiMarauderScriptStageTypeExec:
+        _wifi_marauder_script_execute_exec((WifiMarauderScriptStageExec*)stage_data);
+        break;
+    case WifiMarauderScriptStageTypeDelay:
+        _wifi_marauder_script_execute_delay((WifiMarauderScriptStageDelay*)stage_data, worker);
+        break;
+    }
 }

+ 74 - 75
script/wifi_marauder_script_worker.c

@@ -1,76 +1,75 @@
-#include "../wifi_marauder_app_i.h"
-#include "wifi_marauder_script_worker.h"
-
-
-WifiMarauderScriptWorker* wifi_marauder_script_worker_alloc() {
-    WifiMarauderScriptWorker* worker = malloc(sizeof(WifiMarauderScriptWorker));
-    if(worker == NULL) {
-        return NULL;
-    }
-    worker->callback_start = NULL;
-    worker->callback_stage = NULL;
-    worker->worker_thread = NULL;
-    worker->is_running = false;
-    return worker;
-}
-
-int32_t _wifi_marauder_script_worker_task(void* worker) {
-    WifiMarauderScriptWorker* script_worker = worker;
-    WifiMarauderScript* script = script_worker->script;
-    if(script == NULL) {
-        return WifiMarauderScriptWorkerStatusInvalidScript;
-    }
-
-    // Setup
-    script_worker->callback_start(script_worker->context);
-    if(!script_worker->is_running) {
-        return WifiMarauderScriptWorkerStatusForceExit;
-    }
-
-    // Stages
-    for(int i = 0; i < script->repeat; i++) {
-        WifiMarauderScriptStage* current_stage = script->first_stage;
-        while(current_stage != NULL && script_worker->is_running) {
-            script_worker->callback_stage(current_stage, script_worker->context);
-            current_stage = current_stage->next_stage;
-        }
-        if(!script_worker->is_running) {
-            return WifiMarauderScriptWorkerStatusForceExit;
-        }
-    }
-
-    script_worker->is_running = false;
-
-    return WifiMarauderScriptWorkerStatusSuccess;
-}
-
-bool wifi_marauder_script_worker_start(
-    WifiMarauderScriptWorker* instance,
-    WifiMarauderScript* script) {
-    if(!instance || !script) {
-        return false;
-    }
-    instance->callback_start = wifi_marauder_script_execute_start;
-    instance->callback_stage = wifi_marauder_script_execute_stage;
-    instance->script = script;
-    instance->context = instance;
-    instance->is_running = true;
-    instance->worker_thread = furi_thread_alloc_ex(
-        "WifiMarauderScriptWorker", 1024, _wifi_marauder_script_worker_task, instance);
-    if(!instance->worker_thread) {
-        return false;
-    }
-    furi_thread_start(instance->worker_thread);
-    return true;
-}
-
-void wifi_marauder_script_worker_free(WifiMarauderScriptWorker* worker) {
-    if(worker != NULL) {
-        if(worker->worker_thread != NULL) {
-            worker->is_running = false;
-            furi_thread_join(worker->worker_thread);
-            furi_thread_free(worker->worker_thread);
-        }
-        free(worker);
-    }
+#include "../wifi_marauder_app_i.h"
+#include "wifi_marauder_script_worker.h"
+
+WifiMarauderScriptWorker* wifi_marauder_script_worker_alloc() {
+    WifiMarauderScriptWorker* worker = malloc(sizeof(WifiMarauderScriptWorker));
+    if(worker == NULL) {
+        return NULL;
+    }
+    worker->callback_start = NULL;
+    worker->callback_stage = NULL;
+    worker->worker_thread = NULL;
+    worker->is_running = false;
+    return worker;
+}
+
+int32_t _wifi_marauder_script_worker_task(void* worker) {
+    WifiMarauderScriptWorker* script_worker = worker;
+    WifiMarauderScript* script = script_worker->script;
+    if(script == NULL) {
+        return WifiMarauderScriptWorkerStatusInvalidScript;
+    }
+
+    // Setup
+    script_worker->callback_start(script_worker->context);
+    if(!script_worker->is_running) {
+        return WifiMarauderScriptWorkerStatusForceExit;
+    }
+
+    // Stages
+    for(int i = 0; i < script->repeat; i++) {
+        WifiMarauderScriptStage* current_stage = script->first_stage;
+        while(current_stage != NULL && script_worker->is_running) {
+            script_worker->callback_stage(current_stage, script_worker->context);
+            current_stage = current_stage->next_stage;
+        }
+        if(!script_worker->is_running) {
+            return WifiMarauderScriptWorkerStatusForceExit;
+        }
+    }
+
+    script_worker->is_running = false;
+
+    return WifiMarauderScriptWorkerStatusSuccess;
+}
+
+bool wifi_marauder_script_worker_start(
+    WifiMarauderScriptWorker* instance,
+    WifiMarauderScript* script) {
+    if(!instance || !script) {
+        return false;
+    }
+    instance->callback_start = wifi_marauder_script_execute_start;
+    instance->callback_stage = wifi_marauder_script_execute_stage;
+    instance->script = script;
+    instance->context = instance;
+    instance->is_running = true;
+    instance->worker_thread = furi_thread_alloc_ex(
+        "WifiMarauderScriptWorker", 1024, _wifi_marauder_script_worker_task, instance);
+    if(!instance->worker_thread) {
+        return false;
+    }
+    furi_thread_start(instance->worker_thread);
+    return true;
+}
+
+void wifi_marauder_script_worker_free(WifiMarauderScriptWorker* worker) {
+    if(worker != NULL) {
+        if(worker->worker_thread != NULL) {
+            worker->is_running = false;
+            furi_thread_join(worker->worker_thread);
+            furi_thread_free(worker->worker_thread);
+        }
+        free(worker);
+    }
 }

+ 1 - 1
wifi_marauder_app.h

@@ -4,7 +4,7 @@
 extern "C" {
 #endif
 
-#define WIFI_MARAUDER_APP_VERSION "v0.6.4"
+#define WIFI_MARAUDER_APP_VERSION "v0.6.5"
 
 typedef struct WifiMarauderApp WifiMarauderApp;
 

+ 3 - 1
wifi_marauder_app_i.h

@@ -6,6 +6,7 @@
 #include "scenes/wifi_marauder_scene.h"
 #include "wifi_marauder_custom_event.h"
 #include "wifi_marauder_uart.h"
+#include "wifi_marauder_ep.h"
 #include "file/sequential_file.h"
 #include "script/wifi_marauder_script.h"
 #include "script/wifi_marauder_script_worker.h"
@@ -26,13 +27,14 @@
 #include <lib/toolbox/path.h>
 #include <dialogs/dialogs.h>
 
-#define NUM_MENU_ITEMS (21)
+#define NUM_MENU_ITEMS (22)
 
 #define WIFI_MARAUDER_TEXT_BOX_STORE_SIZE (4096)
 #define WIFI_MARAUDER_TEXT_INPUT_STORE_SIZE (512)
 
 #define MARAUDER_APP_FOLDER_USER "apps_data/marauder"
 #define MARAUDER_APP_FOLDER EXT_PATH(MARAUDER_APP_FOLDER_USER)
+#define MARAUDER_APP_FOLDER_HTML MARAUDER_APP_FOLDER "/html"
 #define MARAUDER_APP_FOLDER_PCAPS MARAUDER_APP_FOLDER "/pcaps"
 #define MARAUDER_APP_FOLDER_LOGS MARAUDER_APP_FOLDER "/logs"
 #define MARAUDER_APP_FOLDER_USER_PCAPS MARAUDER_APP_FOLDER_USER "/pcaps"

+ 44 - 0
wifi_marauder_ep.c

@@ -0,0 +1,44 @@
+#include "wifi_marauder_ep.h"
+
+// returns success (if true, then caller needs to free(the_html))
+bool wifi_marauder_ep_read_html_file(WifiMarauderApp* app, uint8_t** the_html, size_t* html_size) {
+    // browse for files
+    FuriString* predefined_filepath = furi_string_alloc_set_str(MARAUDER_APP_FOLDER_HTML);
+    FuriString* selected_filepath = furi_string_alloc();
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, ".html", &I_Text_10x10);
+    if(!dialog_file_browser_show(
+           app->dialogs, selected_filepath, predefined_filepath, &browser_options)) {
+        return false;
+    }
+
+    File* index_html = storage_file_alloc(app->storage);
+    if(!storage_file_open(
+           index_html, furi_string_get_cstr(selected_filepath), FSAM_READ, FSOM_OPEN_EXISTING)) {
+        dialog_message_show_storage_error(app->dialogs, "Cannot open file");
+        return false;
+    }
+
+    uint64_t size = storage_file_size(index_html);
+
+    *the_html = malloc(size); // to be freed by caller
+    uint8_t* buf_ptr = *the_html;
+    size_t read = 0;
+    while(read < size) {
+        size_t to_read = size - read;
+        if(to_read > UINT16_MAX) to_read = UINT16_MAX;
+        uint16_t now_read = storage_file_read(index_html, buf_ptr, (uint16_t)to_read);
+        read += now_read;
+        buf_ptr += now_read;
+    }
+
+    *html_size = read;
+
+    storage_file_close(index_html);
+    storage_file_free(index_html);
+
+    furi_string_free(selected_filepath);
+    furi_string_free(predefined_filepath);
+
+    return true;
+}

+ 6 - 0
wifi_marauder_ep.h

@@ -0,0 +1,6 @@
+// evil portal helper
+#pragma once
+
+#include "wifi_marauder_app_i.h"
+
+bool wifi_marauder_ep_read_html_file(WifiMarauderApp* app, uint8_t** the_html, size_t* html_size);