فهرست منبع

Merge pull request #11 from tcpassos/feature_wifi_marauder_app

SCRIPTS!!
0xchocolate 2 سال پیش
والد
کامیت
1a8580793e
46فایلهای تغییر یافته به همراه6918 افزوده شده و 119 حذف شده
  1. 1 0
      applications/external/wifi_marauder_companion/application.fam
  2. 35 0
      applications/external/wifi_marauder_companion/file/sequential_file.c
  3. 6 0
      applications/external/wifi_marauder_companion/file/sequential_file.h
  4. BIN
      applications/external/wifi_marauder_companion/icons/DolphinCommon_56x48.png
  5. 9 0
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_config.h
  6. 80 25
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c
  7. 63 0
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_confirm_delete.c
  8. 97 0
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_edit.c
  9. 148 0
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_edit_list.c
  10. 87 0
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_options.c
  11. 78 0
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_select.c
  12. 74 0
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_settings.c
  13. 212 0
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_stage_add.c
  14. 179 0
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_stage_edit.c
  15. 18 7
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c
  16. 131 0
      applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_user_input.c
  17. 3169 0
      applications/external/wifi_marauder_companion/script/cJSON.c
  18. 293 0
      applications/external/wifi_marauder_companion/script/cJSON.h
  19. 31 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu.c
  20. 40 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu.h
  21. 28 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_beaconap.c
  22. 62 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_beaconlist.c
  23. 14 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_config.h
  24. 28 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_deauth.c
  25. 28 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_delay.c
  26. 31 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_exec.c
  27. 28 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_probe.c
  28. 92 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_scan.c
  29. 97 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_select.c
  30. 28 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffbeacon.c
  31. 28 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffdeauth.c
  32. 28 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffesp.c
  33. 92 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffpmkid.c
  34. 28 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffpwn.c
  35. 28 0
      applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffraw.c
  36. 845 0
      applications/external/wifi_marauder_companion/script/wifi_marauder_script.c
  37. 249 0
      applications/external/wifi_marauder_companion/script/wifi_marauder_script.h
  38. 262 0
      applications/external/wifi_marauder_companion/script/wifi_marauder_script_executor.c
  39. 6 0
      applications/external/wifi_marauder_companion/script/wifi_marauder_script_executor.h
  40. 71 0
      applications/external/wifi_marauder_companion/script/wifi_marauder_script_worker.c
  41. 41 0
      applications/external/wifi_marauder_companion/script/wifi_marauder_script_worker.h
  42. 12 0
      applications/external/wifi_marauder_companion/wifi_marauder_app.c
  43. 39 2
      applications/external/wifi_marauder_companion/wifi_marauder_app_i.h
  44. 2 1
      applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h
  45. 0 64
      applications/external/wifi_marauder_companion/wifi_marauder_pcap.c
  46. 0 20
      applications/external/wifi_marauder_companion/wifi_marauder_pcap.h

+ 1 - 0
applications/external/wifi_marauder_companion/application.fam

@@ -8,5 +8,6 @@ App(
     stack_size=4 * 1024,
     order=90,
     fap_icon="wifi_10px.png",
+    fap_icon_assets="icons",
     fap_category="GPIO",
 )

+ 35 - 0
applications/external/wifi_marauder_companion/file/sequential_file.c

@@ -0,0 +1,35 @@
+#include "sequential_file.h"
+
+char* sequential_file_resolve_path(Storage* storage, const char* dir, const char* prefix, const char* extension) {
+    if (storage == NULL || dir == NULL || prefix == NULL || extension == NULL) {
+        return NULL;
+    }
+
+    char file_path[256];
+    int file_index = 0;
+
+    do {
+        if (snprintf(file_path, sizeof(file_path), "%s/%s_%d.%s", dir, prefix, file_index, extension) < 0) {
+            return NULL;
+        }
+        file_index++;
+    } while(storage_file_exists(storage, file_path));
+
+    return strdup(file_path);
+}
+
+bool sequential_file_open(Storage* storage, File* file, const char* dir, const char* prefix, const char* extension) {
+    if (storage == NULL || file == NULL || dir == NULL || prefix == NULL || extension == NULL) {
+        return false;
+    }
+
+    char* file_path = sequential_file_resolve_path(storage, dir, prefix, extension);
+    if (file_path == NULL) {
+        return false;
+    }
+
+    bool success = storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS);
+    free(file_path);
+
+    return success;
+}

+ 6 - 0
applications/external/wifi_marauder_companion/file/sequential_file.h

@@ -0,0 +1,6 @@
+#pragma once
+
+#include <storage/storage.h>
+
+char* sequential_file_resolve_path(Storage* storage, const char* dir, const char* prefix, const char* extension);
+bool sequential_file_open(Storage* storage, File* file, const char* dir, const char* prefix, const char* extension);

BIN
applications/external/wifi_marauder_companion/icons/DolphinCommon_56x48.png


+ 9 - 0
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_config.h

@@ -3,3 +3,12 @@ ADD_SCENE(wifi_marauder, console_output, ConsoleOutput)
 ADD_SCENE(wifi_marauder, text_input, TextInput)
 ADD_SCENE(wifi_marauder, settings_init, SettingsInit)
 ADD_SCENE(wifi_marauder, log_viewer, LogViewer)
+ADD_SCENE(wifi_marauder, user_input, UserInput)
+ADD_SCENE(wifi_marauder, script_select, ScriptSelect)
+ADD_SCENE(wifi_marauder, script_options, ScriptOptions)
+ADD_SCENE(wifi_marauder, script_edit, ScriptEdit)
+ADD_SCENE(wifi_marauder, script_settings, ScriptSettings)
+ADD_SCENE(wifi_marauder, script_confirm_delete, ScriptConfirmDelete)
+ADD_SCENE(wifi_marauder, script_stage_edit, ScriptStageEdit)
+ADD_SCENE(wifi_marauder, script_stage_add, ScriptStageAdd)
+ADD_SCENE(wifi_marauder, script_stage_edit_list, ScriptStageEditList)

+ 80 - 25
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c

@@ -1,5 +1,30 @@
 #include "../wifi_marauder_app_i.h"
 
+char* _wifi_marauder_get_prefix_from_cmd(const char* command) {
+    int end = strcspn(command, " ");
+    char* prefix = (char*) malloc(sizeof(char) * (end + 1));
+    strncpy(prefix, command, end);
+    prefix[end] = '\0';
+    return prefix;
+}
+
+bool _wifi_marauder_is_save_pcaps_enabled(WifiMarauderApp* app) {
+    if (!app->ok_to_save_pcaps) {
+        return false;
+    }
+    // If it is a script that contains a sniff function
+    if (app->script != NULL) {
+        return wifi_marauder_script_has_stage(app->script, WifiMarauderScriptStageTypeSniffRaw) ||
+               wifi_marauder_script_has_stage(app->script, WifiMarauderScriptStageTypeSniffBeacon) ||
+               wifi_marauder_script_has_stage(app->script, WifiMarauderScriptStageTypeSniffDeauth) ||
+               wifi_marauder_script_has_stage(app->script, WifiMarauderScriptStageTypeSniffEsp) ||
+               wifi_marauder_script_has_stage(app->script, WifiMarauderScriptStageTypeSniffPmkid) ||
+               wifi_marauder_script_has_stage(app->script, WifiMarauderScriptStageTypeSniffPwn);
+    }
+    // If it is a sniff function
+    return app->is_command && app->selected_tx_string && strncmp("sniff", app->selected_tx_string, strlen("sniff")) == 0;
+}
+
 void wifi_marauder_console_output_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) {
     furi_assert(context);
     WifiMarauderApp* app = context;
@@ -34,23 +59,29 @@ void wifi_marauder_console_output_handle_rx_packets_cb(uint8_t* buf, size_t len,
 void wifi_marauder_scene_console_output_on_enter(void* context) {
     WifiMarauderApp* app = context;
 
+    // Reset text box and set font
     TextBox* text_box = app->text_box;
-    text_box_reset(app->text_box);
+    text_box_reset(text_box);
     text_box_set_font(text_box, TextBoxFontText);
+
+    // Set focus on start or end
     if(app->focus_console_start) {
         text_box_set_focus(text_box, TextBoxFocusStart);
     } else {
         text_box_set_focus(text_box, TextBoxFocusEnd);
     }
+
+    // Set command-related messages
     if(app->is_command) {
         furi_string_reset(app->text_box_store);
         app->text_box_store_strlen = 0;
+        // Help message
         if(0 == strncmp("help", app->selected_tx_string, strlen("help"))) {
             const char* help_msg = "Marauder companion " WIFI_MARAUDER_APP_VERSION "\n";
             furi_string_cat_str(app->text_box_store, help_msg);
             app->text_box_store_strlen += strlen(help_msg);
         }
-
+        // Stopscan message
         if(app->show_stopscan_tip) {
             const char* help_msg = "Press BACK to send stopscan\n";
             furi_string_cat_str(app->text_box_store, help_msg);
@@ -58,39 +89,59 @@ void wifi_marauder_scene_console_output_on_enter(void* context) {
         }
     }
 
-    // Set starting text - for "View Log from end", this will just be what was already in the text box store
+    // Set starting text
     text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store));
 
+    // Set scene state and switch view
     scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneConsoleOutput, 0);
     view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewConsoleOutput);
 
-    // Register callback to receive data
-    wifi_marauder_uart_set_handle_rx_data_cb(
-        app->uart,
-        wifi_marauder_console_output_handle_rx_data_cb); // setup callback for general log rx thread
-    wifi_marauder_uart_set_handle_rx_data_cb(
-        app->lp_uart,
-        wifi_marauder_console_output_handle_rx_packets_cb); // setup callback for packets rx thread
-
+    // Register callbacks to receive data
+    wifi_marauder_uart_set_handle_rx_data_cb(app->uart, wifi_marauder_console_output_handle_rx_data_cb); // setup callback for general log rx thread
+    wifi_marauder_uart_set_handle_rx_data_cb(app->lp_uart, wifi_marauder_console_output_handle_rx_packets_cb); // setup callback for packets rx thread
+    
     // Get ready to send command
-    if(app->is_command && app->selected_tx_string) {
+    if((app->is_command && app->selected_tx_string) || app->script) {
+        const char* prefix = strlen(app->selected_tx_string) > 0 ?
+                             _wifi_marauder_get_prefix_from_cmd(app->selected_tx_string) : // Function name
+                             app->script->name;                                            // Script name
+
         // Create files *before* sending command
         // (it takes time to iterate through the directory)
         if(app->ok_to_save_logs) {
-            app->is_writing_log = true;
-            wifi_marauder_create_log_file(app);
+            strcpy(app->log_file_path, sequential_file_resolve_path(app->storage, MARAUDER_APP_FOLDER_LOGS, prefix, "log"));
+            if (app->log_file_path != NULL) {
+                if (storage_file_open(app->log_file, app->log_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+                    app->is_writing_log = true;
+                } else {
+                    dialog_message_show_storage_error(app->dialogs, "Cannot open log file");
+                }
+            } else {
+                dialog_message_show_storage_error(app->dialogs, "Cannot resolve log path");
+            }
         }
 
-        // If it is a sniff function, open the pcap file for recording
-        if(app->ok_to_save_pcaps && strncmp("sniff", app->selected_tx_string, strlen("sniff")) == 0) {
-            app->is_writing_pcap = true;
-            wifi_marauder_create_pcap_file(app);
+        // If it is a sniff function or script, open the pcap file for recording
+        if (_wifi_marauder_is_save_pcaps_enabled(app)) {
+            if (sequential_file_open(app->storage, app->capture_file, MARAUDER_APP_FOLDER_PCAPS, prefix, "pcap")) {
+                app->is_writing_pcap = true;
+            } else {
+                dialog_message_show_storage_error(app->dialogs, "Cannot open pcap file");
+            }
         }
 
         // Send command with newline '\n'
-        wifi_marauder_uart_tx(
-            (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
-        wifi_marauder_uart_tx((uint8_t*)("\n"), 1);
+        if (app->selected_tx_string) {
+            wifi_marauder_uart_tx(
+                (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
+            wifi_marauder_uart_tx((uint8_t*)("\n"), 1);
+        }
+
+        // Run the script if the file with the script has been opened
+        if(app->script != NULL) {
+            app->script_worker = wifi_marauder_script_worker_alloc();
+            wifi_marauder_script_worker_start(app->script_worker, app->script);
+        }
     }
 }
 
@@ -112,15 +163,19 @@ bool wifi_marauder_scene_console_output_on_event(void* context, SceneManagerEven
 void wifi_marauder_scene_console_output_on_exit(void* context) {
     WifiMarauderApp* app = context;
 
-    // Unregister rx callback
-    wifi_marauder_uart_set_handle_rx_data_cb(app->uart, NULL);
-    wifi_marauder_uart_set_handle_rx_data_cb(app->lp_uart, NULL);
-
     // Automatically stop the scan when exiting view
     if(app->is_command) {
         wifi_marauder_uart_tx((uint8_t*)("stopscan\n"), strlen("stopscan\n"));
+        furi_delay_ms(50);
     }
 
+    // Unregister rx callback
+    wifi_marauder_uart_set_handle_rx_data_cb(app->uart, NULL);
+    wifi_marauder_uart_set_handle_rx_data_cb(app->lp_uart, NULL);
+
+    wifi_marauder_script_worker_free(app->script_worker);
+    app->script_worker = NULL;
+
     app->is_writing_pcap = false;
     if(app->capture_file && storage_file_is_open(app->capture_file)) {
         storage_file_close(app->capture_file);

+ 63 - 0
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_confirm_delete.c

@@ -0,0 +1,63 @@
+#include "../wifi_marauder_app_i.h"
+
+void wifi_marauder_scene_script_confirm_delete_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    WifiMarauderApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+void wifi_marauder_scene_script_confirm_delete_on_enter(void* context) {
+    WifiMarauderApp* app = context;
+
+    widget_add_button_element(app->widget, GuiButtonTypeLeft, "No", wifi_marauder_scene_script_confirm_delete_widget_callback, app);
+    widget_add_button_element(app->widget, GuiButtonTypeRight, "Yes", wifi_marauder_scene_script_confirm_delete_widget_callback, app);
+
+    widget_add_string_element(app->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Are you sure?");
+    widget_add_text_box_element(
+        app->widget,
+        0, 12, 128, 38,
+        AlignCenter, AlignCenter,
+        "The script will be\npermanently deleted",
+        false);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewWidget);
+}
+
+bool wifi_marauder_scene_script_confirm_delete_on_event(void* context, SceneManagerEvent event) {
+    WifiMarauderApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        // get which button press: "Yes" or "No"
+        if(event.event == GuiButtonTypeRight) {
+            // Yes
+            if (app->script != NULL) {
+                char script_path[256];
+                snprintf(script_path, sizeof(script_path), "%s/%s.json", MARAUDER_APP_FOLDER_SCRIPTS, app->script->name);
+                storage_simply_remove(app->storage, script_path);
+                wifi_marauder_script_free(app->script);
+                app->script = NULL;
+
+                DialogMessage* message = dialog_message_alloc();
+                dialog_message_set_text(message, "Deleted!", 88, 32, AlignCenter, AlignCenter);
+                dialog_message_set_icon(message, &I_DolphinCommon_56x48, 5, 6);
+                dialog_message_set_buttons(message, NULL, "Ok", NULL);
+                dialog_message_show(app->dialogs, message);
+                dialog_message_free(message);
+            }
+        }
+        scene_manager_previous_scene(app->scene_manager);
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void wifi_marauder_scene_script_confirm_delete_on_exit(void* context) {
+    WifiMarauderApp* app = context;
+    widget_reset(app->widget);
+}

+ 97 - 0
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_edit.c

@@ -0,0 +1,97 @@
+#include "../wifi_marauder_app_i.h"
+
+static void wifi_marauder_scene_script_edit_callback(void* context, uint32_t index) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStage* current_stage = app->script->first_stage;
+    uint32_t stage_index = 0;
+
+    while (current_stage != NULL && stage_index < index) {
+        current_stage = current_stage->next_stage;
+        stage_index++;
+    }
+    app->script_edit_selected_stage = current_stage;
+
+    if (app->script_edit_selected_stage != NULL) {
+        scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptEdit, index);
+        scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptStageEdit);
+    }
+}
+
+static void wifi_marauder_scene_script_edit_add_callback(void* context, uint32_t index) {
+    WifiMarauderApp* app = context;
+    scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptEdit, index);
+    scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptStageAdd);
+}
+
+void wifi_marauder_scene_script_edit_on_enter(void* context) {
+    WifiMarauderApp* app = context;
+    Submenu* submenu = app->submenu;
+    WifiMarauderScript* script = app->script;
+    submenu_set_header(submenu, script->name);
+
+    WifiMarauderScriptStage* current_stage = script->first_stage;
+    int stage_index = 0;
+    while (current_stage != NULL) {
+        switch (current_stage->type) {
+            case WifiMarauderScriptStageTypeScan:
+                submenu_add_item(submenu, "Scan", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeSelect:
+                submenu_add_item(submenu, "Select", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeDeauth:
+                submenu_add_item(submenu, "Deauth", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeProbe:
+                submenu_add_item(submenu, "Probe", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeSniffRaw:
+                submenu_add_item(submenu, "Sniff raw", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeSniffBeacon:
+                submenu_add_item(submenu, "Sniff beacon", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeSniffDeauth:
+                submenu_add_item(submenu, "Sniff deauth", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeSniffEsp:
+                submenu_add_item(submenu, "Sniff esp", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeSniffPmkid:
+                submenu_add_item(submenu, "Sniff PMKID", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeSniffPwn:
+                submenu_add_item(submenu, "Sniff pwn", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeBeaconList:
+                submenu_add_item(submenu, "Beacon list", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeBeaconAp:
+                submenu_add_item(submenu, "Beacon AP", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeExec:
+                submenu_add_item(submenu, "Custom command", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+            case WifiMarauderScriptStageTypeDelay:
+                submenu_add_item(submenu, "Delay", stage_index, wifi_marauder_scene_script_edit_callback, app);
+                break;
+        }
+        current_stage = current_stage->next_stage;
+        stage_index++;
+    }
+
+    submenu_add_item(submenu, "[+] ADD STAGE", stage_index++, wifi_marauder_scene_script_edit_add_callback, app);
+    submenu_set_selected_item(submenu, scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneScriptEdit));
+    view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewSubmenu);
+}
+
+bool wifi_marauder_scene_script_edit_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void wifi_marauder_scene_script_edit_on_exit(void* context) {
+    WifiMarauderApp* app = context;
+    submenu_reset(app->submenu);
+}

+ 148 - 0
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_edit_list.c

@@ -0,0 +1,148 @@
+#include "../wifi_marauder_app_i.h"
+
+static void wifi_marauder_scene_script_stage_edit_list_add_callback(void* context, uint32_t index) {
+    WifiMarauderApp* app = context;
+
+    // Creates new item
+    WifiMarauderScriptStageListItem* new_item = (WifiMarauderScriptStageListItem*) malloc(sizeof(WifiMarauderScriptStageListItem));
+    new_item->value = malloc(64);
+    new_item->next_item = NULL;
+    
+    if (app->script_stage_edit_first_item == NULL) {
+        app->script_stage_edit_first_item = new_item;
+    } else {
+        WifiMarauderScriptStageListItem* last_item = app->script_stage_edit_first_item;
+        while (last_item->next_item != NULL) {
+            last_item = last_item->next_item;
+        }
+        last_item->next_item = new_item;
+    }
+
+    scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptStageEditList, index);
+    app->user_input_type = WifiMarauderUserInputTypeString;
+    app->user_input_string_reference = &new_item->value;
+    scene_manager_next_scene(app->scene_manager, WifiMarauderSceneUserInput);
+}
+
+static void wifi_marauder_scene_script_stage_edit_list_deallocate_items(WifiMarauderApp* app) {
+    WifiMarauderScriptStageListItem* current_item = app->script_stage_edit_first_item;
+    while (current_item != NULL) {
+        WifiMarauderScriptStageListItem* next_item = current_item->next_item;
+        free(current_item->value);
+        free(current_item);
+        current_item = next_item;
+    }
+    app->script_stage_edit_first_item = NULL;
+}
+
+static void wifi_marauder_scene_script_stage_edit_list_save_strings(WifiMarauderApp* app) {
+    WifiMarauderScriptStageListItem* current_item = app->script_stage_edit_first_item;
+    int array_size = 0;
+
+    // Calculates the required array size
+    while (current_item != NULL) {
+        array_size++;
+        current_item = current_item->next_item;
+    }
+
+    // Reallocate the array of strings if necessary
+    if (*app->script_stage_edit_string_count_reference < array_size) {
+        *app->script_stage_edit_strings_reference = realloc(*app->script_stage_edit_strings_reference, array_size * sizeof(char*));
+    }
+
+    // Fills the array of strings
+    current_item = app->script_stage_edit_first_item;
+    int i = 0;
+    while (current_item != NULL) {
+        char* current_str = malloc(strlen(current_item->value) + 1);
+        strncpy(current_str, current_item->value, strlen(current_item->value) + 1);
+        (*app->script_stage_edit_strings_reference)[i] = current_str;
+        current_item = current_item->next_item;
+        i++;
+    }
+
+    *app->script_stage_edit_string_count_reference = array_size;
+}
+
+static void wifi_marauder_scene_script_stage_edit_list_save_numbers(WifiMarauderApp* app) {
+    WifiMarauderScriptStageListItem* current_item = app->script_stage_edit_first_item;
+    int array_size = 0;
+
+    // Calculates the required array size
+    while (current_item != NULL) {
+        array_size++;
+        current_item = current_item->next_item;
+    }
+
+    // Reallocate the array of integers if necessary
+    if (*app->script_stage_edit_number_count_reference < array_size) {
+        *app->script_stage_edit_numbers_reference = realloc(*app->script_stage_edit_numbers_reference, array_size * sizeof(int));
+    }
+
+    // Fills the array of integers
+    current_item = app->script_stage_edit_first_item;
+    int i = 0;
+    while (current_item != NULL) {
+        (*app->script_stage_edit_numbers_reference)[i] = atoi(current_item->value);
+        current_item = current_item->next_item;
+        i++;
+    }
+
+    *app->script_stage_edit_number_count_reference = array_size;
+}
+
+static void wifi_marauder_scene_script_stage_edit_list_save_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    if (app->script_stage_edit_strings_reference != NULL && app->script_stage_edit_string_count_reference != NULL) {
+        wifi_marauder_scene_script_stage_edit_list_save_strings(app);
+    }
+
+    if (app->script_stage_edit_numbers_reference != NULL && app->script_stage_edit_number_count_reference != NULL) {
+        wifi_marauder_scene_script_stage_edit_list_save_numbers(app);
+    }
+
+    wifi_marauder_scene_script_stage_edit_list_deallocate_items(app);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+static void wifi_marauder_scene_script_stage_edit_list_clear_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    wifi_marauder_scene_script_stage_edit_list_deallocate_items(app);
+
+    submenu_reset(app->submenu);
+    submenu_add_item(app->submenu, "[+] ADD ITEM", 99, wifi_marauder_scene_script_stage_edit_list_add_callback, app);
+    submenu_add_item(app->submenu, "[*] SAVE ITEMS", 99, wifi_marauder_scene_script_stage_edit_list_save_callback, app);
+    submenu_add_item(app->submenu, "[-] CLEAR LIST", 99, wifi_marauder_scene_script_stage_edit_list_clear_callback, app);
+}
+
+void wifi_marauder_scene_script_stage_edit_list_on_enter(void* context) {
+    WifiMarauderApp* app = context;
+    int item_index = 0;
+    WifiMarauderScriptStageListItem* current_item = app->script_stage_edit_first_item;
+
+    while (current_item != NULL) {
+        submenu_add_item(app->submenu, current_item->value, item_index++, NULL, app);
+        current_item = current_item->next_item;
+    }
+    submenu_add_item(app->submenu, "[+] ADD ITEM", 99, wifi_marauder_scene_script_stage_edit_list_add_callback, app);
+    submenu_add_item(app->submenu, "[*] SAVE ITEMS", 99, wifi_marauder_scene_script_stage_edit_list_save_callback, app);
+    submenu_add_item(app->submenu, "[-] CLEAR LIST", 99, wifi_marauder_scene_script_stage_edit_list_clear_callback, app);
+
+    submenu_set_selected_item(app->submenu, scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneScriptStageEditList));
+    view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewSubmenu);
+}
+
+bool wifi_marauder_scene_script_stage_edit_list_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void wifi_marauder_scene_script_stage_edit_list_on_exit(void* context) {
+    WifiMarauderApp* app = context;
+    submenu_reset(app->submenu);
+}

+ 87 - 0
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_options.c

@@ -0,0 +1,87 @@
+#include "../wifi_marauder_app_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexRun,
+    SubmenuIndexSettings,
+    SubmenuIndexEditStages,
+    SubmenuIndexSave,
+    SubmenuIndexDelete
+};
+
+void wifi_marauder_scene_script_options_save_script(WifiMarauderApp* app) {   
+    char script_path[256];
+    snprintf(script_path, sizeof(script_path), "%s/%s.json", MARAUDER_APP_FOLDER_SCRIPTS, app->script->name);
+    wifi_marauder_script_save_json(app->storage, script_path, app->script);
+
+    DialogMessage* message = dialog_message_alloc();
+    dialog_message_set_text(message, "Saved!", 88, 32, AlignCenter, AlignCenter);
+    dialog_message_set_icon(message, &I_DolphinCommon_56x48, 5, 6);
+    dialog_message_set_buttons(message, NULL, "Ok", NULL);
+    dialog_message_show(app->dialogs, message);
+    dialog_message_free(message);
+}
+
+static void wifi_marauder_scene_script_options_callback(void* context, uint32_t index) {
+    WifiMarauderApp* app = context;
+
+    switch (index) {
+        case SubmenuIndexRun:
+            scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptOptions, index);
+            scene_manager_next_scene(app->scene_manager, WifiMarauderSceneConsoleOutput);
+            break;
+        case SubmenuIndexSettings:
+            scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptOptions, index);
+            scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptSettings);
+            break;
+        case SubmenuIndexEditStages:
+            scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptOptions, index);
+            scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptEdit);
+            break;
+        case SubmenuIndexSave:
+            wifi_marauder_scene_script_options_save_script(app);
+            break;
+        case SubmenuIndexDelete:
+            scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptOptions, index);
+            scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptConfirmDelete);
+            break;
+    }
+}
+
+void wifi_marauder_scene_script_options_on_enter(void* context) {
+    WifiMarauderApp* app = context;
+
+    // If returning after confirming script deletion
+    if (app->script == NULL) {
+        scene_manager_previous_scene(app->scene_manager);
+        return;
+    }
+
+    Submenu* submenu = app->submenu;
+
+    submenu_set_header(submenu, app->script->name);
+    submenu_add_item(submenu, "[>] RUN", SubmenuIndexRun, wifi_marauder_scene_script_options_callback, app);
+    submenu_add_item(submenu, "[S] SETTINGS", SubmenuIndexSettings, wifi_marauder_scene_script_options_callback, app);
+    submenu_add_item(submenu, "[+] EDIT STAGES", SubmenuIndexEditStages, wifi_marauder_scene_script_options_callback, app);
+    submenu_add_item(submenu, "[*] SAVE", SubmenuIndexSave, wifi_marauder_scene_script_options_callback, app);
+    submenu_add_item(submenu, "[X] DELETE", SubmenuIndexDelete, wifi_marauder_scene_script_options_callback, app);
+
+    submenu_set_selected_item(submenu, scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneScriptOptions));
+    view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewSubmenu);
+}
+
+bool wifi_marauder_scene_script_options_on_event(void* context, SceneManagerEvent event) {
+    WifiMarauderApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        wifi_marauder_script_free(app->script);
+        app->script = NULL;
+    }
+
+    return consumed;
+}
+
+void wifi_marauder_scene_script_options_on_exit(void* context) {
+    WifiMarauderApp* app = context;
+    submenu_reset(app->submenu);
+}

+ 78 - 0
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_select.c

@@ -0,0 +1,78 @@
+#include "../wifi_marauder_app_i.h"
+
+static void wifi_marauder_scene_script_select_callback(void* context, uint32_t index) {
+    WifiMarauderApp* app = context;
+
+    char script_path[256];
+    snprintf(script_path, sizeof(script_path), "%s/%s.json", MARAUDER_APP_FOLDER_SCRIPTS, furi_string_get_cstr(app->script_list[index]));
+
+    app->script = wifi_marauder_script_parse_json(app->storage, script_path);
+    if (app->script) {
+        scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptSelect, index);
+        scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptOptions);
+    }
+}
+
+static void wifi_marauder_scene_script_select_add_callback(void* context, uint32_t index) {
+    WifiMarauderApp* app = context;
+    scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptSelect, index);
+
+    app->user_input_type = WifiMarauderUserInputTypeFileName;
+    app->user_input_file_dir = strdup(MARAUDER_APP_FOLDER_SCRIPTS);
+    app->user_input_file_extension = strdup("json");
+    scene_manager_next_scene(app->scene_manager, WifiMarauderSceneUserInput);
+}
+
+void wifi_marauder_scene_script_select_on_enter(void* context) {
+    WifiMarauderApp* app = context;
+    Submenu* submenu = app->submenu;
+
+    File* dir_scripts = storage_file_alloc(app->storage);
+    if(storage_dir_open(dir_scripts, MARAUDER_APP_FOLDER_SCRIPTS)) {
+        FileInfo file_info;
+        char file_path[255];
+        app->script_list_count = 0;
+        // Goes through the files in the folder counting the ones that end with the json extension
+        while(storage_dir_read(dir_scripts, &file_info, file_path, 255)) {
+            app->script_list_count++;
+        }
+        if (app->script_list_count > 0) {
+            submenu_set_header(submenu, "Select a script:");
+            app->script_list = malloc(app->script_list_count * sizeof(FuriString*));
+            storage_dir_close(dir_scripts);
+            storage_dir_open(dir_scripts, MARAUDER_APP_FOLDER_SCRIPTS);
+            // Read the files again from the beginning, adding the scripts in the list
+            int script_index = 0;
+            while(storage_dir_read(dir_scripts, &file_info, file_path, 255)) {
+                app->script_list[script_index] = furi_string_alloc();
+                path_extract_filename_no_ext(file_path, app->script_list[script_index]);
+                submenu_add_item(submenu, furi_string_get_cstr(app->script_list[script_index]), script_index, wifi_marauder_scene_script_select_callback, app);
+                script_index++;
+            }
+        } else {
+            submenu_set_header(submenu, "No script found");
+        }
+        submenu_add_item(submenu, "[+] ADD SCRIPT", 99, wifi_marauder_scene_script_select_add_callback, app);
+        storage_dir_close(dir_scripts);
+    }
+    storage_file_free(dir_scripts);
+
+    submenu_set_selected_item(submenu, scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneScriptSelect));
+    view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewSubmenu);
+}
+
+bool wifi_marauder_scene_script_select_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void wifi_marauder_scene_script_select_on_exit(void* context) {
+    WifiMarauderApp* app = context;
+    submenu_reset(app->submenu);
+
+    for (int i = 0; i < app->script_list_count; i++) {
+        furi_string_free(app->script_list[i]);
+    }
+    free(app->script_list);   
+}

+ 74 - 0
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_settings.c

@@ -0,0 +1,74 @@
+#include "../wifi_marauder_app_i.h"
+
+enum ScriptSettingsOption {
+    ScriptSettingsOptionRepeat,
+    ScriptSettingsOptionSavePcap,
+    ScriptSettingsOptionEnableLed
+};
+
+const char* option_values[3] = {"No", "Yes", "Default"};
+
+static void wifi_marauder_scene_script_settings_enter_callback(void* context, uint32_t index) {
+    WifiMarauderApp* app = context;
+    // Accept script repeat value
+    if (index == ScriptSettingsOptionRepeat) {
+        scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptSettings, index);
+        app->user_input_type = WifiMarauderUserInputTypeNumber;
+        app->user_input_number_reference = &app->script->repeat;
+        scene_manager_next_scene(app->scene_manager, WifiMarauderSceneUserInput);
+    }
+}
+
+static void wifi_marauder_scene_script_settings_change_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+
+    uint8_t current_option = variable_item_list_get_selected_item_index(app->var_item_list);
+    uint8_t option_value_index = variable_item_get_current_value_index(item);
+
+    switch (current_option) {
+        case ScriptSettingsOptionSavePcap:
+            variable_item_set_current_value_text(item, option_values[option_value_index]);
+            app->script->save_pcap = option_value_index;
+            break;
+        case ScriptSettingsOptionEnableLed:
+            variable_item_set_current_value_text(item, option_values[option_value_index]);
+            app->script->enable_led = option_value_index;
+            break;
+    }
+}
+
+void wifi_marauder_scene_script_settings_on_enter(void* context) {
+    WifiMarauderApp* app = context;
+    VariableItemList* var_item_list = app->var_item_list;
+    variable_item_list_set_enter_callback(app->var_item_list, wifi_marauder_scene_script_settings_enter_callback, app);
+
+    // Script repeat option
+    VariableItem* repeat_item = variable_item_list_add(app->var_item_list, "Repeat", 1, NULL, app);
+    char repeat_str[32];
+    snprintf(repeat_str, sizeof(repeat_str), "%d", app->script->repeat);
+    variable_item_set_current_value_text(repeat_item, repeat_str);
+
+    // Save PCAP option
+    VariableItem* save_pcap_item = variable_item_list_add(app->var_item_list, "Save PCAP", 3, wifi_marauder_scene_script_settings_change_callback, app);
+    variable_item_set_current_value_index(save_pcap_item, app->script->save_pcap);
+    variable_item_set_current_value_text(save_pcap_item, option_values[app->script->save_pcap]);
+
+    // Enable board LED option
+    VariableItem* enable_led_item = variable_item_list_add(app->var_item_list, "Enable LED", 3, wifi_marauder_scene_script_settings_change_callback, app);
+    variable_item_set_current_value_index(enable_led_item, app->script->enable_led);
+    variable_item_set_current_value_text(enable_led_item, option_values[app->script->enable_led]);
+
+    variable_item_list_set_selected_item(var_item_list, scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneScriptSettings));
+    view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewVarItemList);
+}
+
+bool wifi_marauder_scene_script_settings_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void wifi_marauder_scene_script_settings_on_exit(void* context) {
+    WifiMarauderApp* app = context;
+    variable_item_list_reset(app->var_item_list);
+}

+ 212 - 0
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_stage_add.c

@@ -0,0 +1,212 @@
+#include "../wifi_marauder_app_i.h"
+
+// Scan
+static void wifi_marauder_scene_script_stage_add_scan_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageScan* stage = (WifiMarauderScriptStageScan*) malloc(sizeof(WifiMarauderScriptStageScan));
+    stage->type = WifiMarauderScriptScanTypeAp;
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_SCAN;
+
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeScan, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Select
+static void wifi_marauder_scene_script_stage_add_select_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageSelect* stage = (WifiMarauderScriptStageSelect*) malloc(sizeof(WifiMarauderScriptStageSelect));
+    stage->type = WifiMarauderScriptSelectTypeAp;
+    stage->filter = strdup("all");
+    
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeSelect, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Deauth
+static void wifi_marauder_scene_script_stage_add_deauth_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageDeauth* stage = (WifiMarauderScriptStageDeauth*) malloc(sizeof(WifiMarauderScriptStageDeauth));
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_DEAUTH;
+    
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeDeauth, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Probe
+static void wifi_marauder_scene_script_stage_add_probe_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageProbe* stage = (WifiMarauderScriptStageProbe*) malloc(sizeof(WifiMarauderScriptStageProbe));
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_PROBE;
+    
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeProbe, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Sniff RAW
+static void wifi_marauder_scene_script_stage_add_sniffraw_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageSniffRaw* stage = (WifiMarauderScriptStageSniffRaw*) malloc(sizeof(WifiMarauderScriptStageSniffRaw));
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+    
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeSniffRaw, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Sniff Beacon
+static void wifi_marauder_scene_script_stage_add_sniffbeacon_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageSniffBeacon* stage = (WifiMarauderScriptStageSniffBeacon*) malloc(sizeof(WifiMarauderScriptStageSniffBeacon));
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+    
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeSniffBeacon, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Sniff Deauth
+static void wifi_marauder_scene_script_stage_add_sniffdeauth_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageSniffDeauth* stage = (WifiMarauderScriptStageSniffDeauth*) malloc(sizeof(WifiMarauderScriptStageSniffDeauth));
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+    
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeSniffDeauth, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Sniff Esp
+static void wifi_marauder_scene_script_stage_add_sniffesp_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageSniffEsp* stage = (WifiMarauderScriptStageSniffEsp*) malloc(sizeof(WifiMarauderScriptStageSniffEsp));
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+    
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeSniffEsp, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Sniff PMKID
+static void wifi_marauder_scene_script_stage_add_sniffpmkid_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageSniffPmkid* stage = (WifiMarauderScriptStageSniffPmkid*) malloc(sizeof(WifiMarauderScriptStageSniffPmkid));
+    stage->channel = 0;
+    stage->force_deauth = WifiMarauderScriptBooleanTrue;
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+    
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeSniffPmkid, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Sniff Pwn
+static void wifi_marauder_scene_script_stage_add_sniffpwn_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageSniffPwn* stage = (WifiMarauderScriptStageSniffPwn*) malloc(sizeof(WifiMarauderScriptStageSniffPwn));
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF;
+    
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeSniffPwn, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Beacon list
+static void wifi_marauder_scene_script_stage_add_beaconlist_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageBeaconList* stage = (WifiMarauderScriptStageBeaconList*) malloc(sizeof(WifiMarauderScriptStageBeaconList));
+    stage->ssids = NULL;
+    stage->ssid_count = 0;
+    stage->random_ssids = 0;
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_BEACON;
+
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeBeaconList, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Beacon AP
+static void wifi_marauder_scene_script_stage_add_beaconap_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageBeaconAp* stage = (WifiMarauderScriptStageBeaconAp*) malloc(sizeof(WifiMarauderScriptStageBeaconAp));
+    stage->timeout = WIFI_MARAUDER_DEFAULT_TIMEOUT_BEACON;
+
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeBeaconAp, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Exec
+static void wifi_marauder_scene_script_stage_add_exec_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageExec* stage = (WifiMarauderScriptStageExec*) malloc(sizeof(WifiMarauderScriptStageExec));
+    stage->command = NULL;
+
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeExec, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+// Delay
+static void wifi_marauder_scene_script_stage_add_delay_callback(void* context, uint32_t index) {
+    UNUSED(index);
+    WifiMarauderApp* app = context;
+
+    WifiMarauderScriptStageDelay* stage = (WifiMarauderScriptStageDelay*) malloc(sizeof(WifiMarauderScriptStageDelay));
+    stage->timeout = 0;
+
+    wifi_marauder_script_add_stage(app->script, WifiMarauderScriptStageTypeDelay, stage);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+void wifi_marauder_scene_script_stage_add_on_enter(void* context) {
+    WifiMarauderApp* app = context;
+    Submenu* submenu = app->submenu;
+    submenu_set_header(submenu, "Add stage");
+
+    int menu_index = 0;
+    submenu_add_item(submenu, "[+] Scan", menu_index++, wifi_marauder_scene_script_stage_add_scan_callback, app);
+    submenu_add_item(submenu, "[+] Select", menu_index++, wifi_marauder_scene_script_stage_add_select_callback, app);
+    submenu_add_item(submenu, "[+] Deauth", menu_index++, wifi_marauder_scene_script_stage_add_deauth_callback, app);
+    submenu_add_item(submenu, "[+] Probe", menu_index++, wifi_marauder_scene_script_stage_add_probe_callback, app);
+    submenu_add_item(submenu, "[+] Sniff RAW", menu_index++, wifi_marauder_scene_script_stage_add_sniffraw_callback, app);
+    submenu_add_item(submenu, "[+] Sniff Beacon", menu_index++, wifi_marauder_scene_script_stage_add_sniffbeacon_callback, app);
+    submenu_add_item(submenu, "[+] Sniff Deauth", menu_index++, wifi_marauder_scene_script_stage_add_sniffdeauth_callback, app);
+    submenu_add_item(submenu, "[+] Sniff Esp", menu_index++, wifi_marauder_scene_script_stage_add_sniffesp_callback, app);
+    submenu_add_item(submenu, "[+] Sniff PMKID", menu_index++, wifi_marauder_scene_script_stage_add_sniffpmkid_callback, app);
+    submenu_add_item(submenu, "[+] Sniff Pwnagotchi", menu_index++, wifi_marauder_scene_script_stage_add_sniffpwn_callback, app);
+    submenu_add_item(submenu, "[+] Beacon List", menu_index++, wifi_marauder_scene_script_stage_add_beaconlist_callback, app);
+    submenu_add_item(submenu, "[+] Beacon AP", menu_index++, wifi_marauder_scene_script_stage_add_beaconap_callback, app);
+    submenu_add_item(submenu, "[+] Custom command", menu_index++, wifi_marauder_scene_script_stage_add_exec_callback, app);
+    submenu_add_item(submenu, "[+] Delay", menu_index++, wifi_marauder_scene_script_stage_add_delay_callback, app);
+
+    submenu_set_selected_item(submenu, scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneScriptEdit));
+    view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewSubmenu);
+}
+
+bool wifi_marauder_scene_script_stage_add_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void wifi_marauder_scene_script_stage_add_on_exit(void* context) {
+    WifiMarauderApp* app = context;
+    submenu_reset(app->submenu);
+}

+ 179 - 0
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_stage_edit.c

@@ -0,0 +1,179 @@
+#include "../wifi_marauder_app_i.h"
+
+void wifi_marauder_scene_script_stage_edit_create_list_strings(WifiMarauderApp* app, char** strings, int string_count) {
+    // Deallocates the existing list
+    WifiMarauderScriptStageListItem* current_item = app->script_stage_edit_first_item;
+    while (current_item != NULL) {
+        WifiMarauderScriptStageListItem* next_item = current_item->next_item;
+        free(current_item->value);
+        free(current_item);
+        current_item = next_item;
+    }
+
+    // Create a new list with numbers
+    WifiMarauderScriptStageListItem* first_item = NULL;
+    WifiMarauderScriptStageListItem* previous_item = NULL;
+    for (int i = 0; i < string_count; i++) {        
+        WifiMarauderScriptStageListItem* item = malloc(sizeof(WifiMarauderScriptStageListItem));
+        item->value = strdup(strings[i]);
+        item->next_item = NULL;
+
+        if (previous_item == NULL) {
+            first_item = item;
+        } else {
+            previous_item->next_item = item;
+        }
+        previous_item = item;
+    }
+
+    app->script_stage_edit_first_item = first_item;
+}
+
+void wifi_marauder_scene_script_stage_edit_create_list_numbers(WifiMarauderApp* app, int* numbers, int number_count) {
+    // Deallocates the existing list
+    WifiMarauderScriptStageListItem* current_item = app->script_stage_edit_first_item;
+    while (current_item != NULL) {
+        WifiMarauderScriptStageListItem* next_item = current_item->next_item;
+        free(current_item->value);
+        free(current_item);
+        current_item = next_item;
+    }
+
+    // Create a new list with numbers
+    WifiMarauderScriptStageListItem* first_item = NULL;
+    WifiMarauderScriptStageListItem* previous_item = NULL;
+    for (int i = 0; i < number_count; i++) {
+        char number_str[32];
+        snprintf(number_str, sizeof(number_str), "%d", numbers[i]);
+        
+        WifiMarauderScriptStageListItem* item = malloc(sizeof(WifiMarauderScriptStageListItem));
+        item->value = strdup(number_str);
+        item->next_item = NULL;
+
+        if (previous_item == NULL) {
+            first_item = item;
+        } else {
+            previous_item->next_item = item;
+        }
+        previous_item = item;
+    }
+
+    app->script_stage_edit_first_item = first_item;
+}
+
+static void wifi_marauder_scene_script_stage_edit_list_enter_callback(void* context, uint32_t index) {
+    WifiMarauderApp* app = context;
+    const WifiMarauderScriptMenuItem* menu_item = &app->script_stage_menu->items[index];
+
+    // Fixed delete item
+    if (index == app->script_stage_menu->num_items) {
+        uint32_t deleted_stage_index = scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneScriptEdit);
+        if (deleted_stage_index > 0) {
+            scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptEdit, deleted_stage_index - 1);
+        }
+        WifiMarauderScriptStage* previous_stage = NULL;
+        WifiMarauderScriptStage* current_stage = app->script->first_stage;
+        uint32_t current_stage_index = 0;
+
+        while (current_stage != NULL && current_stage_index < deleted_stage_index) {
+            previous_stage = current_stage;
+            current_stage = current_stage->next_stage;
+            current_stage_index++;
+        }
+
+        // Delete the stage
+        if (current_stage != NULL) {
+            if (previous_stage != NULL) {
+                if (current_stage->next_stage != NULL) {
+                    previous_stage->next_stage = current_stage->next_stage;
+                } else {
+                    previous_stage->next_stage = NULL;
+                    app->script->last_stage = previous_stage;
+                }
+            } else {
+                if (current_stage->next_stage != NULL) {
+                    app->script->first_stage = current_stage->next_stage;
+                } else {
+                    app->script->first_stage = NULL;
+                    app->script->last_stage = NULL;
+                }
+            }
+        }
+        app->script_edit_selected_stage = NULL;
+
+        scene_manager_previous_scene(app->scene_manager);
+        return;
+    }
+
+    if (menu_item->select_callback == NULL) {
+        return;
+    }
+    if (menu_item->type == WifiMarauderScriptMenuItemTypeNumber) {
+        // Accepts user number input, assigning the value to the reference passed as a parameter
+        menu_item->select_callback(app);
+        scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptStageEdit, index);
+        app->user_input_type = WifiMarauderUserInputTypeNumber;
+        scene_manager_next_scene(app->scene_manager, WifiMarauderSceneUserInput);
+    } else if (menu_item->type == WifiMarauderScriptMenuItemTypeString) {
+        // Accepts user string input, assigning the value to the reference passed as a parameter
+        menu_item->select_callback(app);
+        scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptStageEdit, index);
+        app->user_input_type = WifiMarauderUserInputTypeString;
+        scene_manager_next_scene(app->scene_manager, WifiMarauderSceneUserInput);
+    } else if (menu_item->type == WifiMarauderScriptMenuItemTypeListString) {
+        // Accepts the strings that compose the list
+        menu_item->select_callback(app);
+        wifi_marauder_scene_script_stage_edit_create_list_strings(app, *app->script_stage_edit_strings_reference, *app->script_stage_edit_string_count_reference);
+        scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptStageEdit, index);
+        scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptStageEditList);
+    } else if (menu_item->type == WifiMarauderScriptMenuItemTypeListNumber) {
+        // Accepts the numbers that compose the list
+        menu_item->select_callback(app);
+        wifi_marauder_scene_script_stage_edit_create_list_numbers(app, *app->script_stage_edit_numbers_reference, *app->script_stage_edit_number_count_reference);
+        scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneScriptStageEdit, index);
+        scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptStageEditList);
+    }
+}
+
+void wifi_marauder_scene_script_stage_edit_on_enter(void* context) {
+    WifiMarauderApp* app = context;
+    VariableItemList* var_item_list = app->var_item_list;
+
+    variable_item_list_set_enter_callback(app->var_item_list, wifi_marauder_scene_script_stage_edit_list_enter_callback, app);
+    app->script_stage_menu = wifi_marauder_script_stage_menu_create(app->script_edit_selected_stage->type);
+
+    if (app->script_stage_menu->items != NULL) {
+        for (uint32_t i = 0; i < app->script_stage_menu->num_items; i++) {
+            WifiMarauderScriptMenuItem* stage_item = &app->script_stage_menu->items[i];
+
+            // Changes the list item to handle it in callbacks
+            VariableItem* list_item = variable_item_list_add(app->var_item_list, stage_item->name, stage_item->num_options, stage_item->change_callback, app);
+
+            variable_item_list_set_selected_item(app->var_item_list, i);
+            if (stage_item->setup_callback != NULL) {
+                stage_item->setup_callback(list_item);
+            }
+            if (stage_item->change_callback != NULL) {
+                stage_item->change_callback(list_item);
+            }
+        }
+    }
+
+    variable_item_list_add(app->var_item_list, "[-] DELETE STAGE", 0, NULL, app);
+
+    variable_item_list_set_selected_item(var_item_list, scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneScriptStageEdit));
+    view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewVarItemList);
+}
+
+bool wifi_marauder_scene_script_stage_edit_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void wifi_marauder_scene_script_stage_edit_on_exit(void* context) {
+    WifiMarauderApp* app = context;
+    wifi_marauder_script_stage_menu_free(app->script_stage_menu);
+    app->script_stage_menu = NULL;
+    variable_item_list_reset(app->var_item_list);
+}

+ 18 - 7
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c

@@ -127,6 +127,7 @@ const WifiMarauderItem items[NUM_MENU_ITEMS] = {
     {"Update", {"ota", "sd"}, 2, {"update -w", "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},
+    {"Scripts", {""}, 1, {""}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP},
     {"Save to flipper sdcard", // keep as last entry or change logic in callback below
      {""},
      1,
@@ -143,13 +144,6 @@ static void wifi_marauder_scene_start_var_list_enter_callback(void* context, uin
     furi_assert(index < NUM_MENU_ITEMS);
     const WifiMarauderItem* item = &items[index];
 
-    if(index == NUM_MENU_ITEMS - 1) {
-        // "Save to flipper sdcard" special case - start SettingsInit widget
-        view_dispatcher_send_custom_event(
-            app->view_dispatcher, WifiMarauderEventStartSettingsInit);
-        return;
-    }
-
     const int selected_option_index = app->selected_option_index[index];
     furi_assert(selected_option_index < item->num_options_menu);
     app->selected_tx_string = item->actual_commands[selected_option_index];
@@ -167,6 +161,19 @@ static void wifi_marauder_scene_start_var_list_enter_callback(void* context, uin
         return;
     }
 
+    // Select automation script
+    if(index == NUM_MENU_ITEMS - 2) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventStartScriptSelect);
+        return;
+    }
+
+    if(index == NUM_MENU_ITEMS - 1) {
+        // "Save to flipper sdcard" special case - start SettingsInit widget
+        view_dispatcher_send_custom_event(
+            app->view_dispatcher, WifiMarauderEventStartSettingsInit);
+        return;
+    }
+
     bool needs_keyboard = (item->needs_keyboard == TOGGLE_ARGS) ? (selected_option_index != 0) :
                                                                   item->needs_keyboard;
     if(needs_keyboard) {
@@ -242,6 +249,10 @@ bool wifi_marauder_scene_start_on_event(void* context, SceneManagerEvent event)
             scene_manager_set_scene_state(
                 app->scene_manager, WifiMarauderSceneStart, app->selected_menu_index);
             scene_manager_next_scene(app->scene_manager, WifiMarauderSceneLogViewer);
+        } else if(event.event == WifiMarauderEventStartScriptSelect) {
+            scene_manager_set_scene_state(
+                app->scene_manager, WifiMarauderSceneStart, app->selected_menu_index);
+            scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptSelect);
         }
         consumed = true;
     } else if(event.type == SceneManagerEventTypeTick) {

+ 131 - 0
applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_user_input.c

@@ -0,0 +1,131 @@
+#include "../wifi_marauder_app_i.h"
+
+bool wifi_marauder_scene_user_input_validator_number_callback(const char* text, FuriString* error, void* context) {
+    UNUSED(context);
+    for (int i = 0; text[i] != '\0'; i++) {
+        if (text[i] < '0' || text[i] > '9') {
+            furi_string_printf(error, "This is not\na valid\nnumber!");
+            return false;
+        }
+    }
+    return true;
+}
+
+bool wifi_marauder_scene_user_input_validator_file_callback(const char* text, FuriString* error, void* context) {
+    UNUSED(context);
+    if (strlen(text) == 0) {
+        furi_string_printf(error, "File name\ncannot be\nblank!");
+        return false;
+    }
+    return true;
+}
+
+void wifi_marauder_scene_user_input_ok_callback(void* context) {
+    WifiMarauderApp* app = context;
+
+    File* file = NULL;
+    char* file_path = NULL;
+
+    switch (app->user_input_type) {
+        // Writes the string value of the reference
+        case WifiMarauderUserInputTypeString:
+            if (app->user_input_string_reference != NULL) {
+                strncpy(*app->user_input_string_reference, app->text_input_store, strlen(app->text_input_store) + 1);
+                app->user_input_string_reference = NULL;
+            }
+            break;
+        // Writes the numerical value of the reference
+        case WifiMarauderUserInputTypeNumber:
+            if (app->user_input_number_reference != NULL) {
+                *app->user_input_number_reference = atoi(app->text_input_store);
+                app->user_input_number_reference = NULL;
+            }
+            break;
+        // Creates a file with the name entered by the user, if it does not exist
+        case WifiMarauderUserInputTypeFileName:
+            file = storage_file_alloc(app->storage);
+            // Use application directory if not specified
+            if (app->user_input_file_dir == NULL) {
+                app->user_input_file_dir = strdup(MARAUDER_APP_FOLDER);
+            }
+            if (app->user_input_file_extension != NULL) {
+                size_t file_path_len = strlen(app->user_input_file_dir) + strlen(app->text_input_store) + strlen(app->user_input_file_extension) + 3;
+                file_path = (char*)malloc(file_path_len);
+                snprintf(file_path, file_path_len, "%s/%s.%s", app->user_input_file_dir, app->text_input_store, app->user_input_file_extension);
+            } else {
+                size_t file_path_len = strlen(app->user_input_file_dir) + strlen(app->text_input_store) + 2;
+                file_path = (char*)malloc(file_path_len);
+                snprintf(file_path, file_path_len, "%s/%s", app->user_input_file_dir, app->text_input_store);
+            }
+            if (storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_NEW)) {
+                storage_file_close(file);
+            }
+            // Free memory
+            free(app->user_input_file_dir);
+            app->user_input_file_dir = NULL;
+            free(app->user_input_file_extension);
+            app->user_input_file_extension = NULL;
+            free(file_path);
+            storage_file_free(file);
+            break;
+        default:
+            break;
+    }
+
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+void wifi_marauder_scene_user_input_on_enter(void* context) {
+    WifiMarauderApp* app = context;
+
+    switch (app->user_input_type) {
+        // Loads the string value of the reference
+        case WifiMarauderUserInputTypeString:
+            text_input_set_header_text(app->text_input, "Enter value:");
+            text_input_set_validator(app->text_input, NULL, app);
+            if (app->user_input_string_reference != NULL) {
+                strncpy(app->text_input_store, *app->user_input_string_reference, strlen(*app->user_input_string_reference) + 1);
+            }
+            break;
+        // Loads the numerical value of the reference
+        case WifiMarauderUserInputTypeNumber:
+            text_input_set_header_text(app->text_input, "Enter a valid number:");
+            text_input_set_validator(app->text_input, wifi_marauder_scene_user_input_validator_number_callback, app);
+            if (app->user_input_number_reference != NULL) {
+                char number_str[32];
+                snprintf(number_str, sizeof(number_str), "%d", *app->user_input_number_reference);
+                strncpy(app->text_input_store, number_str, strlen(number_str) + 1);
+            }
+            break;
+        // File name
+        case WifiMarauderUserInputTypeFileName:
+            text_input_set_header_text(app->text_input, "Enter file name:");
+            text_input_set_validator(app->text_input, wifi_marauder_scene_user_input_validator_file_callback, app);
+            break;
+        default:
+            scene_manager_previous_scene(app->scene_manager);
+            return;
+    }
+
+    text_input_set_result_callback(
+        app->text_input,
+        wifi_marauder_scene_user_input_ok_callback,
+        app,
+        app->text_input_store,
+        WIFI_MARAUDER_TEXT_INPUT_STORE_SIZE,
+        false);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewTextInput);
+}
+
+bool wifi_marauder_scene_user_input_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void wifi_marauder_scene_user_input_on_exit(void* context) {
+    WifiMarauderApp* app = context;
+    memset(app->text_input_store, 0, sizeof(app->text_input_store));
+    text_input_reset(app->text_input);
+}

+ 3169 - 0
applications/external/wifi_marauder_companion/script/cJSON.c

@@ -0,0 +1,3169 @@
+/*
+  Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+  THE SOFTWARE.
+*/
+
+/* cJSON */
+/* JSON parser in C. */
+
+/* disable warnings about old C89 functions in MSVC */
+#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER)
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
+#ifdef __GNUC__
+#pragma GCC visibility push(default)
+#endif
+#if defined(_MSC_VER)
+#pragma warning (push)
+/* disable warning about single line comments in system headers */
+#pragma warning (disable : 4001)
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <ctype.h>
+#include <float.h>
+
+#ifdef ENABLE_LOCALES
+#include <locale.h>
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning (pop)
+#endif
+#ifdef __GNUC__
+#pragma GCC visibility pop
+#endif
+
+#include "cJSON.h"
+
+/* define our own boolean type */
+#ifdef true
+#undef true
+#endif
+#define true ((cJSON_bool)1)
+
+#ifdef false
+#undef false
+#endif
+#define false ((cJSON_bool)0)
+
+/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */
+#ifndef isinf
+#define isinf(d) (isnan((d - d)) && !isnan(d))
+#endif
+#ifndef isnan
+#define isnan(d) (d != d)
+#endif
+
+#ifndef NAN
+#ifdef _WIN32
+#define NAN sqrt(-1.0)
+#else
+#define NAN 0.0/0.0
+#endif
+#endif
+
+typedef struct {
+    const unsigned char *json;
+    size_t position;
+} error;
+static error global_error = { NULL, 0 };
+
+CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void)
+{
+    return (const char*) (global_error.json + global_error.position);
+}
+
+CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) 
+{
+    if (!cJSON_IsString(item)) 
+    {
+        return NULL;
+    }
+
+    return item->valuestring;
+}
+
+CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) 
+{
+    if (!cJSON_IsNumber(item)) 
+    {
+        return (double) NAN;
+    }
+
+    return item->valuedouble;
+}
+
+/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */
+#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 15)
+    #error cJSON.h and cJSON.c have different versions. Make sure that both have the same.
+#endif
+
+CJSON_PUBLIC(const char*) cJSON_Version(void)
+{
+    static char version[15];
+    sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);
+
+    return version;
+}
+
+/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */
+static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2)
+{
+    if ((string1 == NULL) || (string2 == NULL))
+    {
+        return 1;
+    }
+
+    if (string1 == string2)
+    {
+        return 0;
+    }
+
+    for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++)
+    {
+        if (*string1 == '\0')
+        {
+            return 0;
+        }
+    }
+
+    return tolower(*string1) - tolower(*string2);
+}
+
+typedef struct internal_hooks
+{
+    void *(CJSON_CDECL *allocate)(size_t size);
+    void (CJSON_CDECL *deallocate)(void *pointer);
+    void *(CJSON_CDECL *reallocate)(void *pointer, size_t size);
+} internal_hooks;
+
+#if defined(_MSC_VER)
+/* work around MSVC error C2322: '...' address of dllimport '...' is not static */
+static void * CJSON_CDECL internal_malloc(size_t size)
+{
+    return malloc(size);
+}
+static void CJSON_CDECL internal_free(void *pointer)
+{
+    free(pointer);
+}
+static void * CJSON_CDECL internal_realloc(void *pointer, size_t size)
+{
+    return realloc(pointer, size);
+}
+#else
+#define internal_malloc malloc
+#define internal_free free
+#define internal_realloc realloc
+#endif
+
+/* strlen of character literals resolved at compile time */
+#define static_strlen(string_literal) (sizeof(string_literal) - sizeof(""))
+
+static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc };
+
+static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks)
+{
+    size_t length = 0;
+    unsigned char *copy = NULL;
+
+    if (string == NULL)
+    {
+        return NULL;
+    }
+
+    length = strlen((const char*)string) + sizeof("");
+    copy = (unsigned char*)hooks->allocate(length);
+    if (copy == NULL)
+    {
+        return NULL;
+    }
+    memcpy(copy, string, length);
+
+    return copy;
+}
+
+CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks)
+{
+    if (hooks == NULL)
+    {
+        /* Reset hooks */
+        global_hooks.allocate = malloc;
+        global_hooks.deallocate = free;
+        global_hooks.reallocate = realloc;
+        return;
+    }
+
+    global_hooks.allocate = malloc;
+    if (hooks->malloc_fn != NULL)
+    {
+        global_hooks.allocate = hooks->malloc_fn;
+    }
+
+    global_hooks.deallocate = free;
+    if (hooks->free_fn != NULL)
+    {
+        global_hooks.deallocate = hooks->free_fn;
+    }
+
+    /* use realloc only if both free and malloc are used */
+    global_hooks.reallocate = NULL;
+    if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free))
+    {
+        global_hooks.reallocate = realloc;
+    }
+}
+
+/* Internal constructor. */
+static cJSON *cJSON_New_Item(const internal_hooks * const hooks)
+{
+    cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON));
+    if (node)
+    {
+        memset(node, '\0', sizeof(cJSON));
+    }
+
+    return node;
+}
+
+/* Delete a cJSON structure. */
+CJSON_PUBLIC(void) cJSON_Delete(cJSON *item)
+{
+    cJSON *next = NULL;
+    while (item != NULL)
+    {
+        next = item->next;
+        if (!(item->type & cJSON_IsReference) && (item->child != NULL))
+        {
+            cJSON_Delete(item->child);
+        }
+        if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))
+        {
+            global_hooks.deallocate(item->valuestring);
+        }
+        if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
+        {
+            global_hooks.deallocate(item->string);
+        }
+        global_hooks.deallocate(item);
+        item = next;
+    }
+}
+
+/* get the decimal point character of the current locale */
+static unsigned char get_decimal_point(void)
+{
+#ifdef ENABLE_LOCALES
+    struct lconv *lconv = localeconv();
+    return (unsigned char) lconv->decimal_point[0];
+#else
+    return '.';
+#endif
+}
+
+typedef struct
+{
+    const unsigned char *content;
+    size_t length;
+    size_t offset;
+    size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */
+    internal_hooks hooks;
+} parse_buffer;
+
+/* check if the given size is left to read in a given parse buffer (starting with 1) */
+#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length))
+/* check if the buffer can be accessed at the given index (starting with 0) */
+#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length))
+#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index))
+/* get a pointer to the buffer at the position */
+#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset)
+
+/* Converts an array of characters to double. Alternative implementation of strtod() */
+double string_to_double(const char* str, char** endptr) {
+    double result = 0.0;
+    int sign = 1;
+    const char* p = str;
+
+    while (isspace((unsigned char)*p)) p++;
+
+    if (*p == '-') {
+        sign = -1;
+        p++;
+    }
+    else if (*p == '+') {
+        p++;
+    }
+
+    while (isdigit((unsigned char)*p)) {
+        result = result * (double)(10) + ((double)(*p - '0'));
+        p++;
+    }
+
+    if (*p == '.') {
+        double fraction = 0.1;
+        p++;
+
+        while (isdigit((unsigned char)p[0])) {
+            fraction *= 0.1L;
+            result += (p++[0] - '0') * fraction;
+        }
+    }
+
+    if (*p == 'e' || *p == 'E') {
+        int exponent = 0;
+        int exp_sign = 1;
+        p++;
+
+        if (*p == '-') {
+            exp_sign = -1;
+            p++;
+        }
+        else if (*p == '+') {
+            p++;
+        }
+
+        while (isdigit((unsigned char)*p)) {
+            exponent = exponent * 10 + (*p - '0');
+            p++;
+        }
+
+        exponent *= exp_sign;
+        result *= pow(10, exponent);
+    }
+
+    *endptr = (char*)p;
+
+    return sign * result;
+}
+
+
+/* Parse the input text to generate a number, and populate the result into item. */
+static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer)
+{
+    double number = 0;
+    unsigned char *after_end = NULL;
+    unsigned char number_c_string[64];
+    unsigned char decimal_point = get_decimal_point();
+    size_t i = 0;
+
+    if ((input_buffer == NULL) || (input_buffer->content == NULL))
+    {
+        return false;
+    }
+
+    /* copy the number into a temporary buffer and replace '.' with the decimal point
+     * of the current locale (for string_to_double)
+     * This also takes care of '\0' not necessarily being available for marking the end of the input */
+    for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++)
+    {
+        switch (buffer_at_offset(input_buffer)[i])
+        {
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            case '+':
+            case '-':
+            case 'e':
+            case 'E':
+                number_c_string[i] = buffer_at_offset(input_buffer)[i];
+                break;
+
+            case '.':
+                number_c_string[i] = decimal_point;
+                break;
+
+            default:
+                goto loop_end;
+        }
+    }
+loop_end:
+    number_c_string[i] = '\0';
+
+    number = string_to_double((const char*)number_c_string, (char**)&after_end);
+    if (number_c_string == after_end)
+    {
+        return false; /* parse_error */
+    }
+
+    item->valuedouble = number;
+
+    /* use saturation in case of overflow */
+    if (number >= INT_MAX)
+    {
+        item->valueint = INT_MAX;
+    }
+    else if (number <= (double)INT_MIN)
+    {
+        item->valueint = INT_MIN;
+    }
+    else
+    {
+        item->valueint = (int)number;
+    }
+
+    item->type = cJSON_Number;
+
+    input_buffer->offset += (size_t)(after_end - number_c_string);
+    return true;
+}
+
+/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */
+CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number)
+{
+    if (number >= INT_MAX)
+    {
+        object->valueint = INT_MAX;
+    }
+    else if (number <= (double)INT_MIN)
+    {
+        object->valueint = INT_MIN;
+    }
+    else
+    {
+        object->valueint = (int)number;
+    }
+
+    return object->valuedouble = number;
+}
+
+CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring)
+{
+    char *copy = NULL;
+    /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */
+    if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference))
+    {
+        return NULL;
+    }
+    if (strlen(valuestring) <= strlen(object->valuestring))
+    {
+        strcpy(object->valuestring, valuestring);
+        return object->valuestring;
+    }
+    copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks);
+    if (copy == NULL)
+    {
+        return NULL;
+    }
+    if (object->valuestring != NULL)
+    {
+        cJSON_free(object->valuestring);
+    }
+    object->valuestring = copy;
+
+    return copy;
+}
+
+typedef struct
+{
+    unsigned char *buffer;
+    size_t length;
+    size_t offset;
+    size_t depth; /* current nesting depth (for formatted printing) */
+    cJSON_bool noalloc;
+    cJSON_bool format; /* is this print a formatted print */
+    internal_hooks hooks;
+} printbuffer;
+
+/* realloc printbuffer if necessary to have at least "needed" bytes more */
+static unsigned char* ensure(printbuffer * const p, size_t needed)
+{
+    unsigned char *newbuffer = NULL;
+    size_t newsize = 0;
+
+    if ((p == NULL) || (p->buffer == NULL))
+    {
+        return NULL;
+    }
+
+    if ((p->length > 0) && (p->offset >= p->length))
+    {
+        /* make sure that offset is valid */
+        return NULL;
+    }
+
+    if (needed > INT_MAX)
+    {
+        /* sizes bigger than INT_MAX are currently not supported */
+        return NULL;
+    }
+
+    needed += p->offset + 1;
+    if (needed <= p->length)
+    {
+        return p->buffer + p->offset;
+    }
+
+    if (p->noalloc) {
+        return NULL;
+    }
+
+    /* calculate new buffer size */
+    if (needed > (INT_MAX / 2))
+    {
+        /* overflow of int, use INT_MAX if possible */
+        if (needed <= INT_MAX)
+        {
+            newsize = INT_MAX;
+        }
+        else
+        {
+            return NULL;
+        }
+    }
+    else
+    {
+        newsize = needed * 2;
+    }
+
+    if (p->hooks.reallocate != NULL)
+    {
+        /* reallocate with realloc if available */
+        newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize);
+        if (newbuffer == NULL)
+        {
+            p->hooks.deallocate(p->buffer);
+            p->length = 0;
+            p->buffer = NULL;
+
+            return NULL;
+        }
+    }
+    else
+    {
+        /* otherwise reallocate manually */
+        newbuffer = (unsigned char*)p->hooks.allocate(newsize);
+        if (!newbuffer)
+        {
+            p->hooks.deallocate(p->buffer);
+            p->length = 0;
+            p->buffer = NULL;
+
+            return NULL;
+        }
+        
+        memcpy(newbuffer, p->buffer, p->offset + 1);
+        p->hooks.deallocate(p->buffer);
+    }
+    p->length = newsize;
+    p->buffer = newbuffer;
+
+    return newbuffer + p->offset;
+}
+
+/* calculate the new length of the string in a printbuffer and update the offset */
+static void update_offset(printbuffer * const buffer)
+{
+    const unsigned char *buffer_pointer = NULL;
+    if ((buffer == NULL) || (buffer->buffer == NULL))
+    {
+        return;
+    }
+    buffer_pointer = buffer->buffer + buffer->offset;
+
+    buffer->offset += strlen((const char*)buffer_pointer);
+}
+
+/* securely comparison of floating-point variables */
+static cJSON_bool compare_double(double a, double b)
+{
+    double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b);
+    return (fabs(a - b) <= maxVal * DBL_EPSILON);
+}
+
+/* Render the number nicely from the given item into a string. */
+static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer)
+{
+    unsigned char *output_pointer = NULL;
+    double d = item->valuedouble;
+    int length = 0;
+    size_t i = 0;
+    unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */
+    unsigned char decimal_point = get_decimal_point();
+    double test = 0.0;
+
+    if (output_buffer == NULL)
+    {
+        return false;
+    }
+
+    /* This checks for NaN and Infinity */
+    if (isnan(d) || isinf(d))
+    {
+        length = snprintf((char*)number_buffer, sizeof(number_buffer), "null");
+    }
+    else
+    {
+        /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */
+        length = snprintf((char*)number_buffer, sizeof(number_buffer), "%1.15g", d);
+
+        /* Check whether the original double can be recovered */
+        if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d))
+        {
+            /* If not, print with 17 decimal places of precision */
+            length = snprintf((char*)number_buffer, sizeof(number_buffer), "%1.17g", d);
+        }
+    }
+
+    /* sprintf failed or buffer overrun occurred */
+    if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))
+    {
+        return false;
+    }
+
+    /* reserve appropriate space in the output */
+    output_pointer = ensure(output_buffer, (size_t)length + sizeof(""));
+    if (output_pointer == NULL)
+    {
+        return false;
+    }
+
+    /* copy the printed number to the output and replace locale
+     * dependent decimal point with '.' */
+    for (i = 0; i < ((size_t)length); i++)
+    {
+        if (number_buffer[i] == decimal_point)
+        {
+            output_pointer[i] = '.';
+            continue;
+        }
+
+        output_pointer[i] = number_buffer[i];
+    }
+    output_pointer[i] = '\0';
+
+    output_buffer->offset += (size_t)length;
+
+    return true;
+}
+
+/* parse 4 digit hexadecimal number */
+static unsigned parse_hex4(const unsigned char * const input)
+{
+    unsigned int h = 0;
+    size_t i = 0;
+
+    for (i = 0; i < 4; i++)
+    {
+        /* parse digit */
+        if ((input[i] >= '0') && (input[i] <= '9'))
+        {
+            h += (unsigned int) input[i] - '0';
+        }
+        else if ((input[i] >= 'A') && (input[i] <= 'F'))
+        {
+            h += (unsigned int) 10 + input[i] - 'A';
+        }
+        else if ((input[i] >= 'a') && (input[i] <= 'f'))
+        {
+            h += (unsigned int) 10 + input[i] - 'a';
+        }
+        else /* invalid */
+        {
+            return 0;
+        }
+
+        if (i < 3)
+        {
+            /* shift left to make place for the next nibble */
+            h = h << 4;
+        }
+    }
+
+    return h;
+}
+
+/* converts a UTF-16 literal to UTF-8
+ * A literal can be one or two sequences of the form \uXXXX */
+static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer)
+{
+    long unsigned int codepoint = 0;
+    unsigned int first_code = 0;
+    const unsigned char *first_sequence = input_pointer;
+    unsigned char utf8_length = 0;
+    unsigned char utf8_position = 0;
+    unsigned char sequence_length = 0;
+    unsigned char first_byte_mark = 0;
+
+    if ((input_end - first_sequence) < 6)
+    {
+        /* input ends unexpectedly */
+        goto fail;
+    }
+
+    /* get the first utf16 sequence */
+    first_code = parse_hex4(first_sequence + 2);
+
+    /* check that the code is valid */
+    if (((first_code >= 0xDC00) && (first_code <= 0xDFFF)))
+    {
+        goto fail;
+    }
+
+    /* UTF16 surrogate pair */
+    if ((first_code >= 0xD800) && (first_code <= 0xDBFF))
+    {
+        const unsigned char *second_sequence = first_sequence + 6;
+        unsigned int second_code = 0;
+        sequence_length = 12; /* \uXXXX\uXXXX */
+
+        if ((input_end - second_sequence) < 6)
+        {
+            /* input ends unexpectedly */
+            goto fail;
+        }
+
+        if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u'))
+        {
+            /* missing second half of the surrogate pair */
+            goto fail;
+        }
+
+        /* get the second utf16 sequence */
+        second_code = parse_hex4(second_sequence + 2);
+        /* check that the code is valid */
+        if ((second_code < 0xDC00) || (second_code > 0xDFFF))
+        {
+            /* invalid second half of the surrogate pair */
+            goto fail;
+        }
+
+
+        /* calculate the unicode codepoint from the surrogate pair */
+        codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF));
+    }
+    else
+    {
+        sequence_length = 6; /* \uXXXX */
+        codepoint = first_code;
+    }
+
+    /* encode as UTF-8
+     * takes at maximum 4 bytes to encode:
+     * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
+    if (codepoint < 0x80)
+    {
+        /* normal ascii, encoding 0xxxxxxx */
+        utf8_length = 1;
+    }
+    else if (codepoint < 0x800)
+    {
+        /* two bytes, encoding 110xxxxx 10xxxxxx */
+        utf8_length = 2;
+        first_byte_mark = 0xC0; /* 11000000 */
+    }
+    else if (codepoint < 0x10000)
+    {
+        /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */
+        utf8_length = 3;
+        first_byte_mark = 0xE0; /* 11100000 */
+    }
+    else if (codepoint <= 0x10FFFF)
+    {
+        /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */
+        utf8_length = 4;
+        first_byte_mark = 0xF0; /* 11110000 */
+    }
+    else
+    {
+        /* invalid unicode codepoint */
+        goto fail;
+    }
+
+    /* encode as utf8 */
+    for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--)
+    {
+        /* 10xxxxxx */
+        (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF);
+        codepoint >>= 6;
+    }
+    /* encode first byte */
+    if (utf8_length > 1)
+    {
+        (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF);
+    }
+    else
+    {
+        (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F);
+    }
+
+    *output_pointer += utf8_length;
+
+    return sequence_length;
+
+fail:
+    return 0;
+}
+
+/* Parse the input text into an unescaped cinput, and populate item. */
+static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer)
+{
+    const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1;
+    const unsigned char *input_end = buffer_at_offset(input_buffer) + 1;
+    unsigned char *output_pointer = NULL;
+    unsigned char *output = NULL;
+
+    /* not a string */
+    if (buffer_at_offset(input_buffer)[0] != '\"')
+    {
+        goto fail;
+    }
+
+    {
+        /* calculate approximate size of the output (overestimate) */
+        size_t allocation_length = 0;
+        size_t skipped_bytes = 0;
+        while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"'))
+        {
+            /* is escape sequence */
+            if (input_end[0] == '\\')
+            {
+                if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length)
+                {
+                    /* prevent buffer overflow when last input character is a backslash */
+                    goto fail;
+                }
+                skipped_bytes++;
+                input_end++;
+            }
+            input_end++;
+        }
+        if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"'))
+        {
+            goto fail; /* string ended unexpectedly */
+        }
+
+        /* This is at most how much we need for the output */
+        allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes;
+        output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof(""));
+        if (output == NULL)
+        {
+            goto fail; /* allocation failure */
+        }
+    }
+
+    output_pointer = output;
+    /* loop through the string literal */
+    while (input_pointer < input_end)
+    {
+        if (*input_pointer != '\\')
+        {
+            *output_pointer++ = *input_pointer++;
+        }
+        /* escape sequence */
+        else
+        {
+            unsigned char sequence_length = 2;
+            if ((input_end - input_pointer) < 1)
+            {
+                goto fail;
+            }
+
+            switch (input_pointer[1])
+            {
+                case 'b':
+                    *output_pointer++ = '\b';
+                    break;
+                case 'f':
+                    *output_pointer++ = '\f';
+                    break;
+                case 'n':
+                    *output_pointer++ = '\n';
+                    break;
+                case 'r':
+                    *output_pointer++ = '\r';
+                    break;
+                case 't':
+                    *output_pointer++ = '\t';
+                    break;
+                case '\"':
+                case '\\':
+                case '/':
+                    *output_pointer++ = input_pointer[1];
+                    break;
+
+                /* UTF-16 literal */
+                case 'u':
+                    sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer);
+                    if (sequence_length == 0)
+                    {
+                        /* failed to convert UTF16-literal to UTF-8 */
+                        goto fail;
+                    }
+                    break;
+
+                default:
+                    goto fail;
+            }
+            input_pointer += sequence_length;
+        }
+    }
+
+    /* zero terminate the output */
+    *output_pointer = '\0';
+
+    item->type = cJSON_String;
+    item->valuestring = (char*)output;
+
+    input_buffer->offset = (size_t) (input_end - input_buffer->content);
+    input_buffer->offset++;
+
+    return true;
+
+fail:
+    if (output != NULL)
+    {
+        input_buffer->hooks.deallocate(output);
+    }
+
+    if (input_pointer != NULL)
+    {
+        input_buffer->offset = (size_t)(input_pointer - input_buffer->content);
+    }
+
+    return false;
+}
+
+/* Render the cstring provided to an escaped version that can be printed. */
+static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer)
+{
+    const unsigned char *input_pointer = NULL;
+    unsigned char *output = NULL;
+    unsigned char *output_pointer = NULL;
+    size_t output_length = 0;
+    /* numbers of additional characters needed for escaping */
+    size_t escape_characters = 0;
+
+    if (output_buffer == NULL)
+    {
+        return false;
+    }
+
+    /* empty string */
+    if (input == NULL)
+    {
+        output = ensure(output_buffer, sizeof("\"\""));
+        if (output == NULL)
+        {
+            return false;
+        }
+        strcpy((char*)output, "\"\"");
+
+        return true;
+    }
+
+    /* set "flag" to 1 if something needs to be escaped */
+    for (input_pointer = input; *input_pointer; input_pointer++)
+    {
+        switch (*input_pointer)
+        {
+            case '\"':
+            case '\\':
+            case '\b':
+            case '\f':
+            case '\n':
+            case '\r':
+            case '\t':
+                /* one character escape sequence */
+                escape_characters++;
+                break;
+            default:
+                if (*input_pointer < 32)
+                {
+                    /* UTF-16 escape sequence uXXXX */
+                    escape_characters += 5;
+                }
+                break;
+        }
+    }
+    output_length = (size_t)(input_pointer - input) + escape_characters;
+
+    output = ensure(output_buffer, output_length + sizeof("\"\""));
+    if (output == NULL)
+    {
+        return false;
+    }
+
+    /* no characters have to be escaped */
+    if (escape_characters == 0)
+    {
+        output[0] = '\"';
+        memcpy(output + 1, input, output_length);
+        output[output_length + 1] = '\"';
+        output[output_length + 2] = '\0';
+
+        return true;
+    }
+
+    output[0] = '\"';
+    output_pointer = output + 1;
+    /* copy the string */
+    for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++)
+    {
+        if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\'))
+        {
+            /* normal character, copy */
+            *output_pointer = *input_pointer;
+        }
+        else
+        {
+            /* character needs to be escaped */
+            *output_pointer++ = '\\';
+            switch (*input_pointer)
+            {
+                case '\\':
+                    *output_pointer = '\\';
+                    break;
+                case '\"':
+                    *output_pointer = '\"';
+                    break;
+                case '\b':
+                    *output_pointer = 'b';
+                    break;
+                case '\f':
+                    *output_pointer = 'f';
+                    break;
+                case '\n':
+                    *output_pointer = 'n';
+                    break;
+                case '\r':
+                    *output_pointer = 'r';
+                    break;
+                case '\t':
+                    *output_pointer = 't';
+                    break;
+                default:
+                    /* escape and print as unicode codepoint */
+                    snprintf((char*)output_pointer, 6, "u%04x", *input_pointer);
+                    output_pointer += 4;
+                    break;
+            }
+        }
+    }
+    output[output_length + 1] = '\"';
+    output[output_length + 2] = '\0';
+
+    return true;
+}
+
+/* Invoke print_string_ptr (which is useful) on an item. */
+static cJSON_bool print_string(const cJSON * const item, printbuffer * const p)
+{
+    return print_string_ptr((unsigned char*)item->valuestring, p);
+}
+
+/* Predeclare these prototypes. */
+static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer);
+static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer);
+static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer);
+static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer);
+static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer);
+static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer);
+
+/* Utility to jump whitespace and cr/lf */
+static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer)
+{
+    if ((buffer == NULL) || (buffer->content == NULL))
+    {
+        return NULL;
+    }
+
+    if (cannot_access_at_index(buffer, 0))
+    {
+        return buffer;
+    }
+
+    while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32))
+    {
+       buffer->offset++;
+    }
+
+    if (buffer->offset == buffer->length)
+    {
+        buffer->offset--;
+    }
+
+    return buffer;
+}
+
+/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */
+static parse_buffer *skip_utf8_bom(parse_buffer * const buffer)
+{
+    if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0))
+    {
+        return NULL;
+    }
+
+    if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0))
+    {
+        buffer->offset += 3;
+    }
+
+    return buffer;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated)
+{
+    size_t buffer_length;
+
+    if (NULL == value)
+    {
+        return NULL;
+    }
+
+    /* Adding null character size due to require_null_terminated. */
+    buffer_length = strlen(value) + sizeof("");
+
+    return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated);
+}
+
+/* Parse an object - create a new root, and populate. */
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated)
+{
+    parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } };
+    cJSON *item = NULL;
+
+    /* reset error position */
+    global_error.json = NULL;
+    global_error.position = 0;
+
+    if (value == NULL || 0 == buffer_length)
+    {
+        goto fail;
+    }
+
+    buffer.content = (const unsigned char*)value;
+    buffer.length = buffer_length; 
+    buffer.offset = 0;
+    buffer.hooks = global_hooks;
+
+    item = cJSON_New_Item(&global_hooks);
+    if (item == NULL) /* memory fail */
+    {
+        goto fail;
+    }
+
+    if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer))))
+    {
+        /* parse failure. ep is set. */
+        goto fail;
+    }
+
+    /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */
+    if (require_null_terminated)
+    {
+        buffer_skip_whitespace(&buffer);
+        if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0')
+        {
+            goto fail;
+        }
+    }
+    if (return_parse_end)
+    {
+        *return_parse_end = (const char*)buffer_at_offset(&buffer);
+    }
+
+    return item;
+
+fail:
+    if (item != NULL)
+    {
+        cJSON_Delete(item);
+    }
+
+    if (value != NULL)
+    {
+        error local_error;
+        local_error.json = (const unsigned char*)value;
+        local_error.position = 0;
+
+        if (buffer.offset < buffer.length)
+        {
+            local_error.position = buffer.offset;
+        }
+        else if (buffer.length > 0)
+        {
+            local_error.position = buffer.length - 1;
+        }
+
+        if (return_parse_end != NULL)
+        {
+            *return_parse_end = (const char*)local_error.json + local_error.position;
+        }
+
+        global_error = local_error;
+    }
+
+    return NULL;
+}
+
+/* Default options for cJSON_Parse */
+CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value)
+{
+    return cJSON_ParseWithOpts(value, 0, 0);
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length)
+{
+    return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0);
+}
+
+#define cjson_min(a, b) (((a) < (b)) ? (a) : (b))
+
+static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks)
+{
+    static const size_t default_buffer_size = 256;
+    printbuffer buffer[1];
+    unsigned char *printed = NULL;
+
+    memset(buffer, 0, sizeof(buffer));
+
+    /* create buffer */
+    buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size);
+    buffer->length = default_buffer_size;
+    buffer->format = format;
+    buffer->hooks = *hooks;
+    if (buffer->buffer == NULL)
+    {
+        goto fail;
+    }
+
+    /* print the value */
+    if (!print_value(item, buffer))
+    {
+        goto fail;
+    }
+    update_offset(buffer);
+
+    /* check if reallocate is available */
+    if (hooks->reallocate != NULL)
+    {
+        printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1);
+        if (printed == NULL) {
+            goto fail;
+        }
+        buffer->buffer = NULL;
+    }
+    else /* otherwise copy the JSON over to a new buffer */
+    {
+        printed = (unsigned char*) hooks->allocate(buffer->offset + 1);
+        if (printed == NULL)
+        {
+            goto fail;
+        }
+        memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1));
+        printed[buffer->offset] = '\0'; /* just to be sure */
+
+        /* free the buffer */
+        hooks->deallocate(buffer->buffer);
+    }
+
+    return printed;
+
+fail:
+    if (buffer->buffer != NULL)
+    {
+        hooks->deallocate(buffer->buffer);
+    }
+
+    if (printed != NULL)
+    {
+        hooks->deallocate(printed);
+    }
+
+    return NULL;
+}
+
+/* Render a cJSON item/entity/structure to text. */
+CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)
+{
+    return (char*)print(item, true, &global_hooks);
+}
+
+CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item)
+{
+    return (char*)print(item, false, &global_hooks);
+}
+
+CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)
+{
+    printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };
+
+    if (prebuffer < 0)
+    {
+        return NULL;
+    }
+
+    p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer);
+    if (!p.buffer)
+    {
+        return NULL;
+    }
+
+    p.length = (size_t)prebuffer;
+    p.offset = 0;
+    p.noalloc = false;
+    p.format = fmt;
+    p.hooks = global_hooks;
+
+    if (!print_value(item, &p))
+    {
+        global_hooks.deallocate(p.buffer);
+        return NULL;
+    }
+
+    return (char*)p.buffer;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format)
+{
+    printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };
+
+    if ((length < 0) || (buffer == NULL))
+    {
+        return false;
+    }
+
+    p.buffer = (unsigned char*)buffer;
+    p.length = (size_t)length;
+    p.offset = 0;
+    p.noalloc = true;
+    p.format = format;
+    p.hooks = global_hooks;
+
+    return print_value(item, &p);
+}
+
+/* Parser core - when encountering text, process appropriately. */
+static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer)
+{
+    if ((input_buffer == NULL) || (input_buffer->content == NULL))
+    {
+        return false; /* no input */
+    }
+
+    /* parse the different types of values */
+    /* null */
+    if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0))
+    {
+        item->type = cJSON_NULL;
+        input_buffer->offset += 4;
+        return true;
+    }
+    /* false */
+    if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0))
+    {
+        item->type = cJSON_False;
+        input_buffer->offset += 5;
+        return true;
+    }
+    /* true */
+    if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0))
+    {
+        item->type = cJSON_True;
+        item->valueint = 1;
+        input_buffer->offset += 4;
+        return true;
+    }
+    /* string */
+    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"'))
+    {
+        return parse_string(item, input_buffer);
+    }
+    /* number */
+    if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9'))))
+    {
+        return parse_number(item, input_buffer);
+    }
+    /* array */
+    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '['))
+    {
+        return parse_array(item, input_buffer);
+    }
+    /* object */
+    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{'))
+    {
+        return parse_object(item, input_buffer);
+    }
+
+    return false;
+}
+
+/* Render a value to text. */
+static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer)
+{
+    unsigned char *output = NULL;
+
+    if ((item == NULL) || (output_buffer == NULL))
+    {
+        return false;
+    }
+
+    switch ((item->type) & 0xFF)
+    {
+        case cJSON_NULL:
+            output = ensure(output_buffer, 5);
+            if (output == NULL)
+            {
+                return false;
+            }
+            strcpy((char*)output, "null");
+            return true;
+
+        case cJSON_False:
+            output = ensure(output_buffer, 6);
+            if (output == NULL)
+            {
+                return false;
+            }
+            strcpy((char*)output, "false");
+            return true;
+
+        case cJSON_True:
+            output = ensure(output_buffer, 5);
+            if (output == NULL)
+            {
+                return false;
+            }
+            strcpy((char*)output, "true");
+            return true;
+
+        case cJSON_Number:
+            return print_number(item, output_buffer);
+
+        case cJSON_Raw:
+        {
+            size_t raw_length = 0;
+            if (item->valuestring == NULL)
+            {
+                return false;
+            }
+
+            raw_length = strlen(item->valuestring) + sizeof("");
+            output = ensure(output_buffer, raw_length);
+            if (output == NULL)
+            {
+                return false;
+            }
+            memcpy(output, item->valuestring, raw_length);
+            return true;
+        }
+
+        case cJSON_String:
+            return print_string(item, output_buffer);
+
+        case cJSON_Array:
+            return print_array(item, output_buffer);
+
+        case cJSON_Object:
+            return print_object(item, output_buffer);
+
+        default:
+            return false;
+    }
+}
+
+/* Build an array from input text. */
+static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer)
+{
+    cJSON *head = NULL; /* head of the linked list */
+    cJSON *current_item = NULL;
+
+    if (input_buffer->depth >= CJSON_NESTING_LIMIT)
+    {
+        return false; /* to deeply nested */
+    }
+    input_buffer->depth++;
+
+    if (buffer_at_offset(input_buffer)[0] != '[')
+    {
+        /* not an array */
+        goto fail;
+    }
+
+    input_buffer->offset++;
+    buffer_skip_whitespace(input_buffer);
+    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']'))
+    {
+        /* empty array */
+        goto success;
+    }
+
+    /* check if we skipped to the end of the buffer */
+    if (cannot_access_at_index(input_buffer, 0))
+    {
+        input_buffer->offset--;
+        goto fail;
+    }
+
+    /* step back to character in front of the first element */
+    input_buffer->offset--;
+    /* loop through the comma separated array elements */
+    do
+    {
+        /* allocate next item */
+        cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));
+        if (new_item == NULL)
+        {
+            goto fail; /* allocation failure */
+        }
+
+        /* attach next item to list */
+        if (head == NULL)
+        {
+            /* start the linked list */
+            current_item = head = new_item;
+        }
+        else
+        {
+            /* add to the end and advance */
+            current_item->next = new_item;
+            new_item->prev = current_item;
+            current_item = new_item;
+        }
+
+        /* parse next value */
+        input_buffer->offset++;
+        buffer_skip_whitespace(input_buffer);
+        if (!parse_value(current_item, input_buffer))
+        {
+            goto fail; /* failed to parse value */
+        }
+        buffer_skip_whitespace(input_buffer);
+    }
+    while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));
+
+    if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']')
+    {
+        goto fail; /* expected end of array */
+    }
+
+success:
+    input_buffer->depth--;
+
+    if (head != NULL) {
+        head->prev = current_item;
+    }
+
+    item->type = cJSON_Array;
+    item->child = head;
+
+    input_buffer->offset++;
+
+    return true;
+
+fail:
+    if (head != NULL)
+    {
+        cJSON_Delete(head);
+    }
+
+    return false;
+}
+
+/* Render an array to text */
+static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer)
+{
+    unsigned char *output_pointer = NULL;
+    size_t length = 0;
+    cJSON *current_element = item->child;
+
+    if (output_buffer == NULL)
+    {
+        return false;
+    }
+
+    /* Compose the output array. */
+    /* opening square bracket */
+    output_pointer = ensure(output_buffer, 1);
+    if (output_pointer == NULL)
+    {
+        return false;
+    }
+
+    *output_pointer = '[';
+    output_buffer->offset++;
+    output_buffer->depth++;
+
+    while (current_element != NULL)
+    {
+        if (!print_value(current_element, output_buffer))
+        {
+            return false;
+        }
+        update_offset(output_buffer);
+        if (current_element->next)
+        {
+            length = (size_t) (output_buffer->format ? 2 : 1);
+            output_pointer = ensure(output_buffer, length + 1);
+            if (output_pointer == NULL)
+            {
+                return false;
+            }
+            *output_pointer++ = ',';
+            if(output_buffer->format)
+            {
+                *output_pointer++ = ' ';
+            }
+            *output_pointer = '\0';
+            output_buffer->offset += length;
+        }
+        current_element = current_element->next;
+    }
+
+    output_pointer = ensure(output_buffer, 2);
+    if (output_pointer == NULL)
+    {
+        return false;
+    }
+    *output_pointer++ = ']';
+    *output_pointer = '\0';
+    output_buffer->depth--;
+
+    return true;
+}
+
+/* Build an object from the text. */
+static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer)
+{
+    cJSON *head = NULL; /* linked list head */
+    cJSON *current_item = NULL;
+
+    if (input_buffer->depth >= CJSON_NESTING_LIMIT)
+    {
+        return false; /* to deeply nested */
+    }
+    input_buffer->depth++;
+
+    if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{'))
+    {
+        goto fail; /* not an object */
+    }
+
+    input_buffer->offset++;
+    buffer_skip_whitespace(input_buffer);
+    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}'))
+    {
+        goto success; /* empty object */
+    }
+
+    /* check if we skipped to the end of the buffer */
+    if (cannot_access_at_index(input_buffer, 0))
+    {
+        input_buffer->offset--;
+        goto fail;
+    }
+
+    /* step back to character in front of the first element */
+    input_buffer->offset--;
+    /* loop through the comma separated array elements */
+    do
+    {
+        /* allocate next item */
+        cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));
+        if (new_item == NULL)
+        {
+            goto fail; /* allocation failure */
+        }
+
+        /* attach next item to list */
+        if (head == NULL)
+        {
+            /* start the linked list */
+            current_item = head = new_item;
+        }
+        else
+        {
+            /* add to the end and advance */
+            current_item->next = new_item;
+            new_item->prev = current_item;
+            current_item = new_item;
+        }
+
+        /* parse the name of the child */
+        input_buffer->offset++;
+        buffer_skip_whitespace(input_buffer);
+        if (!parse_string(current_item, input_buffer))
+        {
+            goto fail; /* failed to parse name */
+        }
+        buffer_skip_whitespace(input_buffer);
+
+        /* swap valuestring and string, because we parsed the name */
+        current_item->string = current_item->valuestring;
+        current_item->valuestring = NULL;
+
+        if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':'))
+        {
+            goto fail; /* invalid object */
+        }
+
+        /* parse the value */
+        input_buffer->offset++;
+        buffer_skip_whitespace(input_buffer);
+        if (!parse_value(current_item, input_buffer))
+        {
+            goto fail; /* failed to parse value */
+        }
+        buffer_skip_whitespace(input_buffer);
+    }
+    while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));
+
+    if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}'))
+    {
+        goto fail; /* expected end of object */
+    }
+
+success:
+    input_buffer->depth--;
+
+    if (head != NULL) {
+        head->prev = current_item;
+    }
+
+    item->type = cJSON_Object;
+    item->child = head;
+
+    input_buffer->offset++;
+    return true;
+
+fail:
+    if (head != NULL)
+    {
+        cJSON_Delete(head);
+    }
+
+    return false;
+}
+
+/* Render an object to text. */
+static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer)
+{
+    unsigned char *output_pointer = NULL;
+    size_t length = 0;
+    cJSON *current_item = item->child;
+
+    if (output_buffer == NULL)
+    {
+        return false;
+    }
+
+    /* Compose the output: */
+    length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */
+    output_pointer = ensure(output_buffer, length + 1);
+    if (output_pointer == NULL)
+    {
+        return false;
+    }
+
+    *output_pointer++ = '{';
+    output_buffer->depth++;
+    if (output_buffer->format)
+    {
+        *output_pointer++ = '\n';
+    }
+    output_buffer->offset += length;
+
+    while (current_item)
+    {
+        if (output_buffer->format)
+        {
+            size_t i;
+            output_pointer = ensure(output_buffer, output_buffer->depth);
+            if (output_pointer == NULL)
+            {
+                return false;
+            }
+            for (i = 0; i < output_buffer->depth; i++)
+            {
+                *output_pointer++ = '\t';
+            }
+            output_buffer->offset += output_buffer->depth;
+        }
+
+        /* print key */
+        if (!print_string_ptr((unsigned char*)current_item->string, output_buffer))
+        {
+            return false;
+        }
+        update_offset(output_buffer);
+
+        length = (size_t) (output_buffer->format ? 2 : 1);
+        output_pointer = ensure(output_buffer, length);
+        if (output_pointer == NULL)
+        {
+            return false;
+        }
+        *output_pointer++ = ':';
+        if (output_buffer->format)
+        {
+            *output_pointer++ = '\t';
+        }
+        output_buffer->offset += length;
+
+        /* print value */
+        if (!print_value(current_item, output_buffer))
+        {
+            return false;
+        }
+        update_offset(output_buffer);
+
+        /* print comma if not last */
+        length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0));
+        output_pointer = ensure(output_buffer, length + 1);
+        if (output_pointer == NULL)
+        {
+            return false;
+        }
+        if (current_item->next)
+        {
+            *output_pointer++ = ',';
+        }
+
+        if (output_buffer->format)
+        {
+            *output_pointer++ = '\n';
+        }
+        *output_pointer = '\0';
+        output_buffer->offset += length;
+
+        current_item = current_item->next;
+    }
+
+    output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2);
+    if (output_pointer == NULL)
+    {
+        return false;
+    }
+    if (output_buffer->format)
+    {
+        size_t i;
+        for (i = 0; i < (output_buffer->depth - 1); i++)
+        {
+            *output_pointer++ = '\t';
+        }
+    }
+    *output_pointer++ = '}';
+    *output_pointer = '\0';
+    output_buffer->depth--;
+
+    return true;
+}
+
+/* Get Array size/item / object item. */
+CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array)
+{
+    cJSON *child = NULL;
+    size_t size = 0;
+
+    if (array == NULL)
+    {
+        return 0;
+    }
+
+    child = array->child;
+
+    while(child != NULL)
+    {
+        size++;
+        child = child->next;
+    }
+
+    /* FIXME: Can overflow here. Cannot be fixed without breaking the API */
+
+    return (int)size;
+}
+
+static cJSON* get_array_item(const cJSON *array, size_t index)
+{
+    cJSON *current_child = NULL;
+
+    if (array == NULL)
+    {
+        return NULL;
+    }
+
+    current_child = array->child;
+    while ((current_child != NULL) && (index > 0))
+    {
+        index--;
+        current_child = current_child->next;
+    }
+
+    return current_child;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index)
+{
+    if (index < 0)
+    {
+        return NULL;
+    }
+
+    return get_array_item(array, (size_t)index);
+}
+
+static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive)
+{
+    cJSON *current_element = NULL;
+
+    if ((object == NULL) || (name == NULL))
+    {
+        return NULL;
+    }
+
+    current_element = object->child;
+    if (case_sensitive)
+    {
+        while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0))
+        {
+            current_element = current_element->next;
+        }
+    }
+    else
+    {
+        while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0))
+        {
+            current_element = current_element->next;
+        }
+    }
+
+    if ((current_element == NULL) || (current_element->string == NULL)) {
+        return NULL;
+    }
+
+    return current_element;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string)
+{
+    return get_object_item(object, string, false);
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string)
+{
+    return get_object_item(object, string, true);
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string)
+{
+    return cJSON_GetObjectItem(object, string) ? 1 : 0;
+}
+
+/* Utility for array list handling. */
+static void suffix_object(cJSON *prev, cJSON *item)
+{
+    prev->next = item;
+    item->prev = prev;
+}
+
+/* Utility for handling references. */
+static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks)
+{
+    cJSON *reference = NULL;
+    if (item == NULL)
+    {
+        return NULL;
+    }
+
+    reference = cJSON_New_Item(hooks);
+    if (reference == NULL)
+    {
+        return NULL;
+    }
+
+    memcpy(reference, item, sizeof(cJSON));
+    reference->string = NULL;
+    reference->type |= cJSON_IsReference;
+    reference->next = reference->prev = NULL;
+    return reference;
+}
+
+static cJSON_bool add_item_to_array(cJSON *array, cJSON *item)
+{
+    cJSON *child = NULL;
+
+    if ((item == NULL) || (array == NULL) || (array == item))
+    {
+        return false;
+    }
+
+    child = array->child;
+    /*
+     * To find the last item in array quickly, we use prev in array
+     */
+    if (child == NULL)
+    {
+        /* list is empty, start new one */
+        array->child = item;
+        item->prev = item;
+        item->next = NULL;
+    }
+    else
+    {
+        /* append to the end */
+        if (child->prev)
+        {
+            suffix_object(child->prev, item);
+            array->child->prev = item;
+        }
+    }
+
+    return true;
+}
+
+/* Add item to array/object. */
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item)
+{
+    return add_item_to_array(array, item);
+}
+
+#if defined(__clang__) || (defined(__GNUC__)  && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))
+    #pragma GCC diagnostic push
+#endif
+#ifdef __GNUC__
+#pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+/* helper function to cast away const */
+static void* cast_away_const(const void* string)
+{
+    return (void*)string;
+}
+#if defined(__clang__) || (defined(__GNUC__)  && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))
+    #pragma GCC diagnostic pop
+#endif
+
+
+static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key)
+{
+    char *new_key = NULL;
+    int new_type = cJSON_Invalid;
+
+    if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item))
+    {
+        return false;
+    }
+
+    if (constant_key)
+    {
+        new_key = (char*)cast_away_const(string);
+        new_type = item->type | cJSON_StringIsConst;
+    }
+    else
+    {
+        new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks);
+        if (new_key == NULL)
+        {
+            return false;
+        }
+
+        new_type = item->type & ~cJSON_StringIsConst;
+    }
+
+    if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
+    {
+        hooks->deallocate(item->string);
+    }
+
+    item->string = new_key;
+    item->type = new_type;
+
+    return add_item_to_array(object, item);
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item)
+{
+    return add_item_to_object(object, string, item, &global_hooks, false);
+}
+
+/* Add an item to an object with constant string as key */
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item)
+{
+    return add_item_to_object(object, string, item, &global_hooks, true);
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item)
+{
+    if (array == NULL)
+    {
+        return false;
+    }
+
+    return add_item_to_array(array, create_reference(item, &global_hooks));
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item)
+{
+    if ((object == NULL) || (string == NULL))
+    {
+        return false;
+    }
+
+    return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false);
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name)
+{
+    cJSON *null = cJSON_CreateNull();
+    if (add_item_to_object(object, name, null, &global_hooks, false))
+    {
+        return null;
+    }
+
+    cJSON_Delete(null);
+    return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name)
+{
+    cJSON *true_item = cJSON_CreateTrue();
+    if (add_item_to_object(object, name, true_item, &global_hooks, false))
+    {
+        return true_item;
+    }
+
+    cJSON_Delete(true_item);
+    return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name)
+{
+    cJSON *false_item = cJSON_CreateFalse();
+    if (add_item_to_object(object, name, false_item, &global_hooks, false))
+    {
+        return false_item;
+    }
+
+    cJSON_Delete(false_item);
+    return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean)
+{
+    cJSON *bool_item = cJSON_CreateBool(boolean);
+    if (add_item_to_object(object, name, bool_item, &global_hooks, false))
+    {
+        return bool_item;
+    }
+
+    cJSON_Delete(bool_item);
+    return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number)
+{
+    cJSON *number_item = cJSON_CreateNumber(number);
+    if (add_item_to_object(object, name, number_item, &global_hooks, false))
+    {
+        return number_item;
+    }
+
+    cJSON_Delete(number_item);
+    return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string)
+{
+    cJSON *string_item = cJSON_CreateString(string);
+    if (add_item_to_object(object, name, string_item, &global_hooks, false))
+    {
+        return string_item;
+    }
+
+    cJSON_Delete(string_item);
+    return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw)
+{
+    cJSON *raw_item = cJSON_CreateRaw(raw);
+    if (add_item_to_object(object, name, raw_item, &global_hooks, false))
+    {
+        return raw_item;
+    }
+
+    cJSON_Delete(raw_item);
+    return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name)
+{
+    cJSON *object_item = cJSON_CreateObject();
+    if (add_item_to_object(object, name, object_item, &global_hooks, false))
+    {
+        return object_item;
+    }
+
+    cJSON_Delete(object_item);
+    return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name)
+{
+    cJSON *array = cJSON_CreateArray();
+    if (add_item_to_object(object, name, array, &global_hooks, false))
+    {
+        return array;
+    }
+
+    cJSON_Delete(array);
+    return NULL;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item)
+{
+    if ((parent == NULL) || (item == NULL))
+    {
+        return NULL;
+    }
+
+    if (item != parent->child)
+    {
+        /* not the first element */
+        item->prev->next = item->next;
+    }
+    if (item->next != NULL)
+    {
+        /* not the last element */
+        item->next->prev = item->prev;
+    }
+
+    if (item == parent->child)
+    {
+        /* first element */
+        parent->child = item->next;
+    }
+    else if (item->next == NULL)
+    {
+        /* last element */
+        parent->child->prev = item->prev;
+    }
+
+    /* make sure the detached item doesn't point anywhere anymore */
+    item->prev = NULL;
+    item->next = NULL;
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which)
+{
+    if (which < 0)
+    {
+        return NULL;
+    }
+
+    return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which));
+}
+
+CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which)
+{
+    cJSON_Delete(cJSON_DetachItemFromArray(array, which));
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string)
+{
+    cJSON *to_detach = cJSON_GetObjectItem(object, string);
+
+    return cJSON_DetachItemViaPointer(object, to_detach);
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string)
+{
+    cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string);
+
+    return cJSON_DetachItemViaPointer(object, to_detach);
+}
+
+CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string)
+{
+    cJSON_Delete(cJSON_DetachItemFromObject(object, string));
+}
+
+CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string)
+{
+    cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string));
+}
+
+/* Replace array/object items with new ones. */
+CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem)
+{
+    cJSON *after_inserted = NULL;
+
+    if (which < 0)
+    {
+        return false;
+    }
+
+    after_inserted = get_array_item(array, (size_t)which);
+    if (after_inserted == NULL)
+    {
+        return add_item_to_array(array, newitem);
+    }
+
+    newitem->next = after_inserted;
+    newitem->prev = after_inserted->prev;
+    after_inserted->prev = newitem;
+    if (after_inserted == array->child)
+    {
+        array->child = newitem;
+    }
+    else
+    {
+        newitem->prev->next = newitem;
+    }
+    return true;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement)
+{
+    if ((parent == NULL) || (replacement == NULL) || (item == NULL))
+    {
+        return false;
+    }
+
+    if (replacement == item)
+    {
+        return true;
+    }
+
+    replacement->next = item->next;
+    replacement->prev = item->prev;
+
+    if (replacement->next != NULL)
+    {
+        replacement->next->prev = replacement;
+    }
+    if (parent->child == item)
+    {
+        if (parent->child->prev == parent->child)
+        {
+            replacement->prev = replacement;
+        }
+        parent->child = replacement;
+    }
+    else
+    {   /*
+         * To find the last item in array quickly, we use prev in array.
+         * We can't modify the last item's next pointer where this item was the parent's child
+         */
+        if (replacement->prev != NULL)
+        {
+            replacement->prev->next = replacement;
+        }
+        if (replacement->next == NULL)
+        {
+            parent->child->prev = replacement;
+        }
+    }
+
+    item->next = NULL;
+    item->prev = NULL;
+    cJSON_Delete(item);
+
+    return true;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem)
+{
+    if (which < 0)
+    {
+        return false;
+    }
+
+    return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem);
+}
+
+static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive)
+{
+    if ((replacement == NULL) || (string == NULL))
+    {
+        return false;
+    }
+
+    /* replace the name in the replacement */
+    if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL))
+    {
+        cJSON_free(replacement->string);
+    }
+    replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
+    replacement->type &= ~cJSON_StringIsConst;
+
+    return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement);
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem)
+{
+    return replace_item_in_object(object, string, newitem, false);
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem)
+{
+    return replace_item_in_object(object, string, newitem, true);
+}
+
+/* Create basic types: */
+CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if(item)
+    {
+        item->type = cJSON_NULL;
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if(item)
+    {
+        item->type = cJSON_True;
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if(item)
+    {
+        item->type = cJSON_False;
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if(item)
+    {
+        item->type = boolean ? cJSON_True : cJSON_False;
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if(item)
+    {
+        item->type = cJSON_Number;
+        item->valuedouble = num;
+
+        /* use saturation in case of overflow */
+        if (num >= INT_MAX)
+        {
+            item->valueint = INT_MAX;
+        }
+        else if (num <= (double)INT_MIN)
+        {
+            item->valueint = INT_MIN;
+        }
+        else
+        {
+            item->valueint = (int)num;
+        }
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if(item)
+    {
+        item->type = cJSON_String;
+        item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
+        if(!item->valuestring)
+        {
+            cJSON_Delete(item);
+            return NULL;
+        }
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if (item != NULL)
+    {
+        item->type = cJSON_String | cJSON_IsReference;
+        item->valuestring = (char*)cast_away_const(string);
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if (item != NULL) {
+        item->type = cJSON_Object | cJSON_IsReference;
+        item->child = (cJSON*)cast_away_const(child);
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) {
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if (item != NULL) {
+        item->type = cJSON_Array | cJSON_IsReference;
+        item->child = (cJSON*)cast_away_const(child);
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if(item)
+    {
+        item->type = cJSON_Raw;
+        item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks);
+        if(!item->valuestring)
+        {
+            cJSON_Delete(item);
+            return NULL;
+        }
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if(item)
+    {
+        item->type=cJSON_Array;
+    }
+
+    return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void)
+{
+    cJSON *item = cJSON_New_Item(&global_hooks);
+    if (item)
+    {
+        item->type = cJSON_Object;
+    }
+
+    return item;
+}
+
+/* Create Arrays: */
+CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count)
+{
+    size_t i = 0;
+    cJSON *n = NULL;
+    cJSON *p = NULL;
+    cJSON *a = NULL;
+
+    if ((count < 0) || (numbers == NULL))
+    {
+        return NULL;
+    }
+
+    a = cJSON_CreateArray();
+
+    for(i = 0; a && (i < (size_t)count); i++)
+    {
+        n = cJSON_CreateNumber(numbers[i]);
+        if (!n)
+        {
+            cJSON_Delete(a);
+            return NULL;
+        }
+        if(!i)
+        {
+            a->child = n;
+        }
+        else
+        {
+            suffix_object(p, n);
+        }
+        p = n;
+    }
+
+    if (a && a->child) {
+        a->child->prev = n;
+    }
+
+    return a;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count)
+{
+    size_t i = 0;
+    cJSON *n = NULL;
+    cJSON *p = NULL;
+    cJSON *a = NULL;
+
+    if ((count < 0) || (numbers == NULL))
+    {
+        return NULL;
+    }
+
+    a = cJSON_CreateArray();
+
+    for(i = 0; a && (i < (size_t)count); i++)
+    {
+        n = cJSON_CreateNumber((double)numbers[i]);
+        if(!n)
+        {
+            cJSON_Delete(a);
+            return NULL;
+        }
+        if(!i)
+        {
+            a->child = n;
+        }
+        else
+        {
+            suffix_object(p, n);
+        }
+        p = n;
+    }
+
+    if (a && a->child) {
+        a->child->prev = n;
+    }
+
+    return a;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count)
+{
+    size_t i = 0;
+    cJSON *n = NULL;
+    cJSON *p = NULL;
+    cJSON *a = NULL;
+
+    if ((count < 0) || (numbers == NULL))
+    {
+        return NULL;
+    }
+
+    a = cJSON_CreateArray();
+
+    for(i = 0; a && (i < (size_t)count); i++)
+    {
+        n = cJSON_CreateNumber(numbers[i]);
+        if(!n)
+        {
+            cJSON_Delete(a);
+            return NULL;
+        }
+        if(!i)
+        {
+            a->child = n;
+        }
+        else
+        {
+            suffix_object(p, n);
+        }
+        p = n;
+    }
+
+    if (a && a->child) {
+        a->child->prev = n;
+    }
+
+    return a;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count)
+{
+    size_t i = 0;
+    cJSON *n = NULL;
+    cJSON *p = NULL;
+    cJSON *a = NULL;
+
+    if ((count < 0) || (strings == NULL))
+    {
+        return NULL;
+    }
+
+    a = cJSON_CreateArray();
+
+    for (i = 0; a && (i < (size_t)count); i++)
+    {
+        n = cJSON_CreateString(strings[i]);
+        if(!n)
+        {
+            cJSON_Delete(a);
+            return NULL;
+        }
+        if(!i)
+        {
+            a->child = n;
+        }
+        else
+        {
+            suffix_object(p,n);
+        }
+        p = n;
+    }
+
+    if (a && a->child) {
+        a->child->prev = n;
+    }
+    
+    return a;
+}
+
+/* Duplication */
+CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse)
+{
+    cJSON *newitem = NULL;
+    cJSON *child = NULL;
+    cJSON *next = NULL;
+    cJSON *newchild = NULL;
+
+    /* Bail on bad ptr */
+    if (!item)
+    {
+        goto fail;
+    }
+    /* Create new item */
+    newitem = cJSON_New_Item(&global_hooks);
+    if (!newitem)
+    {
+        goto fail;
+    }
+    /* Copy over all vars */
+    newitem->type = item->type & (~cJSON_IsReference);
+    newitem->valueint = item->valueint;
+    newitem->valuedouble = item->valuedouble;
+    if (item->valuestring)
+    {
+        newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks);
+        if (!newitem->valuestring)
+        {
+            goto fail;
+        }
+    }
+    if (item->string)
+    {
+        newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks);
+        if (!newitem->string)
+        {
+            goto fail;
+        }
+    }
+    /* If non-recursive, then we're done! */
+    if (!recurse)
+    {
+        return newitem;
+    }
+    /* Walk the ->next chain for the child. */
+    child = item->child;
+    while (child != NULL)
+    {
+        newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */
+        if (!newchild)
+        {
+            goto fail;
+        }
+        if (next != NULL)
+        {
+            /* If newitem->child already set, then crosswire ->prev and ->next and move on */
+            next->next = newchild;
+            newchild->prev = next;
+            next = newchild;
+        }
+        else
+        {
+            /* Set newitem->child and move to it */
+            newitem->child = newchild;
+            next = newchild;
+        }
+        child = child->next;
+    }
+    if (newitem && newitem->child)
+    {
+        newitem->child->prev = newchild;
+    }
+
+    return newitem;
+
+fail:
+    if (newitem != NULL)
+    {
+        cJSON_Delete(newitem);
+    }
+
+    return NULL;
+}
+
+static void skip_oneline_comment(char **input)
+{
+    *input += static_strlen("//");
+
+    for (; (*input)[0] != '\0'; ++(*input))
+    {
+        if ((*input)[0] == '\n') {
+            *input += static_strlen("\n");
+            return;
+        }
+    }
+}
+
+static void skip_multiline_comment(char **input)
+{
+    *input += static_strlen("/*");
+
+    for (; (*input)[0] != '\0'; ++(*input))
+    {
+        if (((*input)[0] == '*') && ((*input)[1] == '/'))
+        {
+            *input += static_strlen("*/");
+            return;
+        }
+    }
+}
+
+static void minify_string(char **input, char **output) {
+    (*output)[0] = (*input)[0];
+    *input += static_strlen("\"");
+    *output += static_strlen("\"");
+
+
+    for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) {
+        (*output)[0] = (*input)[0];
+
+        if ((*input)[0] == '\"') {
+            (*output)[0] = '\"';
+            *input += static_strlen("\"");
+            *output += static_strlen("\"");
+            return;
+        } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) {
+            (*output)[1] = (*input)[1];
+            *input += static_strlen("\"");
+            *output += static_strlen("\"");
+        }
+    }
+}
+
+CJSON_PUBLIC(void) cJSON_Minify(char *json)
+{
+    char *into = json;
+
+    if (json == NULL)
+    {
+        return;
+    }
+
+    while (json[0] != '\0')
+    {
+        switch (json[0])
+        {
+            case ' ':
+            case '\t':
+            case '\r':
+            case '\n':
+                json++;
+                break;
+
+            case '/':
+                if (json[1] == '/')
+                {
+                    skip_oneline_comment(&json);
+                }
+                else if (json[1] == '*')
+                {
+                    skip_multiline_comment(&json);
+                } else {
+                    json++;
+                }
+                break;
+
+            case '\"':
+                minify_string(&json, (char**)&into);
+                break;
+
+            default:
+                into[0] = json[0];
+                json++;
+                into++;
+        }
+    }
+
+    /* and null-terminate. */
+    *into = '\0';
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item)
+{
+    if (item == NULL)
+    {
+        return false;
+    }
+
+    return (item->type & 0xFF) == cJSON_Invalid;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item)
+{
+    if (item == NULL)
+    {
+        return false;
+    }
+
+    return (item->type & 0xFF) == cJSON_False;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item)
+{
+    if (item == NULL)
+    {
+        return false;
+    }
+
+    return (item->type & 0xff) == cJSON_True;
+}
+
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item)
+{
+    if (item == NULL)
+    {
+        return false;
+    }
+
+    return (item->type & (cJSON_True | cJSON_False)) != 0;
+}
+CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item)
+{
+    if (item == NULL)
+    {
+        return false;
+    }
+
+    return (item->type & 0xFF) == cJSON_NULL;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item)
+{
+    if (item == NULL)
+    {
+        return false;
+    }
+
+    return (item->type & 0xFF) == cJSON_Number;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item)
+{
+    if (item == NULL)
+    {
+        return false;
+    }
+
+    return (item->type & 0xFF) == cJSON_String;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item)
+{
+    if (item == NULL)
+    {
+        return false;
+    }
+
+    return (item->type & 0xFF) == cJSON_Array;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item)
+{
+    if (item == NULL)
+    {
+        return false;
+    }
+
+    return (item->type & 0xFF) == cJSON_Object;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item)
+{
+    if (item == NULL)
+    {
+        return false;
+    }
+
+    return (item->type & 0xFF) == cJSON_Raw;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive)
+{
+    if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)))
+    {
+        return false;
+    }
+
+    /* check if type is valid */
+    switch (a->type & 0xFF)
+    {
+        case cJSON_False:
+        case cJSON_True:
+        case cJSON_NULL:
+        case cJSON_Number:
+        case cJSON_String:
+        case cJSON_Raw:
+        case cJSON_Array:
+        case cJSON_Object:
+            break;
+
+        default:
+            return false;
+    }
+
+    /* identical objects are equal */
+    if (a == b)
+    {
+        return true;
+    }
+
+    switch (a->type & 0xFF)
+    {
+        /* in these cases and equal type is enough */
+        case cJSON_False:
+        case cJSON_True:
+        case cJSON_NULL:
+            return true;
+
+        case cJSON_Number:
+            if (compare_double(a->valuedouble, b->valuedouble))
+            {
+                return true;
+            }
+            return false;
+
+        case cJSON_String:
+        case cJSON_Raw:
+            if ((a->valuestring == NULL) || (b->valuestring == NULL))
+            {
+                return false;
+            }
+            if (strcmp(a->valuestring, b->valuestring) == 0)
+            {
+                return true;
+            }
+
+            return false;
+
+        case cJSON_Array:
+        {
+            cJSON *a_element = a->child;
+            cJSON *b_element = b->child;
+
+            for (; (a_element != NULL) && (b_element != NULL);)
+            {
+                if (!cJSON_Compare(a_element, b_element, case_sensitive))
+                {
+                    return false;
+                }
+
+                a_element = a_element->next;
+                b_element = b_element->next;
+            }
+
+            /* one of the arrays is longer than the other */
+            if (a_element != b_element) {
+                return false;
+            }
+
+            return true;
+        }
+
+        case cJSON_Object:
+        {
+            cJSON *a_element = NULL;
+            cJSON *b_element = NULL;
+            cJSON_ArrayForEach(a_element, a)
+            {
+                /* TODO This has O(n^2) runtime, which is horrible! */
+                b_element = get_object_item(b, a_element->string, case_sensitive);
+                if (b_element == NULL)
+                {
+                    return false;
+                }
+
+                if (!cJSON_Compare(a_element, b_element, case_sensitive))
+                {
+                    return false;
+                }
+            }
+
+            /* doing this twice, once on a and b to prevent true comparison if a subset of b
+             * TODO: Do this the proper way, this is just a fix for now */
+            cJSON_ArrayForEach(b_element, b)
+            {
+                a_element = get_object_item(a, b_element->string, case_sensitive);
+                if (a_element == NULL)
+                {
+                    return false;
+                }
+
+                if (!cJSON_Compare(b_element, a_element, case_sensitive))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        default:
+            return false;
+    }
+}
+
+CJSON_PUBLIC(void *) cJSON_malloc(size_t size)
+{
+    return global_hooks.allocate(size);
+}
+
+CJSON_PUBLIC(void) cJSON_free(void *object)
+{
+    global_hooks.deallocate(object);
+}

+ 293 - 0
applications/external/wifi_marauder_companion/script/cJSON.h

@@ -0,0 +1,293 @@
+/*
+  Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+  THE SOFTWARE.
+*/
+
+#ifndef cJSON__h
+#define cJSON__h
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
+#define __WINDOWS__
+#endif
+
+#ifdef __WINDOWS__
+
+/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention.  For windows you have 3 define options:
+
+CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
+CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
+CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
+
+For *nix builds that support visibility attribute, you can define similar behavior by
+
+setting default visibility to hidden by adding
+-fvisibility=hidden (for gcc)
+or
+-xldscope=hidden (for sun cc)
+to CFLAGS
+
+then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
+
+*/
+
+#define CJSON_CDECL __cdecl
+#define CJSON_STDCALL __stdcall
+
+/* export symbols by default, this is necessary for copy pasting the C and header file */
+#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
+#define CJSON_EXPORT_SYMBOLS
+#endif
+
+#if defined(CJSON_HIDE_SYMBOLS)
+#define CJSON_PUBLIC(type)   type CJSON_STDCALL
+#elif defined(CJSON_EXPORT_SYMBOLS)
+#define CJSON_PUBLIC(type)   __declspec(dllexport) type CJSON_STDCALL
+#elif defined(CJSON_IMPORT_SYMBOLS)
+#define CJSON_PUBLIC(type)   __declspec(dllimport) type CJSON_STDCALL
+#endif
+#else /* !__WINDOWS__ */
+#define CJSON_CDECL
+#define CJSON_STDCALL
+
+#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
+#define CJSON_PUBLIC(type)   __attribute__((visibility("default"))) type
+#else
+#define CJSON_PUBLIC(type) type
+#endif
+#endif
+
+/* project version */
+#define CJSON_VERSION_MAJOR 1
+#define CJSON_VERSION_MINOR 7
+#define CJSON_VERSION_PATCH 15
+
+#include <stddef.h>
+
+/* cJSON Types: */
+#define cJSON_Invalid (0)
+#define cJSON_False  (1 << 0)
+#define cJSON_True   (1 << 1)
+#define cJSON_NULL   (1 << 2)
+#define cJSON_Number (1 << 3)
+#define cJSON_String (1 << 4)
+#define cJSON_Array  (1 << 5)
+#define cJSON_Object (1 << 6)
+#define cJSON_Raw    (1 << 7) /* raw json */
+
+#define cJSON_IsReference 256
+#define cJSON_StringIsConst 512
+
+/* The cJSON structure: */
+typedef struct cJSON
+{
+    /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
+    struct cJSON *next;
+    struct cJSON *prev;
+    /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
+    struct cJSON *child;
+
+    /* The type of the item, as above. */
+    int type;
+
+    /* The item's string, if type==cJSON_String  and type == cJSON_Raw */
+    char *valuestring;
+    /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
+    int valueint;
+    /* The item's number, if type==cJSON_Number */
+    double valuedouble;
+
+    /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
+    char *string;
+} cJSON;
+
+typedef struct cJSON_Hooks
+{
+      /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
+      void *(CJSON_CDECL *malloc_fn)(size_t sz);
+      void (CJSON_CDECL *free_fn)(void *ptr);
+} cJSON_Hooks;
+
+typedef int cJSON_bool;
+
+/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
+ * This is to prevent stack overflows. */
+#ifndef CJSON_NESTING_LIMIT
+#define CJSON_NESTING_LIMIT 1000
+#endif
+
+/* returns the version of cJSON as a string */
+CJSON_PUBLIC(const char*) cJSON_Version(void);
+
+/* Supply malloc, realloc and free functions to cJSON */
+CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
+
+/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
+/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
+CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
+/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
+/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
+
+/* Render a cJSON entity to text for transfer/storage. */
+CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
+/* Render a cJSON entity to text for transfer/storage without any formatting. */
+CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
+/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
+CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
+/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
+/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
+CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
+/* Delete a cJSON entity and all subentities. */
+CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
+
+/* Returns the number of items in an array (or object). */
+CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
+/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
+CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
+/* Get item "string" from object. Case insensitive. */
+CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
+CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
+CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
+/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
+CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
+
+/* Check item type and return its value */
+CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
+CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
+
+/* These functions check the type of an item */
+CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
+
+/* These calls create a cJSON item of the appropriate type. */
+CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
+CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
+CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
+CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
+CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
+CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
+/* raw json */
+CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
+CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
+CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
+
+/* Create a string where valuestring references a string so
+ * it will not be freed by cJSON_Delete */
+CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
+/* Create an object/array that only references it's elements so
+ * they will not be freed by cJSON_Delete */
+CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
+CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
+
+/* These utilities create an Array of count items.
+ * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
+CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
+CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
+CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
+CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
+
+/* Append item to the specified array/object. */
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
+/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
+ * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
+ * writing to `item->string` */
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
+/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
+
+/* Remove/Detach items from Arrays/Objects. */
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
+CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
+CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
+CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
+
+/* Update array items. */
+CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
+
+/* Duplicate a cJSON item */
+CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
+/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
+ * need to be released. With recurse!=0, it will duplicate any children connected to the item.
+ * The item->next and ->prev pointers are always zero on return from Duplicate. */
+/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
+ * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
+CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
+
+/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
+ * The input pointer json cannot point to a read-only address area, such as a string constant, 
+ * but should point to a readable and writable address area. */
+CJSON_PUBLIC(void) cJSON_Minify(char *json);
+
+/* Helper functions for creating and adding items to an object at the same time.
+ * They return the added item or NULL on failure. */
+CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
+CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
+CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
+CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
+CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
+CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
+CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
+CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
+CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
+
+/* When assigning an integer value, it needs to be propagated to valuedouble too. */
+#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
+/* helper for the cJSON_SetNumberValue macro */
+CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
+#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
+/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
+CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
+
+/* Macro for iterating over an array or object */
+#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
+
+/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
+CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
+CJSON_PUBLIC(void) cJSON_free(void *object);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 31 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu.c

@@ -0,0 +1,31 @@
+#include "wifi_marauder_script_stage_menu.h"
+
+WifiMarauderScriptStageMenu* wifi_marauder_script_stage_menu_create(WifiMarauderScriptStageType stage_type) {
+    WifiMarauderScriptStageMenu* script_stage_menu = malloc(sizeof(WifiMarauderScriptStageMenu));
+
+    switch (stage_type) {
+        #define ADD_STAGE(name, id) \
+            case WifiMarauderScriptStageType##id: \
+                wifi_marauder_script_stage_menu_##name##_load(script_stage_menu); \
+                break;
+
+        #include "wifi_marauder_script_stage_menu_config.h"
+        #undef ADD_STAGE
+    }
+    return script_stage_menu;
+}
+
+void wifi_marauder_script_stage_menu_free(WifiMarauderScriptStageMenu* stage_menu) {
+    if (stage_menu == NULL) {
+        return;
+    }
+    for (uint32_t i = 0; i < stage_menu->num_items; i++) {
+        WifiMarauderScriptMenuItem* item = &(stage_menu->items[i]);
+        for (int j = 0; j < item->num_options; j++) {
+            free(item->options[j]);
+        }
+        free(item->name);
+    }
+    free(stage_menu->items);
+    free(stage_menu);
+}

+ 40 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu.h

@@ -0,0 +1,40 @@
+#pragma once
+
+#include <gui/modules/variable_item_list.h>
+#include "../wifi_marauder_script.h"
+
+#define ITEM_EDIT_MAX_OPTIONS (12)
+
+typedef void (*VariableItemSetupCallback)(VariableItem* item);
+typedef void (*VariableItemSelectCallback)(void* context);
+
+typedef enum WifiMarauderScriptMenuItemType {
+    WifiMarauderScriptMenuItemTypeString,
+    WifiMarauderScriptMenuItemTypeNumber,
+    WifiMarauderScriptMenuItemTypeOptionsString,
+    WifiMarauderScriptMenuItemTypeOptionsNumber,
+    WifiMarauderScriptMenuItemTypeListString,
+    WifiMarauderScriptMenuItemTypeListNumber
+} WifiMarauderScriptMenuItemType;
+
+typedef struct WifiMarauderScriptMenuItem {
+    char* name;
+    WifiMarauderScriptMenuItemType type;
+    int num_options;
+    char* options[ITEM_EDIT_MAX_OPTIONS];
+    VariableItemSetupCallback setup_callback;
+    VariableItemChangeCallback change_callback;
+    VariableItemSelectCallback select_callback;
+} WifiMarauderScriptMenuItem;
+
+typedef struct WifiMarauderScriptStageMenu {
+    WifiMarauderScriptMenuItem* items;
+    uint32_t num_items;
+} WifiMarauderScriptStageMenu;
+
+#define ADD_STAGE(name, id) void wifi_marauder_script_stage_menu_##name##_load(WifiMarauderScriptStageMenu*);
+#include "wifi_marauder_script_stage_menu_config.h"
+#undef ADD_STAGE
+
+WifiMarauderScriptStageMenu* wifi_marauder_script_stage_menu_create(WifiMarauderScriptStageType stage_type);
+void wifi_marauder_script_stage_menu_free(WifiMarauderScriptStageMenu* list);

+ 28 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_beaconap.c

@@ -0,0 +1,28 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_beaconap_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageBeaconAp* 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);
+}
+
+void wifi_marauder_beaconap_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageBeaconAp* stage_beaconap = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_beaconap->timeout;
+}
+
+void wifi_marauder_script_stage_menu_beaconap_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 1;
+    stage_menu->items = malloc(1 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = "Timeout",
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_beaconap_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_beaconap_stage_timeout_select_callback
+    };
+}

+ 62 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_beaconlist.c

@@ -0,0 +1,62 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_beaconlist_stage_ssids_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageBeaconList* stage_beaconlist = app->script_edit_selected_stage->stage;
+    app->script_stage_edit_strings_reference = &stage_beaconlist->ssids;
+    app->script_stage_edit_string_count_reference = &stage_beaconlist->ssid_count;
+}
+
+void wifi_marauder_beaconlist_stage_random_ssids_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageBeaconList* stage = app->script_edit_selected_stage->stage;
+    char random_ssids_str[32];
+    snprintf(random_ssids_str, sizeof(random_ssids_str), "%d", stage->random_ssids);
+    variable_item_set_current_value_text(item, random_ssids_str);
+}
+
+void wifi_marauder_beaconlist_stage_random_ssids_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageBeaconList* stage_beaconlist = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_beaconlist->random_ssids;
+}
+
+void wifi_marauder_beaconlist_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageBeaconList* 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);
+}
+
+void wifi_marauder_beaconlist_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageBeaconList* stage_beaconlist = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_beaconlist->timeout;
+}
+
+void wifi_marauder_script_stage_menu_beaconlist_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 3;
+    stage_menu->items = malloc(3 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("SSIDs"),
+        .type = WifiMarauderScriptMenuItemTypeListString,
+        .num_options = 1,
+        .select_callback = wifi_marauder_beaconlist_stage_ssids_select_callback
+    };
+    stage_menu->items[1] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Generate random"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_beaconlist_stage_random_ssids_setup_callback,
+        .select_callback = wifi_marauder_beaconlist_stage_random_ssids_select_callback
+    };
+    stage_menu->items[2] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_beaconlist_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_beaconlist_stage_timeout_select_callback
+    };
+}

+ 14 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_config.h

@@ -0,0 +1,14 @@
+ADD_STAGE(scan, Scan)
+ADD_STAGE(select, Select)
+ADD_STAGE(deauth, Deauth)
+ADD_STAGE(probe, Probe)
+ADD_STAGE(sniffraw, SniffRaw)
+ADD_STAGE(sniffbeacon, SniffBeacon)
+ADD_STAGE(sniffdeauth, SniffDeauth)
+ADD_STAGE(sniffesp, SniffEsp)
+ADD_STAGE(sniffpmkid, SniffPmkid)
+ADD_STAGE(sniffpwn, SniffPwn)
+ADD_STAGE(beaconlist, BeaconList)
+ADD_STAGE(beaconap, BeaconAp)
+ADD_STAGE(exec, Exec)
+ADD_STAGE(delay, Delay)

+ 28 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_deauth.c

@@ -0,0 +1,28 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_deauth_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageDeauth* 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);
+}
+
+void wifi_marauder_deauth_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageDeauth* stage_deauth = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_deauth->timeout;
+}
+
+void wifi_marauder_script_stage_menu_deauth_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 1;
+    stage_menu->items = malloc(1 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_deauth_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_deauth_stage_timeout_select_callback
+    };
+}

+ 28 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_delay.c

@@ -0,0 +1,28 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_delay_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageDelay* 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);
+}
+
+void wifi_marauder_delay_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageDelay* stage_delay = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_delay->timeout;
+}
+
+void wifi_marauder_script_stage_menu_delay_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 1;
+    stage_menu->items = malloc(1 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_delay_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_delay_stage_timeout_select_callback
+    };
+}

+ 31 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_exec.c

@@ -0,0 +1,31 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_exec_stage_filter_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageExec* stage = app->script_edit_selected_stage->stage;
+    if (stage->command != NULL) {
+        variable_item_set_current_value_text(item, stage->command);
+    }
+}
+
+void wifi_marauder_exec_stage_filter_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageExec* stage_select = app->script_edit_selected_stage->stage;
+    if (stage_select->command == NULL) {
+        stage_select->command = malloc(128);
+    }
+    app->user_input_string_reference = &stage_select->command;
+}
+
+void wifi_marauder_script_stage_menu_exec_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 1;
+    stage_menu->items = malloc(1 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Command"),
+        .type = WifiMarauderScriptMenuItemTypeString,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_exec_stage_filter_setup_callback,
+        .select_callback = wifi_marauder_exec_stage_filter_select_callback
+    };
+}

+ 28 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_probe.c

@@ -0,0 +1,28 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_probe_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageProbe* 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);
+}
+
+void wifi_marauder_probe_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageProbe* stage_probe = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_probe->timeout;
+}
+
+void wifi_marauder_script_stage_menu_probe_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 1;
+    stage_menu->items = malloc(1 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_probe_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_probe_stage_timeout_select_callback
+    };
+}

+ 92 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_scan.c

@@ -0,0 +1,92 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_scan_stage_type_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageScan* stage = app->script_edit_selected_stage->stage;
+    variable_item_set_current_value_index(item, stage->type);
+}
+
+void wifi_marauder_scan_stage_type_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
+    WifiMarauderScriptStageScan* stage = app->script_edit_selected_stage->stage;
+    stage->type = option_index;
+}
+
+void wifi_marauder_scan_stage_channel_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageScan* 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);
+    }
+}
+
+void wifi_marauder_scan_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
+    WifiMarauderScriptStageScan* stage = app->script_edit_selected_stage->stage;
+    stage->channel = option_index;
+}
+
+void wifi_marauder_scan_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageScan* 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);
+}
+
+void wifi_marauder_scan_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageScan* stage_scan = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_scan->timeout;
+}
+
+void wifi_marauder_script_stage_menu_scan_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 3;
+    stage_menu->items = malloc(3 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Type"),
+        .type = WifiMarauderScriptMenuItemTypeOptionsString,
+        .num_options = 2,
+        .options = {"ap", "station"},
+        .setup_callback = wifi_marauder_scan_stage_type_setup_callback,
+        .change_callback = wifi_marauder_scan_stage_type_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_scan_stage_channel_setup_callback,
+        .change_callback = wifi_marauder_scan_stage_channel_change_callback,
+    };
+    stage_menu->items[2] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_scan_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_scan_stage_timeout_select_callback
+    };
+}

+ 97 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_select.c

@@ -0,0 +1,97 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_select_stage_type_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSelect* stage = app->script_edit_selected_stage->stage;
+    variable_item_set_current_value_index(item, stage->type);
+}
+
+void wifi_marauder_select_stage_type_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
+    WifiMarauderScriptStageSelect* stage = app->script_edit_selected_stage->stage;
+    stage->type = option_index;
+}
+
+void wifi_marauder_select_stage_filter_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSelect* stage = app->script_edit_selected_stage->stage;
+
+    if (stage->filter != NULL) {
+        variable_item_set_current_value_index(item, 0);
+        variable_item_set_current_value_text(item, stage->filter);
+    } else {
+        variable_item_set_current_value_index(item, 1);
+    }
+}
+
+void wifi_marauder_select_stage_filter_change_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSelect* stage = app->script_edit_selected_stage->stage;
+
+    // Clears the filter if you change the option. Flipper input box does not accept blank text
+    if (variable_item_get_current_value_index(item) == 1) {
+        stage->filter = NULL;
+        variable_item_set_current_value_index(item, 0);
+        variable_item_set_values_count(item, 1);
+    }
+
+    if (stage->filter != NULL) {
+        variable_item_set_current_value_text(item, stage->filter);
+    } else {
+        variable_item_set_current_value_text(item, "");
+    }
+}
+
+void wifi_marauder_select_stage_filter_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageSelect* stage_select = app->script_edit_selected_stage->stage;
+    if (stage_select->filter == NULL) {
+        stage_select->filter = malloc(128);
+    }
+    app->user_input_string_reference = &stage_select->filter;
+}
+
+void wifi_marauder_select_stage_indexes_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageSelect* stage_select = app->script_edit_selected_stage->stage;
+    app->script_stage_edit_numbers_reference = &stage_select->indexes;
+    app->script_stage_edit_number_count_reference = &stage_select->index_count;
+}
+
+void wifi_marauder_script_stage_menu_select_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 3;
+    stage_menu->items = malloc(3 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Type"),
+        .type = WifiMarauderScriptMenuItemTypeOptionsString,
+        .num_options = 2,
+        .options = {"ap", "station"},
+        .setup_callback = wifi_marauder_select_stage_type_setup_callback,
+        .change_callback = wifi_marauder_select_stage_type_change_callback
+    };
+    stage_menu->items[1] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Filter"),
+        .type = WifiMarauderScriptMenuItemTypeString,
+        .num_options = 2,
+        .setup_callback = wifi_marauder_select_stage_filter_setup_callback,
+        .change_callback = wifi_marauder_select_stage_filter_change_callback,
+        .select_callback = wifi_marauder_select_stage_filter_select_callback
+    };
+    stage_menu->items[2] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Indexes"),
+        .type = WifiMarauderScriptMenuItemTypeListNumber,
+        .num_options = 1,
+        .select_callback = wifi_marauder_select_stage_indexes_select_callback
+    };
+}

+ 28 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffbeacon.c

@@ -0,0 +1,28 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_sniffbeacon_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSniffBeacon* 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);
+}
+
+void wifi_marauder_sniffbeacon_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageSniffBeacon* stage_sniffbeacon = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_sniffbeacon->timeout;
+}
+
+void wifi_marauder_script_stage_menu_sniffbeacon_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 1;
+    stage_menu->items = malloc(1 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_sniffbeacon_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_sniffbeacon_stage_timeout_select_callback
+    };
+}

+ 28 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffdeauth.c

@@ -0,0 +1,28 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_sniffdeauth_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSniffDeauth* 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);
+}
+
+void wifi_marauder_sniffdeauth_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageSniffDeauth* stage_sniffdeauth = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_sniffdeauth->timeout;
+}
+
+void wifi_marauder_script_stage_menu_sniffdeauth_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 1;
+    stage_menu->items = malloc(1 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_sniffdeauth_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_sniffdeauth_stage_timeout_select_callback
+    };
+}

+ 28 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffesp.c

@@ -0,0 +1,28 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_sniffesp_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSniffEsp* 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);
+}
+
+void wifi_marauder_sniffesp_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageSniffEsp* stage_sniffesp = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_sniffesp->timeout;
+}
+
+void wifi_marauder_script_stage_menu_sniffesp_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 1;
+    stage_menu->items = malloc(1 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_sniffesp_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_sniffesp_stage_timeout_select_callback
+    };
+}

+ 92 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffpmkid.c

@@ -0,0 +1,92 @@
+#include "../../wifi_marauder_app_i.h"
+
+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 = 3;
+    stage_menu->items = malloc(3 * 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
+    };
+}

+ 28 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffpwn.c

@@ -0,0 +1,28 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_sniffpwn_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSniffPwn* 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);
+}
+
+void wifi_marauder_sniffpwn_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageSniffPwn* stage_sniffpwn = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_sniffpwn->timeout;
+}
+
+void wifi_marauder_script_stage_menu_sniffpwn_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 1;
+    stage_menu->items = malloc(1 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_sniffpwn_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_sniffpwn_stage_timeout_select_callback
+    };
+}

+ 28 - 0
applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffraw.c

@@ -0,0 +1,28 @@
+#include "../../wifi_marauder_app_i.h"
+
+void wifi_marauder_sniffraw_stage_timeout_setup_callback(VariableItem* item) {
+    WifiMarauderApp* app = variable_item_get_context(item);
+    WifiMarauderScriptStageSniffRaw* 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);
+}
+
+void wifi_marauder_sniffraw_stage_timeout_select_callback(void* context) {
+    WifiMarauderApp* app = context;
+    WifiMarauderScriptStageSniffRaw* stage_sniffraw = app->script_edit_selected_stage->stage;
+    app->user_input_number_reference = &stage_sniffraw->timeout;
+}
+
+void wifi_marauder_script_stage_menu_sniffraw_load(WifiMarauderScriptStageMenu* stage_menu) {
+    stage_menu->num_items = 1;
+    stage_menu->items = malloc(1 * sizeof(WifiMarauderScriptMenuItem));
+
+    stage_menu->items[0] = (WifiMarauderScriptMenuItem) {
+        .name = strdup("Timeout"),
+        .type = WifiMarauderScriptMenuItemTypeNumber,
+        .num_options = 1,
+        .setup_callback = wifi_marauder_sniffraw_stage_timeout_setup_callback,
+        .select_callback = wifi_marauder_sniffraw_stage_timeout_select_callback
+    };
+}

+ 845 - 0
applications/external/wifi_marauder_companion/script/wifi_marauder_script.c

@@ -0,0 +1,845 @@
+#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;
+
+    WifiMarauderScriptStageSniffPmkid *sniff_pmkid_stage = (WifiMarauderScriptStageSniffPmkid*) malloc(sizeof(WifiMarauderScriptStageSniffPmkid));
+    sniff_pmkid_stage->channel = channel;
+    sniff_pmkid_stage->timeout = timeout;
+    sniff_pmkid_stage->force_deauth = force_deauth;
+
+    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);
+    }
+    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);
+}

+ 249 - 0
applications/external/wifi_marauder_companion/script/wifi_marauder_script.h

@@ -0,0 +1,249 @@
+/*
+ * ----------------------------------------------------------------------------------------------------
+ * STEPS TO ADD A NEW STAGE:
+ * 
+ * wifi_marauder_script.h
+ * - Complement WifiMarauderScriptStageType enum with new stage
+ * - Create struct WifiMarauderScriptStage???? for the new stage
+ * 
+ * wifi_marauder_script.c
+ * - Change _wifi_marauder_script_load_stages() to load new stage
+ * - Change wifi_marauder_script_save_json() to support the new stage
+ * - Add case to free memory in wifi_marauder_script_free()
+ * 
+ * wifi_marauder_script_executor.c
+ * - Create function "void _wifi_marauder_script_execute_????(WifiMarauderScriptStage????* stage)"
+ * - Add case in wifi_marauder_script_execute_stage()
+ * 
+ * wifi_marauder_scene_script_edit.c
+ * - Add case in wifi_marauder_scene_script_edit_on_enter()
+ *
+ * wifi_marauder_scene_script_stage_add.c
+ * - Create stage creation function and add in wifi_marauder_scene_script_stage_add_on_enter()
+ * 
+ * wifi_marauder_script_stage_menu_config.h
+ * - Add the new stage and implement its functions in a new file
+ * 
+ * ----------------------------------------------------------------------------------------------------
+ * SCRIPT SYNTAX (In order of execution):
+ * {
+ *     "meta": {
+ *         "description": "My script",
+ *         "repeat": times the script will repeat (default 1),
+ *         "enableLed": true (default) | false,
+ *         "savePcap": true (default) | false
+ *     },
+ *     "stages": {
+ *         "scan": {
+ *             "type": "ap" | "station",
+ *             "timeout": seconds,
+ *             "channel": 1-11
+ *         },
+ *         "select": {
+ *             "type": "ap" | "station" | "ssid",
+ *             "filter": "all" | "contains -f '{SSID fragment}' or equals '{SSID}' or ...",
+ *             "indexes": [0, 1, 2, 3...],
+ *         },
+ *         "deauth": {
+ *             "timeout": seconds
+ *         },
+ *         "probe": {
+ *             "timeout": seconds
+ *         },
+ *         "sniffRaw": {
+ *             "timeout": seconds
+ *         },
+ *         "sniffBeacon": {
+ *             "timeout": seconds
+ *         },
+ *         "sniffDeauth": {
+ *             "timeout": seconds
+ *         },
+ *         "sniffEsp": {
+ *             "timeout": seconds
+ *         },
+ *         "sniffPmkid": {
+ *             "forceDeauth": true (default) | false,
+ *             "channel": 1-11,
+ *             "timeout": seconds
+ *         },
+ *         "sniffPwn": {
+ *             "timeout": seconds
+ *         },
+ *         "beaconList": {
+ *             "ssids": [
+ *                 "SSID 1",
+ *                 "SSID 2",
+ *                 "SSID 3"
+ *             ],
+ *             "generate": number of random SSIDs that will be generated,
+ *             "timeout": seconds
+ *         }
+ *         "beaconAp": {
+ *             "timeout": seconds
+ *         }
+ *         "exec": {
+ *             "command": Command (eg: "clearlist -a")
+ *         }
+ *         "delay": {
+ *             "timeout": seconds
+ *         }
+ *     }
+ * }
+ * 
+ * Note: It is possible to inform "stages" as an array, allowing ordering and repetition of stages of the same type:
+ *     "stages": [
+ *       {
+ *         "beaconList": { "ssids": ["SSID 1", "SSID 2"] }
+ *       },
+ *       {
+ *         "beaconList": { "generate": 4 }
+ *       },
+ *     ]
+ * ----------------------------------------------------------------------------------------------------
+ */
+
+#pragma once
+
+#include <storage/storage.h>
+#include "cJSON.h"
+
+#define WIFI_MARAUDER_DEFAULT_TIMEOUT_SCAN 15
+#define WIFI_MARAUDER_DEFAULT_TIMEOUT_DEAUTH 30
+#define WIFI_MARAUDER_DEFAULT_TIMEOUT_PROBE 60
+#define WIFI_MARAUDER_DEFAULT_TIMEOUT_SNIFF 60
+#define WIFI_MARAUDER_DEFAULT_TIMEOUT_BEACON 60
+
+typedef enum {
+    WifiMarauderScriptBooleanFalse = 0,
+    WifiMarauderScriptBooleanTrue = 1,
+    WifiMarauderScriptBooleanUndefined = 2
+} WifiMarauderScriptBoolean;
+
+typedef enum {
+    WifiMarauderScriptStageTypeScan,
+    WifiMarauderScriptStageTypeSelect,
+    WifiMarauderScriptStageTypeDeauth,
+    WifiMarauderScriptStageTypeProbe,
+    WifiMarauderScriptStageTypeSniffRaw,
+    WifiMarauderScriptStageTypeSniffBeacon,
+    WifiMarauderScriptStageTypeSniffDeauth,
+    WifiMarauderScriptStageTypeSniffEsp,
+    WifiMarauderScriptStageTypeSniffPmkid,
+    WifiMarauderScriptStageTypeSniffPwn,
+    WifiMarauderScriptStageTypeBeaconList,
+    WifiMarauderScriptStageTypeBeaconAp,
+    WifiMarauderScriptStageTypeExec,
+    WifiMarauderScriptStageTypeDelay,
+} WifiMarauderScriptStageType;
+
+typedef enum {
+    WifiMarauderScriptScanTypeAp = 0,
+    WifiMarauderScriptScanTypeStation = 1
+} WifiMarauderScriptScanType;
+
+typedef enum {
+    WifiMarauderScriptSelectTypeAp,
+    WifiMarauderScriptSelectTypeStation,
+    WifiMarauderScriptSelectTypeSsid
+} WifiMarauderScriptSelectType;
+
+// Stages
+typedef struct WifiMarauderScriptStage {
+    WifiMarauderScriptStageType type;
+    void* stage;
+    struct WifiMarauderScriptStage *next_stage;
+} WifiMarauderScriptStage;
+
+typedef struct WifiMarauderScriptStageScan {
+    WifiMarauderScriptScanType type;
+    int channel;
+    int timeout;
+} WifiMarauderScriptStageScan;
+
+typedef struct WifiMarauderScriptStageSelect {
+    WifiMarauderScriptSelectType type;
+    char* filter;
+    int* indexes;
+    int index_count;
+    // TODO: Implement a feature to not select the same items in the next iteration of the script
+    bool allow_repeat;
+} WifiMarauderScriptStageSelect;
+
+typedef struct WifiMarauderScriptStageDeauth {
+    int timeout;
+} WifiMarauderScriptStageDeauth;
+
+typedef struct WifiMarauderScriptStageProbe {
+    int timeout;
+} WifiMarauderScriptStageProbe;
+
+typedef struct WifiMarauderScriptStageSniffRaw {
+    int timeout;
+} WifiMarauderScriptStageSniffRaw;
+
+typedef struct WifiMarauderScriptStageSniffBeacon {
+    int timeout;
+} WifiMarauderScriptStageSniffBeacon;
+
+typedef struct WifiMarauderScriptStageSniffDeauth {
+    int timeout;
+} WifiMarauderScriptStageSniffDeauth;
+
+typedef struct WifiMarauderScriptStageSniffEsp {
+    int timeout;
+} WifiMarauderScriptStageSniffEsp;
+
+typedef struct WifiMarauderScriptStageSniffPmkid {
+    bool force_deauth;
+    int channel;
+    int timeout;
+} WifiMarauderScriptStageSniffPmkid;
+
+typedef struct WifiMarauderScriptStageSniffPwn {
+    int timeout;
+} WifiMarauderScriptStageSniffPwn;
+
+typedef struct WifiMarauderScriptStageBeaconList {
+    char **ssids;
+    int ssid_count;
+    int random_ssids;
+    int timeout;
+} WifiMarauderScriptStageBeaconList;
+
+typedef struct WifiMarauderScriptStageBeaconAp {
+    int timeout;
+} WifiMarauderScriptStageBeaconAp;
+
+typedef struct WifiMarauderScriptStageExec {
+    char* command;
+} WifiMarauderScriptStageExec;
+
+typedef struct WifiMarauderScriptStageDelay {
+    int timeout;
+} WifiMarauderScriptStageDelay;
+
+// Script
+typedef struct WifiMarauderScript {
+    char* name;
+    char* description;
+    WifiMarauderScriptStage *first_stage;
+    WifiMarauderScriptStage *last_stage;
+    WifiMarauderScriptBoolean enable_led;
+    WifiMarauderScriptBoolean save_pcap;
+    int repeat;
+} WifiMarauderScript;
+
+typedef struct WifiMarauderScriptStageListItem {
+    char* value;
+    struct WifiMarauderScriptStageListItem* next_item;
+} WifiMarauderScriptStageListItem;
+
+WifiMarauderScript* wifi_marauder_script_alloc();
+WifiMarauderScript* wifi_marauder_script_create(const char* script_name);
+WifiMarauderScript* wifi_marauder_script_parse_raw(const char* script_raw);
+WifiMarauderScript* wifi_marauder_script_parse_json(Storage* storage, const char* file_path);
+void wifi_marauder_script_save_json(Storage* storage, const char* file_path, WifiMarauderScript* script);
+void wifi_marauder_script_add_stage(WifiMarauderScript* script, WifiMarauderScriptStageType stage_type, void* stage_data);
+bool wifi_marauder_script_has_stage(WifiMarauderScript* script, WifiMarauderScriptStageType stage_type);
+void wifi_marauder_script_free(WifiMarauderScript *script);

+ 262 - 0
applications/external/wifi_marauder_companion/script/wifi_marauder_script_executor.c

@@ -0,0 +1,262 @@
+#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];
+    wifi_marauder_uart_tx((uint8_t*)("\n"), 1);
+    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) {
+    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));
+    }
+}
+
+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;
+    }
+}

+ 6 - 0
applications/external/wifi_marauder_companion/script/wifi_marauder_script_executor.h

@@ -0,0 +1,6 @@
+#pragma once
+
+#include "wifi_marauder_script.h"
+
+void wifi_marauder_script_execute_start(void *context);
+void wifi_marauder_script_execute_stage(WifiMarauderScriptStage* stage, void *context);

+ 71 - 0
applications/external/wifi_marauder_companion/script/wifi_marauder_script_worker.c

@@ -0,0 +1,71 @@
+#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);
+    }
+}

+ 41 - 0
applications/external/wifi_marauder_companion/script/wifi_marauder_script_worker.h

@@ -0,0 +1,41 @@
+#pragma once
+
+#include "wifi_marauder_script.h"
+
+typedef enum {
+    WifiMarauderScriptWorkerStatusSuccess = 0,
+    WifiMarauderScriptWorkerStatusInvalidScript = 1,
+    WifiMarauderScriptWorkerStatusForceExit = 2
+} WifiMarauderScriptWorkerStatus;
+
+typedef struct WifiMarauderScriptWorker {
+    WifiMarauderScript *script;
+    FuriThread *worker_thread;
+    void (*callback_start)(void*);
+    void (*callback_stage)(WifiMarauderScriptStage*, void*);
+    void *context;
+    bool is_running;
+} WifiMarauderScriptWorker;
+
+/**
+ * @brief Allocates a new instance of WifiMarauderScriptWorker.
+ *
+ * @return A pointer to the allocated instance or NULL if allocation fails.
+ */
+WifiMarauderScriptWorker* wifi_marauder_script_worker_alloc();
+
+/**
+ * @brief Starts the execution of the worker and sets the callback function to be called after each stage is executed.
+ *
+ * @param instance A pointer to the instance of WifiMarauderScriptWorker to start.
+ * @param script Script to be executed
+ * @return True if the worker was successfully started, false otherwise.
+ */
+bool wifi_marauder_script_worker_start(WifiMarauderScriptWorker* instance, WifiMarauderScript* script);
+
+/**
+ * @brief Frees the memory used by the instance of WifiMarauderScriptWorker.
+ *
+ * @param script A pointer to the instance of WifiMarauderScriptWorker to free.
+ */
+void wifi_marauder_script_worker_free(WifiMarauderScriptWorker* script);

+ 12 - 0
applications/external/wifi_marauder_companion/wifi_marauder_app.c

@@ -79,6 +79,10 @@ WifiMarauderApp* wifi_marauder_app_alloc() {
         (!storage_file_exists(app->storage, SAVE_PCAP_SETTING_FILEPATH) ||
          !storage_file_exists(app->storage, SAVE_LOGS_SETTING_FILEPATH));
 
+    // Submenu
+    app->submenu = submenu_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, WifiMarauderAppViewSubmenu, submenu_get_view(app->submenu));
+
     scene_manager_next_scene(app->scene_manager, WifiMarauderSceneStart);
 
     return app;
@@ -98,6 +102,10 @@ void wifi_marauder_make_app_folder(WifiMarauderApp* app) {
     if(!storage_simply_mkdir(app->storage, MARAUDER_APP_FOLDER_LOGS)) {
         dialog_message_show_storage_error(app->dialogs, "Cannot create\npcaps folder");
     }
+
+    if(!storage_simply_mkdir(app->storage, MARAUDER_APP_FOLDER_SCRIPTS)) {
+        dialog_message_show_storage_error(app->dialogs, "Cannot create\nscripts folder");
+    }
 }
 
 void wifi_marauder_load_settings(WifiMarauderApp* app) {
@@ -132,10 +140,14 @@ void wifi_marauder_app_free(WifiMarauderApp* app) {
     view_dispatcher_remove_view(app->view_dispatcher, WifiMarauderAppViewConsoleOutput);
     view_dispatcher_remove_view(app->view_dispatcher, WifiMarauderAppViewTextInput);
     view_dispatcher_remove_view(app->view_dispatcher, WifiMarauderAppViewWidget);
+    view_dispatcher_remove_view(app->view_dispatcher, WifiMarauderAppViewSubmenu);
+
     widget_free(app->widget);
     text_box_free(app->text_box);
     furi_string_free(app->text_box_store);
     text_input_free(app->text_input);
+    submenu_free(app->submenu);
+    variable_item_list_free(app->var_item_list);
     storage_file_free(app->capture_file);
     storage_file_free(app->log_file);
     storage_file_free(app->save_pcap_setting_file);

+ 39 - 2
applications/external/wifi_marauder_companion/wifi_marauder_app_i.h

@@ -6,20 +6,27 @@
 #include "scenes/wifi_marauder_scene.h"
 #include "wifi_marauder_custom_event.h"
 #include "wifi_marauder_uart.h"
-#include "wifi_marauder_pcap.h"
+#include "file/sequential_file.h"
+#include "script/wifi_marauder_script.h"
+#include "script/wifi_marauder_script_worker.h"
+#include "script/wifi_marauder_script_executor.h"
+#include "script/menu/wifi_marauder_script_stage_menu.h"
 
 #include <gui/gui.h>
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <gui/modules/text_box.h>
 #include <gui/modules/text_input.h>
+#include <gui/modules/submenu.h>
 #include <gui/modules/variable_item_list.h>
 #include <gui/modules/widget.h>
 
+#include <assets_icons.h>
 #include <storage/storage.h>
+#include <lib/toolbox/path.h>
 #include <dialogs/dialogs.h>
 
-#define NUM_MENU_ITEMS (17)
+#define NUM_MENU_ITEMS (18)
 
 #define WIFI_MARAUDER_TEXT_BOX_STORE_SIZE (4096)
 #define WIFI_MARAUDER_TEXT_INPUT_STORE_SIZE (512)
@@ -30,9 +37,17 @@
 #define MARAUDER_APP_FOLDER_LOGS MARAUDER_APP_FOLDER "/logs"
 #define MARAUDER_APP_FOLDER_USER_PCAPS MARAUDER_APP_FOLDER_USER "/pcaps"
 #define MARAUDER_APP_FOLDER_USER_LOGS MARAUDER_APP_FOLDER_USER "/logs"
+#define MARAUDER_APP_FOLDER_SCRIPTS MARAUDER_APP_FOLDER "/scripts"
+#define MARAUDER_APP_SCRIPT_PATH(file_name) MARAUDER_APP_FOLDER_SCRIPTS "/" file_name ".json"
 #define SAVE_PCAP_SETTING_FILEPATH MARAUDER_APP_FOLDER "/save_pcaps_here.setting"
 #define SAVE_LOGS_SETTING_FILEPATH MARAUDER_APP_FOLDER "/save_logs_here.setting"
 
+typedef enum WifiMarauderUserInputType {
+    WifiMarauderUserInputTypeString,
+    WifiMarauderUserInputTypeNumber,
+    WifiMarauderUserInputTypeFileName
+} WifiMarauderUserInputType;
+
 struct WifiMarauderApp {
     Gui* gui;
     ViewDispatcher* view_dispatcher;
@@ -58,6 +73,7 @@ struct WifiMarauderApp {
 
     VariableItemList* var_item_list;
     Widget* widget;
+    Submenu* submenu;
     int open_log_file_page;
     int open_log_file_num_pages;
 
@@ -73,6 +89,26 @@ struct WifiMarauderApp {
     bool is_writing_pcap;
     bool is_writing_log;
 
+    // User input
+    WifiMarauderUserInputType user_input_type;
+    char** user_input_string_reference;
+    int* user_input_number_reference;
+    char* user_input_file_dir;
+    char* user_input_file_extension;
+
+    // Automation script
+    WifiMarauderScript* script;
+    WifiMarauderScriptWorker* script_worker;
+    FuriString** script_list;
+    int script_list_count;
+    WifiMarauderScriptStage* script_edit_selected_stage;
+    WifiMarauderScriptStageMenu* script_stage_menu;
+    WifiMarauderScriptStageListItem* script_stage_edit_first_item;
+    char*** script_stage_edit_strings_reference;
+    int* script_stage_edit_string_count_reference;
+    int** script_stage_edit_numbers_reference;
+    int* script_stage_edit_number_count_reference;
+
     // For input source and destination MAC in targeted deauth attack
     int special_case_input_step;
     char special_case_input_src_addr[20];
@@ -105,4 +141,5 @@ typedef enum {
     WifiMarauderAppViewConsoleOutput,
     WifiMarauderAppViewTextInput,
     WifiMarauderAppViewWidget,
+    WifiMarauderAppViewSubmenu,
 } WifiMarauderAppView;

+ 2 - 1
applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h

@@ -7,5 +7,6 @@ typedef enum {
     WifiMarauderEventSaveSourceMac,
     WifiMarauderEventSaveDestinationMac,
     WifiMarauderEventStartSettingsInit,
-    WifiMarauderEventStartLogViewer
+    WifiMarauderEventStartLogViewer,
+    WifiMarauderEventStartScriptSelect
 } WifiMarauderCustomEvent;

+ 0 - 64
applications/external/wifi_marauder_companion/wifi_marauder_pcap.c

@@ -1,64 +0,0 @@
-#include "wifi_marauder_app_i.h"
-#include "wifi_marauder_pcap.h"
-
-void wifi_marauder_get_prefix_from_sniff_cmd(char* dest, const char* command) {
-    int start, end, delta;
-    start = strlen("sniff");
-    end = strcspn(command, " ");
-    delta = end - start;
-    strncpy(dest, command + start, end - start);
-    dest[delta] = '\0';
-}
-
-void wifi_marauder_get_prefix_from_cmd(char* dest, const char* command) {
-    int end;
-    end = strcspn(command, " ");
-    strncpy(dest, command, end);
-    dest[end] = '\0';
-}
-
-void wifi_marauder_create_pcap_file(WifiMarauderApp* app) {
-    char prefix[10];
-    char capture_file_path[100];
-    wifi_marauder_get_prefix_from_sniff_cmd(prefix, app->selected_tx_string);
-
-    int i = 0;
-    do {
-        snprintf(
-            capture_file_path,
-            sizeof(capture_file_path),
-            "%s/%s_%d.pcap",
-            MARAUDER_APP_FOLDER_PCAPS,
-            prefix,
-            i);
-        i++;
-    } while(storage_file_exists(app->storage, capture_file_path));
-
-    if(!storage_file_open(app->capture_file, capture_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
-        dialog_message_show_storage_error(app->dialogs, "Cannot open pcap file");
-    }
-}
-
-void wifi_marauder_create_log_file(WifiMarauderApp* app) {
-    char prefix[10];
-    char log_file_path[100];
-    wifi_marauder_get_prefix_from_cmd(prefix, app->selected_tx_string);
-
-    int i = 0;
-    do {
-        snprintf(
-            log_file_path,
-            sizeof(log_file_path),
-            "%s/%s_%d.log",
-            MARAUDER_APP_FOLDER_LOGS,
-            prefix,
-            i);
-        i++;
-    } while(storage_file_exists(app->storage, log_file_path));
-
-    if(!storage_file_open(app->log_file, log_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
-        dialog_message_show_storage_error(app->dialogs, "Cannot open log file");
-    } else {
-        strcpy(app->log_file_path, log_file_path);
-    }
-}

+ 0 - 20
applications/external/wifi_marauder_companion/wifi_marauder_pcap.h

@@ -1,20 +0,0 @@
-#pragma once
-
-#include "furi_hal.h"
-
-/**
- * Creates a PCAP file to store incoming packets.
- * The file name will have a prefix according to the type of scan being performed by the application (Eg: raw_0.pcap)
- * 
- * @param app Application context
- */
-void wifi_marauder_create_pcap_file(WifiMarauderApp* app);
-
-/**
- * Creates a log file to store text from console output.
- * The file name will have a prefix according to the command being performed by the application (Eg: scanap_0.log)
- *
- * @param app Application context
- */
-// same as wifi_marauder_create_pcap_file, but for log files (to save console text output)
-void wifi_marauder_create_log_file(WifiMarauderApp* app);