diff --git a/.gitignore b/.gitignore
index e33c10125..d6a2dc470 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,7 +30,7 @@ bindings/
Brewfile.lock.json
# Visual Studio Code
-.vscode/
+/.vscode/
# Visual Studio
.vs/
diff --git a/.pvsoptions b/.pvsoptions
index 6b22aed76..ecb333dec 100644
--- a/.pvsoptions
+++ b/.pvsoptions
@@ -1 +1 @@
---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/external/dap_link/lib/free-dap
+--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/xtreme -e lib/nanopb -e */arm-none-eabi/* -e applications/external/dap_link/lib/free-dap
diff --git a/ReadMe.md b/ReadMe.md
index 87e6df32e..6934b45c1 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -32,18 +32,23 @@ Note, the below mentioned changes are only a few things we did. For a full list
We wrote a powerful yet easy-to-use application specifically for our Firmware, that gives you easy-access to all the fancy things we implemented:
-
+
-Misc:
-All the other options that don't fit elsewhere. Change your Flipper's name, change xp level, and manage settings for RGB backlight.
+
+
-
+- Interface: Customize every bit of your Flipper, from the desktop animations, to the main menu apps, lockscreen style etc.
+
+- Protocols: Here you can toggle between USB & Bluetooth mode for BadKB, and manage custom Subghz frequencies.
+
+- Misc: All the other options that don't fit elsewhere. Change your Flipper's name, xp level, and configure the RGB backlight.
+
+
-----
diff --git a/applications/debug/accessor/accessor_app.cpp b/applications/debug/accessor/accessor_app.cpp
index 2e40b3c35..05a248efa 100644
--- a/applications/debug/accessor/accessor_app.cpp
+++ b/applications/debug/accessor/accessor_app.cpp
@@ -23,7 +23,7 @@ void AccessorApp::run(void) {
exit = switch_to_previous_scene();
}
}
- };
+ }
scenes[current_scene]->on_exit(this);
diff --git a/applications/debug/battery_test_app/views/battery_info.c b/applications/debug/battery_test_app/views/battery_info.c
index 5353a2e2a..e32d1b461 100644
--- a/applications/debug/battery_test_app/views/battery_info.c
+++ b/applications/debug/battery_test_app/views/battery_info.c
@@ -17,7 +17,7 @@ static void draw_stat(Canvas* canvas, int x, int y, const Icon* icon, char* val)
canvas_draw_box(canvas, x - 4, y + 16, 24, 6);
canvas_set_color(canvas, ColorBlack);
canvas_draw_str_aligned(canvas, x + 8, y + 22, AlignCenter, AlignBottom, val);
-};
+}
static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
char emote[20] = {};
@@ -85,7 +85,7 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
canvas_draw_str_aligned(canvas, 92, y + 3, AlignCenter, AlignCenter, emote);
canvas_draw_str_aligned(canvas, 92, y + 15, AlignCenter, AlignCenter, header);
canvas_draw_str_aligned(canvas, 92, y + 27, AlignCenter, AlignCenter, value);
-};
+}
static void battery_info_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
diff --git a/applications/debug/uart_echo/application.fam b/applications/debug/uart_echo/application.fam
index c4079c6c1..ecdc847cd 100644
--- a/applications/debug/uart_echo/application.fam
+++ b/applications/debug/uart_echo/application.fam
@@ -1,12 +1,12 @@
App(
appid="UART_Echo",
name="[GPIO] UART Echo",
- apptype=FlipperAppType.EXTERNAL,
+ apptype=FlipperAppType.DEBUG,
entry_point="uart_echo_app",
cdefines=["APP_UART_ECHO"],
requires=["gui"],
stack_size=2 * 1024,
order=70,
fap_icon="uart_10px.png",
- fap_category="GPIO",
+ fap_category="Debug",
)
diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c
index 54bdd5909..3b3a44431 100644
--- a/applications/debug/unit_tests/nfc/nfc_test.c
+++ b/applications/debug/unit_tests/nfc/nfc_test.c
@@ -1,10 +1,12 @@
#include
#include
+#include
#include
#include
#include
#include
#include
+#include
#include
#include
@@ -179,6 +181,153 @@ MU_TEST(nfc_digital_signal_test) {
"NFC long digital signal test failed\r\n");
}
+static bool nfc_test_pulse_reader_toggle(
+ uint32_t usec_low,
+ uint32_t usec_high,
+ uint32_t period_count,
+ uint32_t tolerance) {
+ furi_assert(nfc_test);
+
+ bool success = false;
+ uint32_t pulses = 0;
+ const GpioPin* gpio_in = &gpio_ext_pa6;
+ const GpioPin* gpio_out = &gpio_ext_pa7;
+ PulseReader* reader = NULL;
+
+ do {
+ reader = pulse_reader_alloc(gpio_in, 512);
+
+ if(!reader) {
+ FURI_LOG_E(TAG, "failed to allocate pulse reader");
+ break;
+ }
+
+ /* use TIM1 to create a specific number of pulses with defined duty cycle
+ but first set the IO to high, so the low/high pulse can get detected */
+ furi_hal_gpio_init(gpio_out, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+ furi_hal_gpio_write(gpio_out, true);
+
+ LL_TIM_DeInit(TIM1);
+
+ LL_TIM_SetCounterMode(TIM1, LL_TIM_COUNTERMODE_UP);
+ LL_TIM_SetRepetitionCounter(TIM1, 0);
+ LL_TIM_SetClockDivision(TIM1, LL_TIM_CLOCKDIVISION_DIV1);
+ LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL);
+ LL_TIM_DisableARRPreload(TIM1);
+
+ LL_TIM_OC_DisablePreload(TIM1, LL_TIM_CHANNEL_CH1);
+ LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM2);
+ LL_TIM_OC_SetPolarity(TIM1, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH);
+ LL_TIM_OC_DisableFast(TIM1, LL_TIM_CHANNEL_CH1);
+ LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N);
+
+ LL_TIM_EnableAllOutputs(TIM1);
+
+ /* now calculate the TIM1 period and compare values */
+ uint32_t freq_div = 64 * (usec_low + usec_high);
+ uint32_t prescaler = freq_div / 0x10000LU;
+ uint32_t period = freq_div / (prescaler + 1);
+ uint32_t compare = 64 * usec_low / (prescaler + 1);
+
+ LL_TIM_SetPrescaler(TIM1, prescaler);
+ LL_TIM_SetAutoReload(TIM1, period - 1);
+ LL_TIM_SetCounter(TIM1, period - 1);
+ LL_TIM_OC_SetCompareCH1(TIM1, compare);
+
+ /* timer is ready to launch, now start the pulse reader */
+ pulse_reader_set_timebase(reader, PulseReaderUnitMicrosecond);
+ pulse_reader_start(reader);
+
+ /* and quickly enable and switch over the GPIO to the generated signal */
+ LL_TIM_EnableCounter(TIM1);
+ furi_hal_gpio_init_ex(
+ gpio_out, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedVeryHigh, GpioAltFn1TIM1);
+
+ /* now it's time to parse the pulses received by the reader */
+ uint32_t timer_pulses = period_count;
+ uint32_t prev_cnt = 0;
+
+ while(timer_pulses > 0) {
+ /* whenever the counter gets reset, we went through a full period */
+ uint32_t cur_cnt = LL_TIM_GetCounter(TIM1);
+ if(cur_cnt < prev_cnt) {
+ timer_pulses--;
+ }
+ prev_cnt = cur_cnt;
+ }
+ /* quickly halt the counter to keep a static signal */
+ LL_TIM_DisableCounter(TIM1);
+
+ do {
+ /* as all edges were sampled asynchronously, the timeout can be zero */
+ uint32_t length = pulse_reader_receive(reader, 0);
+
+ /* in the last pulse, we expect a "no edge" return value. if seen that, test succeeded. */
+ if(pulses > period_count * 2) {
+ if(length != PULSE_READER_NO_EDGE) {
+ FURI_LOG_E(
+ TAG,
+ "last pulse expected to be PULSE_READER_NO_EDGE, but was %lu.",
+ length);
+ break;
+ }
+ success = true;
+ break;
+ }
+
+ /* else we shall never see "no edge" or "lost edge" */
+ if(length == PULSE_READER_NO_EDGE) {
+ FURI_LOG_E(TAG, "%lu. pulse not expected to be PULSE_READER_NO_EDGE", pulses);
+ break;
+ }
+ if(length == PULSE_READER_LOST_EDGE) {
+ FURI_LOG_E(TAG, "%lu. pulse not expected to be PULSE_READER_LOST_EDGE", pulses);
+ break;
+ }
+
+ if(pulses > 0) {
+ /* throw away the first pulse, which is the 1->0 from the first start and will be irrelevant for our test */
+ bool phase = ((pulses - 1) % 2) == 1;
+ uint32_t expected = phase ? usec_high : usec_low;
+ uint32_t deviation = abs((int32_t)length - (int32_t)expected);
+
+ if(deviation > tolerance) {
+ FURI_LOG_E(
+ TAG,
+ "%lu. pulse expected %lu, but pulse was %lu.",
+ pulses,
+ expected,
+ length);
+ break;
+ }
+ }
+ pulses++;
+ } while(true);
+ } while(false);
+
+ if(reader != NULL) {
+ pulse_reader_stop(reader);
+ pulse_reader_free(reader);
+ }
+
+ LL_TIM_DeInit(TIM1);
+ furi_hal_gpio_init_simple(gpio_in, GpioModeAnalog);
+ furi_hal_gpio_init_simple(gpio_out, GpioModeAnalog);
+
+ return success;
+}
+
+MU_TEST(nfc_pulse_reader_test) {
+ mu_assert(nfc_test_pulse_reader_toggle(1500, 2500, 50, 10), "1 ms signal failed\r\n");
+ mu_assert(nfc_test_pulse_reader_toggle(10000, 10000, 10, 10), "10 ms signal failed\r\n");
+ mu_assert(nfc_test_pulse_reader_toggle(100000, 100000, 5, 50), "100 ms signal failed\r\n");
+ mu_assert(nfc_test_pulse_reader_toggle(100, 900, 50, 10), "1 ms asymmetric signal failed\r\n");
+ mu_assert(
+ nfc_test_pulse_reader_toggle(3333, 6666, 10, 10), "10 ms asymmetric signal failed\r\n");
+ mu_assert(
+ nfc_test_pulse_reader_toggle(25000, 75000, 5, 10), "100 ms asymmetric signal failed\r\n");
+}
+
MU_TEST(mf_classic_dict_test) {
MfClassicDict* instance = NULL;
uint64_t key = 0;
@@ -513,6 +662,7 @@ MU_TEST(mf_classic_4k_7b_file_test) {
MU_TEST_SUITE(nfc) {
nfc_test_alloc();
+ MU_RUN_TEST(nfc_pulse_reader_test);
MU_RUN_TEST(nfca_file_test);
MU_RUN_TEST(mf_mini_file_test);
MU_RUN_TEST(mf_classic_1k_4b_file_test);
diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c
index 329f3b741..167266a84 100644
--- a/applications/debug/unit_tests/rpc/rpc_test.c
+++ b/applications/debug/unit_tests/rpc/rpc_test.c
@@ -84,7 +84,7 @@ static void test_rpc_setup(void) {
rpc = furi_record_open(RECORD_RPC);
for(int i = 0; !(rpc_session[0].session) && (i < 10000); ++i) {
- rpc_session[0].session = rpc_session_open(rpc);
+ rpc_session[0].session = rpc_session_open(rpc, RpcOwnerUnknown);
furi_delay_tick(1);
}
furi_check(rpc_session[0].session);
@@ -104,7 +104,7 @@ static void test_rpc_setup_second_session(void) {
furi_check(!(rpc_session[1].session));
for(int i = 0; !(rpc_session[1].session) && (i < 10000); ++i) {
- rpc_session[1].session = rpc_session_open(rpc);
+ rpc_session[1].session = rpc_session_open(rpc, RpcOwnerUnknown);
furi_delay_tick(1);
}
furi_check(rpc_session[1].session);
diff --git a/applications/external/airmouse/tracking/util/matrix_4x4.h b/applications/external/airmouse/tracking/util/matrix_4x4.h
index 9934f6be0..83f151fc4 100644
--- a/applications/external/airmouse/tracking/util/matrix_4x4.h
+++ b/applications/external/airmouse/tracking/util/matrix_4x4.h
@@ -34,4 +34,4 @@ private:
} // namespace cardboard
-#endif // CARDBOARD_SDK_UTIL_MATRIX4X4_H_
+#endif // CARDBOARD_SDK_UTIL_MATRIX_4X4_H_
diff --git a/applications/external/asteroids/application.fam b/applications/external/asteroids/application.fam
index 5f70a0e1c..5eb43a6e5 100644
--- a/applications/external/asteroids/application.fam
+++ b/applications/external/asteroids/application.fam
@@ -8,8 +8,8 @@ App(
stack_size=8 * 1024,
order=50,
fap_icon="appicon.png",
- fap_icon_assets="assets", # Image assets to compile for this application
fap_category="Games",
+ fap_icon_assets="assets", # Image assets to compile for this application
fap_description="An implementation of the classic arcade game Asteroids",
fap_author="antirez, SimplyMinimal",
fap_weburl="https://github.com/SimplyMinimal/FlipperZero-Asteroids",
diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.c b/applications/external/avr_isp_programmer/helpers/avr_isp.c
index 51b4f8846..1b9e2fd1f 100644
--- a/applications/external/avr_isp_programmer/helpers/avr_isp.c
+++ b/applications/external/avr_isp_programmer/helpers/avr_isp.c
@@ -152,7 +152,12 @@ bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) {
}
}
}
- if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+
+ if(instance->spi) {
+ avr_isp_spi_sw_free(instance->spi);
+ instance->spi = NULL;
+ }
+
return false;
}
@@ -169,7 +174,7 @@ static void avr_isp_commit(AvrIsp* instance, uint16_t addr, uint8_t data) {
while((furi_get_tick() - starttime) < 30) {
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) {
break;
- };
+ }
}
}
}
@@ -352,7 +357,7 @@ uint8_t avr_isp_read_lock_byte(AvrIsp* instance) {
data = avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE);
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == data) {
break;
- };
+ }
data = 0x00;
}
return data;
@@ -372,7 +377,7 @@ bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock) {
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == lock) {
ret = true;
break;
- };
+ }
}
}
return ret;
@@ -387,7 +392,7 @@ uint8_t avr_isp_read_fuse_low(AvrIsp* instance) {
data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW);
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == data) {
break;
- };
+ }
data = 0x00;
}
return data;
@@ -407,7 +412,7 @@ bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse) {
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == lfuse) {
ret = true;
break;
- };
+ }
}
}
return ret;
@@ -422,7 +427,7 @@ uint8_t avr_isp_read_fuse_high(AvrIsp* instance) {
data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH);
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == data) {
break;
- };
+ }
data = 0x00;
}
return data;
@@ -442,7 +447,7 @@ bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse) {
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == hfuse) {
ret = true;
break;
- };
+ }
}
}
return ret;
@@ -457,7 +462,7 @@ uint8_t avr_isp_read_fuse_extended(AvrIsp* instance) {
data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED);
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == data) {
break;
- };
+ }
data = 0x00;
}
return data;
@@ -477,7 +482,7 @@ bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse) {
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == efuse) {
ret = true;
break;
- };
+ }
}
}
return ret;
diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c
index b3c81f3b1..051d97e9c 100644
--- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c
+++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c
@@ -111,7 +111,7 @@ static uint8_t avr_isp_prog_getch(AvrIspProg* instance) {
uint8_t data[1] = {0};
while(furi_stream_buffer_receive(instance->stream_rx, &data, sizeof(int8_t), 30) == 0) {
if(instance->exit) break;
- };
+ }
return data[0];
}
@@ -196,7 +196,7 @@ static void avr_isp_prog_set_cfg(AvrIspProg* instance) {
instance->cfg->lockbytes = instance->buff[6];
instance->cfg->fusebytes = instance->buff[7];
instance->cfg->flashpoll = instance->buff[8];
- // ignore (instance->buff[9] == instance->buff[8]) //FLASH polling value. Same as flashpoll
+ // ignore (instance->buff[9] == instance->buff[8]) //FLASH polling value. Same as οΏ½flashpollοΏ½
instance->cfg->eeprompoll = instance->buff[10] << 8 | instance->buff[11];
instance->cfg->pagesize = instance->buff[12] << 8 | instance->buff[13];
instance->cfg->eepromsize = instance->buff[14] << 8 | instance->buff[15];
@@ -317,7 +317,12 @@ static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) {
}
}
}
- if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+
+ if(instance->spi) {
+ avr_isp_spi_sw_free(instance->spi);
+ instance->spi = NULL;
+ }
+
return false;
}
@@ -343,7 +348,7 @@ static void avr_isp_prog_commit(AvrIspProg* instance, uint16_t addr, uint8_t dat
while((furi_get_tick() - starttime) < 30) {
if(avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) {
break;
- };
+ }
}
}
}
diff --git a/applications/external/barcode_gen/barcode_utils.c b/applications/external/barcode_gen/barcode_utils.c
index 0a4770045..502014d85 100644
--- a/applications/external/barcode_gen/barcode_utils.c
+++ b/applications/external/barcode_gen/barcode_utils.c
@@ -98,7 +98,7 @@ const char* get_error_code_name(ErrorCode error_code) {
return "OK";
default:
return "Unknown Code";
- };
+ }
}
const char* get_error_code_message(ErrorCode error_code) {
@@ -121,5 +121,5 @@ const char* get_error_code_message(ErrorCode error_code) {
return "OK";
default:
return "Could not read barcode data";
- };
+ }
}
\ No newline at end of file
diff --git a/applications/external/bomberduck/LICENSE b/applications/external/bomberduck/LICENSE
new file mode 100644
index 000000000..4624b249c
--- /dev/null
+++ b/applications/external/bomberduck/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2023 Π»Π΅Π½Ρ
+
+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.
+
diff --git a/applications/external/bomberduck/application.fam b/applications/external/bomberduck/application.fam
new file mode 100644
index 000000000..d2ee5564b
--- /dev/null
+++ b/applications/external/bomberduck/application.fam
@@ -0,0 +1,14 @@
+App(
+ appid="bomberduck",
+ name="Bomberduck",
+ apptype=FlipperAppType.EXTERNAL,
+ entry_point="bomberduck_app",
+ requires=[
+ "gui",
+ ],
+ stack_size=1 * 1024,
+ order=90,
+ fap_icon="bomb.png",
+ fap_category="Games",
+ fap_icon_assets="assets",
+)
diff --git a/applications/external/bomberduck/assets/bomb0.png b/applications/external/bomberduck/assets/bomb0.png
new file mode 100644
index 000000000..3fdc3a3c1
Binary files /dev/null and b/applications/external/bomberduck/assets/bomb0.png differ
diff --git a/applications/external/bomberduck/assets/bomb1.png b/applications/external/bomberduck/assets/bomb1.png
new file mode 100644
index 000000000..11d05b9b7
Binary files /dev/null and b/applications/external/bomberduck/assets/bomb1.png differ
diff --git a/applications/external/bomberduck/assets/bomb2.png b/applications/external/bomberduck/assets/bomb2.png
new file mode 100644
index 000000000..38ce7c732
Binary files /dev/null and b/applications/external/bomberduck/assets/bomb2.png differ
diff --git a/applications/external/bomberduck/assets/box.png b/applications/external/bomberduck/assets/box.png
new file mode 100644
index 000000000..bbd352b6f
Binary files /dev/null and b/applications/external/bomberduck/assets/box.png differ
diff --git a/applications/external/bomberduck/assets/end.png b/applications/external/bomberduck/assets/end.png
new file mode 100644
index 000000000..d634933b7
Binary files /dev/null and b/applications/external/bomberduck/assets/end.png differ
diff --git a/applications/external/bomberduck/assets/enemy1.png b/applications/external/bomberduck/assets/enemy1.png
new file mode 100644
index 000000000..7ee7cb27f
Binary files /dev/null and b/applications/external/bomberduck/assets/enemy1.png differ
diff --git a/applications/external/bomberduck/assets/enemyleft.png b/applications/external/bomberduck/assets/enemyleft.png
new file mode 100644
index 000000000..bb85dfbb2
Binary files /dev/null and b/applications/external/bomberduck/assets/enemyleft.png differ
diff --git a/applications/external/bomberduck/assets/enemyright.png b/applications/external/bomberduck/assets/enemyright.png
new file mode 100644
index 000000000..45e6a861a
Binary files /dev/null and b/applications/external/bomberduck/assets/enemyright.png differ
diff --git a/applications/external/bomberduck/assets/explore.png b/applications/external/bomberduck/assets/explore.png
new file mode 100644
index 000000000..5eb50b669
Binary files /dev/null and b/applications/external/bomberduck/assets/explore.png differ
diff --git a/applications/external/bomberduck/assets/playerleft.png b/applications/external/bomberduck/assets/playerleft.png
new file mode 100644
index 000000000..86997a985
Binary files /dev/null and b/applications/external/bomberduck/assets/playerleft.png differ
diff --git a/applications/external/bomberduck/assets/playerright.png b/applications/external/bomberduck/assets/playerright.png
new file mode 100644
index 000000000..1a6283d9c
Binary files /dev/null and b/applications/external/bomberduck/assets/playerright.png differ
diff --git a/applications/external/bomberduck/assets/unbreakbox.png b/applications/external/bomberduck/assets/unbreakbox.png
new file mode 100644
index 000000000..5e65912d5
Binary files /dev/null and b/applications/external/bomberduck/assets/unbreakbox.png differ
diff --git a/applications/external/bomberduck/bomb.png b/applications/external/bomberduck/bomb.png
new file mode 100644
index 000000000..44b9bfdea
Binary files /dev/null and b/applications/external/bomberduck/bomb.png differ
diff --git a/applications/external/bomberduck/bomberduck.c b/applications/external/bomberduck/bomberduck.c
new file mode 100644
index 000000000..8c38b1a9c
--- /dev/null
+++ b/applications/external/bomberduck/bomberduck.c
@@ -0,0 +1,643 @@
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include "bomberduck_icons.h"
+
+int max(int a, int b) {
+ return (a > b) ? a : b;
+}
+
+int min(int a, int b) {
+ return (a < b) ? a : b;
+}
+
+#define WorldSizeX 12
+#define WorldSizeY 6
+#define BombRange 1
+
+typedef struct {
+ FuriMutex* mutex;
+} BomberState;
+
+typedef struct {
+ int row;
+ int col;
+} Cell;
+
+typedef struct {
+ Cell cells[WorldSizeY * WorldSizeX];
+ int front;
+ int rear;
+} Queue;
+
+void enqueue(Queue* q, Cell c) {
+ q->cells[q->rear] = c;
+ q->rear++;
+}
+
+Cell dequeue(Queue* q) {
+ Cell c = q->cells[q->front];
+ q->front++;
+
+ return c;
+}
+
+bool is_empty(Queue* q) {
+ return q->front == q->rear;
+}
+
+typedef struct {
+ int x;
+ int y;
+ int planted;
+} Bomb;
+
+typedef struct {
+ int x;
+ int y;
+ bool side;
+} Player;
+
+typedef struct {
+ int x;
+ int y;
+ int last;
+ bool side;
+ int level;
+} Enemy;
+
+typedef struct {
+ int matrix[WorldSizeY][WorldSizeX];
+ Player* player;
+ bool running;
+ int level;
+
+ Enemy enemies[10];
+ int enemies_count;
+
+ Bomb bombs[100];
+ int bombs_count;
+
+ int endx;
+ int endy;
+} World;
+
+Player player = {0, 0, 1};
+World world = {{{0}}, &player, 1, 0, {}, 0, {}, 0, 0, 0};
+bool vibration = false;
+
+void init() {
+ player.x = 1;
+ player.y = 1;
+
+ world.endx = 4 + rand() % 8;
+ world.endy = rand() % 6;
+ for(int i = 0; i < WorldSizeY; i++) {
+ for(int j = 0; j < WorldSizeX; j++) {
+ world.matrix[i][j] = rand() % 3;
+ }
+ }
+ world.running = 1;
+ world.bombs_count = 0;
+ vibration = false;
+ for(int j = max(0, player.y - BombRange); j < min(WorldSizeY, player.y + BombRange + 1); j++) {
+ world.matrix[j][player.x] = 0;
+ }
+
+ for(int j = max(0, player.x - BombRange); j < min(WorldSizeX, player.x + BombRange + 1); j++) {
+ world.matrix[player.y][j] = 0;
+ }
+
+ world.enemies_count = 0;
+ for(int j = 0; j < rand() % 4 + world.level / 5; j++) {
+ Enemy enemy;
+ enemy.x = 4 + rand() % 7;
+ enemy.y = rand() % 6;
+ enemy.last = 0;
+ enemy.side = 1;
+ enemy.level = 0;
+
+ world.enemies[j] = enemy;
+ world.enemies_count++;
+
+ for(int m = max(0, world.enemies[j].y - BombRange);
+ m < min(WorldSizeY, world.enemies[j].y + BombRange + 1);
+ m++) {
+ world.matrix[m][world.enemies[j].x] = 0;
+ }
+
+ for(int m = max(0, world.enemies[j].x - BombRange);
+ m < min(WorldSizeX, world.enemies[j].x + BombRange + 1);
+ m++) {
+ world.matrix[world.enemies[j].y][m] = 0;
+ }
+ }
+ world.matrix[world.endy][world.endx] = 1;
+}
+
+const NotificationSequence end = {
+ &message_vibro_on,
+
+ &message_note_ds4,
+ &message_delay_10,
+ &message_sound_off,
+ &message_delay_10,
+
+ &message_note_ds4,
+ &message_delay_10,
+ &message_sound_off,
+ &message_delay_10,
+
+ &message_note_ds4,
+ &message_delay_10,
+ &message_sound_off,
+ &message_delay_10,
+
+ &message_vibro_off,
+ NULL,
+};
+
+static const NotificationSequence bomb2 = {
+ &message_vibro_on,
+ &message_delay_25,
+ &message_vibro_off,
+ NULL,
+};
+
+static const NotificationSequence bomb_explore = {
+ &message_vibro_on,
+ &message_delay_50,
+ &message_vibro_off,
+ NULL,
+};
+
+static const NotificationSequence vibr1 = {
+ &message_vibro_on,
+ &message_delay_10,
+ &message_vibro_off,
+ &message_delay_10,
+ &message_vibro_on,
+ &message_delay_10,
+ &message_vibro_off,
+ &message_delay_10,
+
+ NULL,
+};
+
+void intToStr(int num, char* str) {
+ int i = 0, sign = 0;
+
+ if(num < 0) {
+ num = -num;
+ sign = 1;
+ }
+
+ do {
+ str[i++] = num % 10 + '0';
+ num /= 10;
+ } while(num > 0);
+
+ if(sign) {
+ str[i++] = '-';
+ }
+
+ str[i] = '\0';
+
+ // Reverse the string
+ int j, len = i;
+ char temp;
+ for(j = 0; j < len / 2; j++) {
+ temp = str[j];
+ str[j] = str[len - j - 1];
+ str[len - j - 1] = temp;
+ }
+}
+
+bool BFS() {
+ // Initialize visited array and queue
+ int visited[WorldSizeY][WorldSizeX] = {0};
+ Queue q = {.front = 0, .rear = 0};
+ // Mark the starting cell as visited and enqueue it
+ visited[world.player->y][world.player->x] = 1;
+ Cell startCell = {.row = world.player->y, .col = world.player->x};
+ enqueue(&q, startCell);
+ // Traverse the field
+ while(!is_empty(&q)) {
+ // Dequeue a cell from the queue
+ Cell currentCell = dequeue(&q);
+ // Check if the current cell is the destination cell
+ if(currentCell.row == world.endy && currentCell.col == world.endx) {
+ return true;
+ }
+ // Check the neighboring cells
+ for(int rowOffset = -1; rowOffset <= 1; rowOffset++) {
+ for(int colOffset = -1; colOffset <= 1; colOffset++) {
+ // Skip diagonals and the current cell
+ if(rowOffset == 0 && colOffset == 0) {
+ continue;
+ }
+ if(rowOffset != 0 && colOffset != 0) {
+ continue;
+ }
+ // Calculate the row and column of the neighboring cell
+ int neighborRow = currentCell.row + rowOffset;
+ int neighborCol = currentCell.col + colOffset;
+ // Skip out-of-bounds cells and already visited cells
+ if(neighborRow < 0 || neighborRow >= WorldSizeY || neighborCol < 0 ||
+ neighborCol >= WorldSizeX) {
+ continue;
+ }
+ if(visited[neighborRow][neighborCol]) {
+ continue;
+ }
+ // Mark the neighboring cell as visited and enqueue it
+ if(world.matrix[neighborRow][neighborCol] != 2) {
+ visited[neighborRow][neighborCol] = 1;
+ Cell neighborCell = {.row = neighborRow, .col = neighborCol};
+ enqueue(&q, neighborCell);
+ }
+ }
+ }
+ }
+ return false;
+}
+
+static void draw_callback(Canvas* canvas, void* ctx) {
+ furi_assert(ctx);
+ const BomberState* bomber_state = ctx;
+
+ furi_mutex_acquire(bomber_state->mutex, FuriWaitForever);
+ if(!BFS()) {
+ init();
+ }
+ canvas_clear(canvas);
+
+ canvas_draw_icon(canvas, world.endx * 10 + 4, world.endy * 10 + 2, &I_end);
+
+ if(world.running) {
+ for(size_t i = 0; i < WorldSizeY; i++) {
+ for(size_t j = 0; j < WorldSizeX; j++) {
+ switch(world.matrix[i][j]) {
+ case 0:
+ break;
+ case 1:
+ canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_box);
+ break;
+ case 2:
+ canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_unbreakbox);
+ break;
+ case 3:
+ canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_bomb0);
+ break;
+ case 4:
+ canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_bomb1);
+ break;
+ case 5:
+ canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_bomb2);
+ break;
+ case 6:
+ canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_explore);
+ world.matrix[i][j] = 0;
+ break;
+ }
+ }
+ }
+
+ if(world.player->side) {
+ canvas_draw_icon(
+ canvas, world.player->x * 10 + 4, world.player->y * 10 + 2, &I_playerright);
+ } else {
+ canvas_draw_icon(
+ canvas, world.player->x * 10 + 4, world.player->y * 10 + 2, &I_playerleft);
+ }
+
+ for(int i = 0; i < world.enemies_count; i++) {
+ if(world.enemies[i].level > 0) {
+ canvas_draw_icon(
+ canvas, world.enemies[i].x * 10 + 4, world.enemies[i].y * 10 + 2, &I_enemy1);
+ } else {
+ if(world.enemies[i].side) {
+ canvas_draw_icon(
+ canvas,
+ world.enemies[i].x * 10 + 4,
+ world.enemies[i].y * 10 + 2,
+ &I_enemyright);
+ } else {
+ canvas_draw_icon(
+ canvas,
+ world.enemies[i].x * 10 + 4,
+ world.enemies[i].y * 10 + 2,
+ &I_enemyleft);
+ }
+ }
+ }
+ } else {
+ canvas_set_font(canvas, FontPrimary);
+ if(world.player->x == world.endx && world.player->y == world.endy) {
+ if(world.level == 20) {
+ canvas_draw_str(canvas, 30, 35, "You win!");
+ } else {
+ canvas_draw_str(canvas, 30, 35, "Next level!");
+ char str[20];
+ intToStr(world.level, str);
+ canvas_draw_str(canvas, 90, 35, str);
+ }
+
+ } else {
+ canvas_draw_str(canvas, 30, 35, "You died :(");
+ }
+ }
+
+ furi_mutex_release(bomber_state->mutex);
+}
+
+static void input_callback(InputEvent* input_event, void* ctx) {
+ // ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ, ΡΡΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ Π½Π΅ Π½ΡΠ»Π΅Π²ΠΎΠΉ
+ furi_assert(ctx);
+ FuriMessageQueue* event_queue = ctx;
+
+ furi_message_queue_put(event_queue, input_event, FuriWaitForever);
+}
+
+int32_t bomberduck_app(void* p) {
+ UNUSED(p);
+
+ // Π’Π΅ΠΊΡΡΠ΅Π΅ ΡΠΎΠ±ΡΡΠΈΠ΅ ΡΠΈΠΏΠ° InputEvent
+ InputEvent event;
+ // ΠΡΠ΅ΡΠ΅Π΄Ρ ΡΠΎΠ±ΡΡΠΈΠΉ Π½Π° 8 ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΡΠ°Π·ΠΌΠ΅ΡΠ° InputEvent
+ FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
+
+ BomberState* bomber_state = malloc(sizeof(BomberState));
+
+ bomber_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); // Alloc Mutex
+ if(!bomber_state->mutex) {
+ FURI_LOG_E("BomberDuck", "cannot create mutex\r\n");
+ furi_message_queue_free(event_queue);
+ free(bomber_state);
+ return 255;
+ }
+
+ // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π½ΠΎΠ²ΡΠΉ view port
+ ViewPort* view_port = view_port_alloc();
+ // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ callback ΠΎΡΡΠΈΡΠΎΠ²ΠΊΠΈ, Π±Π΅Π· ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡΠ°
+ view_port_draw_callback_set(view_port, draw_callback, bomber_state);
+ // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ callback Π½Π°ΠΆΠ°ΡΠΈΠΉ Π½Π° ΠΊΠ»Π°Π²ΠΈΡΠΈ, Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡΠ° ΠΏΠ΅ΡΠ΅Π΄Π°Π΅ΠΌ
+ // Π½Π°ΡΡ ΠΎΡΠ΅ΡΠ΅Π΄Ρ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠΉ, ΡΡΠΎΠ± Π·Π°ΠΏΠΈΡ
ΠΈΠ²Π°ΡΡ Π² Π½Π΅Ρ ΡΡΠΈ ΡΠΎΠ±ΡΡΠΈΡ
+ view_port_input_callback_set(view_port, input_callback, event_queue);
+
+ // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ GUI ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
+ Gui* gui = furi_record_open(RECORD_GUI);
+ // ΠΠΎΠ΄ΠΊΠ»ΡΡΠ°Π΅ΠΌ view port ΠΊ GUI Π² ΠΏΠΎΠ»Π½ΠΎΡΠΊΡΠ°Π½Π½ΠΎΠΌ ΡΠ΅ΠΆΠΈΠΌΠ΅
+ gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+ NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
+ notification_message_block(notification, &sequence_display_backlight_enforce_on);
+
+ init();
+
+ // ΠΠ΅ΡΠΊΠΎΠ½Π΅ΡΠ½ΡΠΉ ΡΠΈΠΊΠ» ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΎΡΠ΅ΡΠ΅Π΄ΠΈ ΡΠΎΠ±ΡΡΠΈΠΉ
+ while(1) {
+ if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
+ furi_mutex_acquire(bomber_state->mutex, FuriWaitForever);
+ // ΠΡΠ»ΠΈ Π½Π°ΠΆΠ°ΡΠ° ΠΊΠ½ΠΎΠΏΠΊΠ° "Π½Π°Π·Π°Π΄", ΡΠΎ Π²ΡΡ
ΠΎΠ΄ΠΈΠΌ ΠΈΠ· ΡΠΈΠΊΠ»Π°, Π° ΡΠ»Π΅Π΄ΠΎΠ²Π°ΡΠ΅Π»ΡΠ½ΠΎ ΠΈ ΠΈΠ· ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
+
+ if(event.type == InputTypePress) {
+ if(event.key == InputKeyOk) {
+ if(world.running) {
+ if(world.matrix[world.player->y][world.player->x] == 0 &&
+ world.bombs_count < 2) {
+ notification_message(notification, &bomb2);
+ world.matrix[world.player->y][world.player->x] = 3;
+ Bomb bomb = {world.player->x, world.player->y, furi_get_tick()};
+ world.bombs[world.bombs_count] = bomb;
+ world.bombs_count++;
+ }
+ } else {
+ init();
+ }
+ }
+ if(world.running) {
+ if(event.key == InputKeyUp) {
+ if(world.player->y > 0 &&
+ world.matrix[world.player->y - 1][world.player->x] == 0)
+ world.player->y--;
+ }
+ if(event.key == InputKeyDown) {
+ if(world.player->y < WorldSizeY - 1 &&
+ world.matrix[world.player->y + 1][world.player->x] == 0)
+ world.player->y++;
+ }
+ if(event.key == InputKeyLeft) {
+ world.player->side = 0;
+ if(world.player->x > 0 &&
+ world.matrix[world.player->y][world.player->x - 1] == 0)
+ world.player->x--;
+ }
+ if(event.key == InputKeyRight) {
+ world.player->side = 1;
+ if(world.player->x < WorldSizeX - 1 &&
+ world.matrix[world.player->y][world.player->x + 1] == 0)
+ world.player->x++;
+ }
+ }
+ } else if(event.type == InputTypeLong) {
+ if(event.key == InputKeyBack) {
+ break;
+ }
+ }
+ }
+ if(world.running) {
+ if(world.player->x == world.endx && world.player->y == world.endy) {
+ notification_message(notification, &end);
+ world.running = 0;
+ world.level += 1;
+ }
+ for(int i = 0; i < world.bombs_count; i++) {
+ if(furi_get_tick() - world.bombs[i].planted >
+ (unsigned long)max((3000 - world.level * 150), 1000)) {
+ vibration = false;
+ world.matrix[world.bombs[i].y][world.bombs[i].x] = 6;
+ notification_message(notification, &bomb_explore);
+
+ for(int j = max(0, world.bombs[i].y - BombRange);
+ j < min(WorldSizeY, world.bombs[i].y + BombRange + 1);
+ j++) {
+ if(world.matrix[j][world.bombs[i].x] != 2) {
+ world.matrix[j][world.bombs[i].x] = 6;
+ if(j == world.player->y && world.bombs[i].x == world.player->x) {
+ notification_message(notification, &end);
+ world.running = 0;
+ }
+ for(int e = 0; e < world.enemies_count; e++) {
+ if(j == world.enemies[e].y &&
+ world.bombs[i].x == world.enemies[e].x) {
+ if(world.enemies[e].level > 0) {
+ world.enemies[e].level--;
+ } else {
+ for(int l = e; l < world.enemies_count - 1; l++) {
+ world.enemies[l] = world.enemies[l + 1];
+ }
+ world.enemies_count--;
+ }
+ }
+ }
+ }
+ }
+
+ for(int j = max(0, world.bombs[i].x - BombRange);
+ j < min(WorldSizeX, world.bombs[i].x + BombRange + 1);
+ j++) {
+ if(world.matrix[world.bombs[i].y][j] != 2) {
+ world.matrix[world.bombs[i].y][j] = 6;
+ if(world.bombs[i].y == world.player->y && j == world.player->x) {
+ notification_message(notification, &end);
+ world.running = 0;
+ }
+ for(int e = 0; e < world.enemies_count; e++) {
+ if(world.bombs[i].y == world.enemies[e].y &&
+ j == world.enemies[e].x) {
+ if(world.enemies[e].level > 0) {
+ world.enemies[e].level--;
+ } else {
+ for(int l = e; l < world.enemies_count - 1; l++) {
+ world.enemies[l] = world.enemies[l + 1];
+ }
+ world.enemies_count--;
+ }
+ }
+ }
+ }
+ }
+
+ for(int j = i; j < world.bombs_count - 1; j++) {
+ world.bombs[j] = world.bombs[j + 1];
+ }
+ world.bombs_count--;
+ } else if(
+ furi_get_tick() - world.bombs[i].planted >
+ (unsigned long)max((3000 - world.level * 150) * 2 / 3, 666) &&
+ world.matrix[world.bombs[i].y][world.bombs[i].x] != 5) {
+ world.matrix[world.bombs[i].y][world.bombs[i].x] = 5;
+ vibration = true;
+
+ } else if(
+ furi_get_tick() - world.bombs[i].planted >
+ (unsigned long)max((3000 - world.level * 150) / 3, 333) &&
+ world.matrix[world.bombs[i].y][world.bombs[i].x] != 4) {
+ world.matrix[world.bombs[i].y][world.bombs[i].x] = 4;
+ }
+ }
+ for(int e = 0; e < world.enemies_count; e++) {
+ if(world.player->y == world.enemies[e].y &&
+ world.player->x == world.enemies[e].x) {
+ notification_message(notification, &end);
+ world.running = 0;
+ }
+ }
+
+ for(int e = 0; e < world.enemies_count; e++) {
+ if(world.enemies[e].level > 0) {
+ if(furi_get_tick() - world.enemies[e].last >
+ (unsigned long)max((2000 - world.level * 100), 1000)) {
+ world.enemies[e].last = furi_get_tick();
+ int move = rand() % 4;
+ switch(move) {
+ case 0:
+ if(world.enemies[e].y > 0 &&
+ world.matrix[world.enemies[e].y - 1][world.enemies[e].x] != 2)
+ world.enemies[e].y--;
+ break;
+ case 1:
+ if(world.enemies[e].y < WorldSizeY - 1 &&
+ world.matrix[world.enemies[e].y + 1][world.enemies[e].x] != 2)
+ world.enemies[e].y++;
+ break;
+ case 2:
+ world.enemies[e].side = 0;
+ if(world.enemies[e].x > 0 &&
+ world.matrix[world.enemies[e].y][world.enemies[e].x - 1] != 2)
+ world.enemies[e].x--;
+ break;
+ case 3:
+ world.enemies[e].side = 1;
+ if(world.enemies[e].x < WorldSizeX - 1 &&
+ world.matrix[world.enemies[e].y][world.enemies[e].x + 1] != 2)
+ world.enemies[e].x++;
+ default:
+ break;
+ }
+ }
+ } else {
+ if(furi_get_tick() - world.enemies[e].last >
+ (unsigned long)max((1000 - world.level * 50), 500)) {
+ world.enemies[e].last = furi_get_tick();
+ int move = rand() % 4;
+ switch(move) {
+ case 0:
+ if(world.enemies[e].y > 0 &&
+ world.matrix[world.enemies[e].y - 1][world.enemies[e].x] == 0)
+ world.enemies[e].y--;
+ break;
+ case 1:
+ if(world.enemies[e].y < WorldSizeY - 1 &&
+ world.matrix[world.enemies[e].y + 1][world.enemies[e].x] == 0)
+ world.enemies[e].y++;
+ break;
+ case 2:
+ world.enemies[e].side = 0;
+ if(world.enemies[e].x > 0 &&
+ world.matrix[world.enemies[e].y][world.enemies[e].x - 1] == 0)
+ world.enemies[e].x--;
+ break;
+ case 3:
+ world.enemies[e].side = 1;
+ if(world.enemies[e].x < WorldSizeX - 1 &&
+ world.matrix[world.enemies[e].y][world.enemies[e].x + 1] == 0)
+ world.enemies[e].x++;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ for(int e = 0; e < world.enemies_count; e++) {
+ for(int h = e + 1; h < world.enemies_count; h++) {
+ if(world.enemies[e].y == world.enemies[h].y &&
+ world.enemies[e].x == world.enemies[h].x) {
+ world.enemies[h].level++;
+ for(int l = e; l < world.enemies_count - 1; l++) {
+ world.enemies[l] = world.enemies[l + 1];
+ }
+ world.enemies_count--;
+ }
+ }
+ }
+ if(vibration) {
+ notification_message(notification, &vibr1);
+ }
+ }
+
+ view_port_update(view_port);
+ furi_mutex_release(bomber_state->mutex);
+ }
+
+ // Return to normal backlight settings
+ notification_message_block(notification, &sequence_display_backlight_enforce_auto);
+ furi_record_close(RECORD_NOTIFICATION);
+ // Π‘ΠΏΠ΅ΡΠΈΠ°Π»ΡΠ½Π°Ρ ΠΎΡΠΈΡΡΠΊΠ° ΠΏΠ°ΠΌΡΡΠΈ, Π·Π°Π½ΠΈΠΌΠ°Π΅ΠΌΠΎΠΉ ΠΎΡΠ΅ΡΠ΅Π΄ΡΡ
+ furi_message_queue_free(event_queue);
+
+ // Π§ΠΈΡΡΠΈΠΌ ΡΠΎΠ·Π΄Π°Π½Π½ΡΠ΅ ΠΎΠ±ΡΠ΅ΠΊΡΡ, ΡΠ²ΡΠ·Π°Π½Π½ΡΠ΅ Ρ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΠΎΠΌ
+ gui_remove_view_port(gui, view_port);
+ view_port_free(view_port);
+
+ furi_mutex_free(bomber_state->mutex);
+ furi_record_close(RECORD_GUI);
+ free(bomber_state);
+
+ return 0;
+}
diff --git a/applications/external/bpmtapper/img/screenshot.png b/applications/external/bpmtapper/img/screenshot.png
deleted file mode 100644
index fbba2aad9..000000000
Binary files a/applications/external/bpmtapper/img/screenshot.png and /dev/null differ
diff --git a/applications/external/brainfuck/worker.c b/applications/external/brainfuck/worker.c
index 584bb9fb8..8fed94b40 100644
--- a/applications/external/brainfuck/worker.c
+++ b/applications/external/brainfuck/worker.c
@@ -111,7 +111,7 @@ void rShift() {
memset((tmp + stackSize) - BF_STACK_STEP_SIZE, 0x00, BF_STACK_STEP_SIZE);
bfStack = (uint8_t*)tmp;
- };
+ }
if(stackPtr > stackSizeReal) {
stackSizeReal = stackPtr;
}
diff --git a/applications/external/calculator/calculator.c b/applications/external/calculator/calculator.c
index 1ca1d3a86..cc12cb7d1 100644
--- a/applications/external/calculator/calculator.c
+++ b/applications/external/calculator/calculator.c
@@ -199,7 +199,7 @@ void generate_calculator_layout(Canvas* canvas) {
canvas_draw_str(canvas, 19, 118, " 0");
canvas_draw_str(canvas, 35, 118, " .");
canvas_draw_str(canvas, 51, 118, " =");
-};
+}
void calculator_draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
diff --git a/applications/external/cli_bridge/internal_defs.h b/applications/external/cli_bridge/internal_defs.h
index 9840d008b..09fe6169c 100644
--- a/applications/external/cli_bridge/internal_defs.h
+++ b/applications/external/cli_bridge/internal_defs.h
@@ -79,8 +79,8 @@ typedef struct {
void* view_dispatcher;
void* primary_menu;
- void* plugins_menu;
- void* debug_menu;
+ // void* plugins_menu;
+ // void* debug_menu;
void* settings_menu;
volatile uint8_t lock_count;
diff --git a/applications/external/doom/assets/screenshot1.png b/applications/external/doom/assets/screenshot1.png
deleted file mode 100644
index 1ecb073a6..000000000
Binary files a/applications/external/doom/assets/screenshot1.png and /dev/null differ
diff --git a/applications/external/doom/assets/screenshot2.png b/applications/external/doom/assets/screenshot2.png
deleted file mode 100644
index 216b699de..000000000
Binary files a/applications/external/doom/assets/screenshot2.png and /dev/null differ
diff --git a/applications/external/doom/assets/screenshot3.png b/applications/external/doom/assets/screenshot3.png
deleted file mode 100644
index b5aec03fd..000000000
Binary files a/applications/external/doom/assets/screenshot3.png and /dev/null differ
diff --git a/applications/external/doom/doom_music_player_worker.c b/applications/external/doom/doom_music_player_worker.c
index e81549625..c8b9bb9db 100644
--- a/applications/external/doom/doom_music_player_worker.c
+++ b/applications/external/doom/doom_music_player_worker.c
@@ -393,7 +393,7 @@ bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const
if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_E(TAG, "Unable to open file");
break;
- };
+ }
uint16_t ret = 0;
do {
diff --git a/applications/external/flipfrid/application.fam b/applications/external/flipfrid/application.fam
index e2482e0d1..f66cc7b73 100644
--- a/applications/external/flipfrid/application.fam
+++ b/applications/external/flipfrid/application.fam
@@ -4,7 +4,7 @@ App(
apptype=FlipperAppType.EXTERNAL,
entry_point="flipfrid_start",
requires=["gui", "storage", "dialogs", "input", "notification"],
- stack_size=1 * 1024,
+ stack_size=2 * 1024,
order=180,
fap_icon="rfid_10px.png",
fap_category="Tools",
diff --git a/applications/external/flipfrid/flipfrid.c b/applications/external/flipfrid/flipfrid.c
index ff52ab160..172a98ef3 100644
--- a/applications/external/flipfrid/flipfrid.c
+++ b/applications/external/flipfrid/flipfrid.c
@@ -61,6 +61,16 @@ FlipFridState* flipfrid_alloc() {
flipfrid->proto_name = furi_string_alloc();
flipfrid->data_str = furi_string_alloc();
+ flipfrid->main_menu_items[0] = furi_string_alloc_set("Default Values");
+ flipfrid->main_menu_items[1] = furi_string_alloc_set("BF Customer ID");
+ flipfrid->main_menu_items[2] = furi_string_alloc_set("Load File");
+ flipfrid->main_menu_items[3] = furi_string_alloc_set("Load UIDs from file");
+
+ flipfrid->main_menu_proto_items[0] = furi_string_alloc_set("EM4100");
+ flipfrid->main_menu_proto_items[1] = furi_string_alloc_set("HIDProx");
+ flipfrid->main_menu_proto_items[2] = furi_string_alloc_set("PAC/Stanley");
+ flipfrid->main_menu_proto_items[3] = furi_string_alloc_set("H10301");
+
flipfrid->previous_scene = NoneScene;
flipfrid->current_scene = SceneEntryPoint;
flipfrid->is_running = true;
@@ -103,8 +113,13 @@ void flipfrid_free(FlipFridState* flipfrid) {
furi_string_free(flipfrid->proto_name);
furi_string_free(flipfrid->data_str);
- free(flipfrid->data);
- free(flipfrid->payload);
+ for(uint32_t i = 0; i < 4; i++) {
+ furi_string_free(flipfrid->main_menu_items[i]);
+ }
+
+ for(uint32_t i = 0; i < 4; i++) {
+ furi_string_free(flipfrid->main_menu_proto_items[i]);
+ }
// The rest
free(flipfrid);
diff --git a/applications/external/flipfrid/flipfrid.h b/applications/external/flipfrid/flipfrid.h
index e4122054b..0ee8aa320 100644
--- a/applications/external/flipfrid/flipfrid.h
+++ b/applications/external/flipfrid/flipfrid.h
@@ -75,6 +75,8 @@ typedef struct {
FlipFridProtos proto;
FuriString* attack_name;
FuriString* proto_name;
+ FuriString* main_menu_items[4];
+ FuriString* main_menu_proto_items[4];
DialogsApp* dialogs;
FuriString* notification_msg;
diff --git a/applications/external/flipfrid/scene/flipfrid_scene_entrypoint.c b/applications/external/flipfrid/scene/flipfrid_scene_entrypoint.c
index 24c19dc4c..f4b39aa66 100644
--- a/applications/external/flipfrid/scene/flipfrid_scene_entrypoint.c
+++ b/applications/external/flipfrid/scene/flipfrid_scene_entrypoint.c
@@ -1,8 +1,5 @@
#include "flipfrid_scene_entrypoint.h"
-FuriString* main_menu_items[4];
-FuriString* main_menu_proto_items[4];
-
void flipfrid_scene_entrypoint_menu_callback(
FlipFridState* context,
uint32_t index,
@@ -68,31 +65,14 @@ void flipfrid_scene_entrypoint_on_enter(FlipFridState* context) {
menu_items[i] = furi_string_alloc();
}*/
- main_menu_items[0] = furi_string_alloc_set("Default Values");
- main_menu_items[1] = furi_string_alloc_set("BF Customer ID");
- main_menu_items[2] = furi_string_alloc_set("Load File");
- main_menu_items[3] = furi_string_alloc_set("Load UIDs from file");
-
context->menu_proto_index = 0;
/*for(uint32_t i = 0; i < 4; i++) {
menu_proto_items[i] = furi_string_alloc();
}*/
-
- main_menu_proto_items[0] = furi_string_alloc_set("EM4100");
- main_menu_proto_items[1] = furi_string_alloc_set("HIDProx");
- main_menu_proto_items[2] = furi_string_alloc_set("PAC/Stanley");
- main_menu_proto_items[3] = furi_string_alloc_set("H10301");
}
void flipfrid_scene_entrypoint_on_exit(FlipFridState* context) {
UNUSED(context);
- for(uint32_t i = 0; i < 4; i++) {
- furi_string_free(main_menu_items[i]);
- }
-
- for(uint32_t i = 0; i < 4; i++) {
- furi_string_free(main_menu_proto_items[i]);
- }
}
void flipfrid_scene_entrypoint_on_tick(FlipFridState* context) {
@@ -145,73 +125,77 @@ void flipfrid_scene_entrypoint_on_draw(Canvas* canvas, FlipFridState* context) {
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
- if(main_menu_items[context->menu_index] != NULL) {
- if(context->menu_index > FlipFridAttackDefaultValues) {
- canvas_set_font(canvas, FontSecondary);
+ if(context->main_menu_items != NULL) {
+ if(context->main_menu_items[context->menu_index] != NULL) {
+ if(context->menu_index > FlipFridAttackDefaultValues) {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str_aligned(
+ canvas,
+ 64,
+ 24,
+ AlignCenter,
+ AlignTop,
+ furi_string_get_cstr(context->main_menu_items[context->menu_index - 1]));
+ }
+
+ canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas,
64,
- 24,
+ 36,
AlignCenter,
AlignTop,
- furi_string_get_cstr(main_menu_items[context->menu_index - 1]));
- }
+ furi_string_get_cstr(context->main_menu_items[context->menu_index]));
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(
- canvas,
- 64,
- 36,
- AlignCenter,
- AlignTop,
- furi_string_get_cstr(main_menu_items[context->menu_index]));
+ if(context->menu_index < FlipFridAttackLoadFileCustomUids) {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str_aligned(
+ canvas,
+ 64,
+ 48,
+ AlignCenter,
+ AlignTop,
+ furi_string_get_cstr(context->main_menu_items[context->menu_index + 1]));
+ }
- if(context->menu_index < FlipFridAttackLoadFileCustomUids) {
- canvas_set_font(canvas, FontSecondary);
+ if(context->menu_proto_index > EM4100) {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str_aligned(
+ canvas,
+ 64,
+ -12,
+ AlignCenter,
+ AlignTop,
+ furi_string_get_cstr(
+ context->main_menu_proto_items[context->menu_proto_index - 1]));
+ }
+
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str_aligned(canvas, 27, 4, AlignCenter, AlignTop, "<");
+
+ canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas,
64,
- 48,
+ 4,
AlignCenter,
AlignTop,
- furi_string_get_cstr(main_menu_items[context->menu_index + 1]));
- }
+ furi_string_get_cstr(context->main_menu_proto_items[context->menu_proto_index]));
- if(context->menu_proto_index > EM4100) {
- canvas_set_font(canvas, FontSecondary);
- canvas_draw_str_aligned(
- canvas,
- 64,
- -12,
- AlignCenter,
- AlignTop,
- furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index - 1]));
- }
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str_aligned(canvas, 101, 4, AlignCenter, AlignTop, ">");
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(canvas, 27, 4, AlignCenter, AlignTop, "<");
-
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(
- canvas,
- 64,
- 4,
- AlignCenter,
- AlignTop,
- furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index]));
-
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(canvas, 101, 4, AlignCenter, AlignTop, ">");
-
- if(context->menu_proto_index < H10301) {
- canvas_set_font(canvas, FontSecondary);
- canvas_draw_str_aligned(
- canvas,
- 64,
- -12,
- AlignCenter,
- AlignTop,
- furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index + 1]));
+ if(context->menu_proto_index < H10301) {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str_aligned(
+ canvas,
+ 64,
+ -12,
+ AlignCenter,
+ AlignTop,
+ furi_string_get_cstr(
+ context->main_menu_proto_items[context->menu_proto_index + 1]));
+ }
}
}
}
\ No newline at end of file
diff --git a/applications/external/flipfrid/scene/flipfrid_scene_run_attack.c b/applications/external/flipfrid/scene/flipfrid_scene_run_attack.c
index 6c726832a..225752559 100644
--- a/applications/external/flipfrid/scene/flipfrid_scene_run_attack.c
+++ b/applications/external/flipfrid/scene/flipfrid_scene_run_attack.c
@@ -356,7 +356,7 @@ void flipfrid_scene_run_attack_on_tick(FlipFridState* context) {
stream_rewind(context->uids_stream);
end_of_list = true;
break;
- };
+ }
if(furi_string_get_char(context->data_str, 0) == '#') continue;
if(furi_string_size(context->data_str) != 11) break;
break;
@@ -370,7 +370,7 @@ void flipfrid_scene_run_attack_on_tick(FlipFridState* context) {
notification_message(context->notify, &sequence_blink_stop);
notification_message(context->notify, &sequence_error);
break;
- };
+ }
// string is valid, parse it in context->payload
for(uint8_t i = 0; i < 5; i++) {
@@ -394,7 +394,7 @@ void flipfrid_scene_run_attack_on_tick(FlipFridState* context) {
stream_rewind(context->uids_stream);
end_of_list = true;
break;
- };
+ }
if(furi_string_get_char(context->data_str, 0) == '#') continue;
if(furi_string_size(context->data_str) != 9) break;
break;
@@ -408,7 +408,7 @@ void flipfrid_scene_run_attack_on_tick(FlipFridState* context) {
notification_message(context->notify, &sequence_blink_stop);
notification_message(context->notify, &sequence_error);
break;
- };
+ }
// string is valid, parse it in context->payload
for(uint8_t i = 0; i < 4; i++) {
diff --git a/applications/external/geiger/flipper_geiger.c b/applications/external/geiger/flipper_geiger.c
index a5503eb90..9c3d0d3fc 100644
--- a/applications/external/geiger/flipper_geiger.c
+++ b/applications/external/geiger/flipper_geiger.c
@@ -39,33 +39,53 @@ typedef struct {
static void draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
- mutexStruct displayStruct;
- mutexStruct* geigerMutex = ctx;
- furi_mutex_acquire(geigerMutex->mutex, FuriWaitForever);
- memcpy(&displayStruct, geigerMutex, sizeof(mutexStruct));
- furi_mutex_release(geigerMutex->mutex);
+ mutexStruct* mutexVal = ctx;
+ mutexStruct mutexDraw;
+ furi_mutex_acquire(mutexVal->mutex, FuriWaitForever);
+ memcpy(&mutexDraw, mutexVal, sizeof(mutexStruct));
+ furi_mutex_release(mutexVal->mutex);
char buffer[32];
- if(displayStruct.data == 0)
- snprintf(
- buffer, sizeof(buffer), "%ld cps - %ld cpm", displayStruct.cps, displayStruct.cpm);
- else if(displayStruct.data == 1)
+ if(mutexDraw.data == 0)
+ snprintf(buffer, sizeof(buffer), "%ld cps - %ld cpm", mutexDraw.cps, mutexDraw.cpm);
+ else if(mutexDraw.data == 1)
snprintf(
buffer,
sizeof(buffer),
"%ld cps - %.2f uSv/h",
- displayStruct.cps,
- ((double)displayStruct.cpm * (double)CONVERSION_FACTOR));
- else
+ mutexDraw.cps,
+ ((double)mutexDraw.cpm * (double)CONVERSION_FACTOR));
+ else if(mutexDraw.data == 2)
snprintf(
buffer,
sizeof(buffer),
"%ld cps - %.2f mSv/y",
- displayStruct.cps,
- (((double)displayStruct.cpm * (double)CONVERSION_FACTOR)) * (double)8.76);
+ mutexDraw.cps,
+ (((double)mutexDraw.cpm * (double)CONVERSION_FACTOR)) * (double)8.76);
+ else if(mutexDraw.data == 3)
+ snprintf(
+ buffer,
+ sizeof(buffer),
+ "%ld cps - %.4f Rad/h",
+ mutexDraw.cps,
+ ((double)mutexDraw.cpm * (double)CONVERSION_FACTOR) / (double)10000);
+ else if(mutexDraw.data == 4)
+ snprintf(
+ buffer,
+ sizeof(buffer),
+ "%ld cps - %.2f mR/h",
+ mutexDraw.cps,
+ ((double)mutexDraw.cpm * (double)CONVERSION_FACTOR) / (double)10);
+ else
+ snprintf(
+ buffer,
+ sizeof(buffer),
+ "%ld cps - %.2f uR/h",
+ mutexDraw.cps,
+ ((double)mutexDraw.cpm * (double)CONVERSION_FACTOR) * (double)100);
for(int i = 0; i < SCREEN_SIZE_X; i += 2) {
- float Y = SCREEN_SIZE_Y - (displayStruct.line[i / 2] * displayStruct.coef);
+ float Y = SCREEN_SIZE_Y - (mutexDraw.line[i / 2] * mutexDraw.coef);
canvas_draw_line(canvas, i, Y, i, SCREEN_SIZE_Y);
canvas_draw_line(canvas, i + 1, Y, i + 1, SCREEN_SIZE_Y);
@@ -103,8 +123,7 @@ static void gpiocallback(void* ctx) {
furi_message_queue_put(queue, &event, 0);
}
-int32_t flipper_geiger_app(void* p) {
- UNUSED(p);
+int32_t flipper_geiger_app() {
EventApp event;
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(EventApp));
@@ -127,7 +146,7 @@ int32_t flipper_geiger_app(void* p) {
}
ViewPort* view_port = view_port_alloc();
- view_port_draw_callback_set(view_port, draw_callback, &mutexVal);
+ view_port_draw_callback_set(view_port, draw_callback, &mutexVal.mutex);
view_port_input_callback_set(view_port, input_callback, event_queue);
furi_hal_gpio_add_int_callback(&gpio_ext_pa7, gpiocallback, event_queue);
@@ -167,7 +186,7 @@ int32_t flipper_geiger_app(void* p) {
if(mutexVal.data != 0)
mutexVal.data--;
else
- mutexVal.data = 2;
+ mutexVal.data = 5;
screenRefresh = 1;
furi_mutex_release(mutexVal.mutex);
@@ -175,7 +194,7 @@ int32_t flipper_geiger_app(void* p) {
event.input.type == InputTypeShort)) {
furi_mutex_acquire(mutexVal.mutex, FuriWaitForever);
- if(mutexVal.data != 2)
+ if(mutexVal.data != 5)
mutexVal.data++;
else
mutexVal.data = 0;
diff --git a/applications/external/gpio_reader_a/GPIO_reader.c b/applications/external/gpioreader_a/GPIO_reader.c
similarity index 100%
rename from applications/external/gpio_reader_a/GPIO_reader.c
rename to applications/external/gpioreader_a/GPIO_reader.c
diff --git a/applications/external/gpio_reader_a/GPIO_reader_item.c b/applications/external/gpioreader_a/GPIO_reader_item.c
similarity index 100%
rename from applications/external/gpio_reader_a/GPIO_reader_item.c
rename to applications/external/gpioreader_a/GPIO_reader_item.c
diff --git a/applications/external/gpio_reader_a/GPIO_reader_item.h b/applications/external/gpioreader_a/GPIO_reader_item.h
similarity index 100%
rename from applications/external/gpio_reader_a/GPIO_reader_item.h
rename to applications/external/gpioreader_a/GPIO_reader_item.h
diff --git a/applications/external/gpio_reader_a/application.fam b/applications/external/gpioreader_a/application.fam
similarity index 100%
rename from applications/external/gpio_reader_a/application.fam
rename to applications/external/gpioreader_a/application.fam
diff --git a/applications/external/gpio_reader_a/icon.png b/applications/external/gpioreader_a/icon.png
similarity index 100%
rename from applications/external/gpio_reader_a/icon.png
rename to applications/external/gpioreader_a/icon.png
diff --git a/applications/external/gpio_reader_b/LICENSE b/applications/external/gpioreader_b/LICENSE
similarity index 100%
rename from applications/external/gpio_reader_b/LICENSE
rename to applications/external/gpioreader_b/LICENSE
diff --git a/applications/external/gpio_reader_b/application.fam b/applications/external/gpioreader_b/application.fam
similarity index 100%
rename from applications/external/gpio_reader_b/application.fam
rename to applications/external/gpioreader_b/application.fam
diff --git a/applications/external/gpio_reader_b/gpio_app.c b/applications/external/gpioreader_b/gpio_app.c
similarity index 100%
rename from applications/external/gpio_reader_b/gpio_app.c
rename to applications/external/gpioreader_b/gpio_app.c
diff --git a/applications/external/gpio_reader_b/gpio_app.h b/applications/external/gpioreader_b/gpio_app.h
similarity index 100%
rename from applications/external/gpio_reader_b/gpio_app.h
rename to applications/external/gpioreader_b/gpio_app.h
diff --git a/applications/external/gpio_reader_b/gpio_app_i.h b/applications/external/gpioreader_b/gpio_app_i.h
similarity index 100%
rename from applications/external/gpio_reader_b/gpio_app_i.h
rename to applications/external/gpioreader_b/gpio_app_i.h
diff --git a/applications/external/gpio_reader_b/gpio_custom_event.h b/applications/external/gpioreader_b/gpio_custom_event.h
similarity index 100%
rename from applications/external/gpio_reader_b/gpio_custom_event.h
rename to applications/external/gpioreader_b/gpio_custom_event.h
diff --git a/applications/external/gpio_reader_b/gpio_item.c b/applications/external/gpioreader_b/gpio_item.c
similarity index 100%
rename from applications/external/gpio_reader_b/gpio_item.c
rename to applications/external/gpioreader_b/gpio_item.c
diff --git a/applications/external/gpio_reader_b/gpio_item.h b/applications/external/gpioreader_b/gpio_item.h
similarity index 100%
rename from applications/external/gpio_reader_b/gpio_item.h
rename to applications/external/gpioreader_b/gpio_item.h
diff --git a/applications/external/gpio_reader_b/icon.png b/applications/external/gpioreader_b/icon.png
similarity index 100%
rename from applications/external/gpio_reader_b/icon.png
rename to applications/external/gpioreader_b/icon.png
diff --git a/applications/external/gpio_reader_b/scenes/gpio_scene.c b/applications/external/gpioreader_b/scenes/gpio_scene.c
similarity index 100%
rename from applications/external/gpio_reader_b/scenes/gpio_scene.c
rename to applications/external/gpioreader_b/scenes/gpio_scene.c
diff --git a/applications/external/gpio_reader_b/scenes/gpio_scene.h b/applications/external/gpioreader_b/scenes/gpio_scene.h
similarity index 100%
rename from applications/external/gpio_reader_b/scenes/gpio_scene.h
rename to applications/external/gpioreader_b/scenes/gpio_scene.h
diff --git a/applications/external/gpio_reader_b/scenes/gpio_scene_config.h b/applications/external/gpioreader_b/scenes/gpio_scene_config.h
similarity index 100%
rename from applications/external/gpio_reader_b/scenes/gpio_scene_config.h
rename to applications/external/gpioreader_b/scenes/gpio_scene_config.h
diff --git a/applications/external/gpio_reader_b/scenes/gpio_scene_reader.c b/applications/external/gpioreader_b/scenes/gpio_scene_reader.c
similarity index 100%
rename from applications/external/gpio_reader_b/scenes/gpio_scene_reader.c
rename to applications/external/gpioreader_b/scenes/gpio_scene_reader.c
diff --git a/applications/external/gpio_reader_b/scenes/gpio_scene_start.c b/applications/external/gpioreader_b/scenes/gpio_scene_start.c
similarity index 100%
rename from applications/external/gpio_reader_b/scenes/gpio_scene_start.c
rename to applications/external/gpioreader_b/scenes/gpio_scene_start.c
diff --git a/applications/external/gpio_reader_b/scenes/gpio_scene_test.c b/applications/external/gpioreader_b/scenes/gpio_scene_test.c
similarity index 100%
rename from applications/external/gpio_reader_b/scenes/gpio_scene_test.c
rename to applications/external/gpioreader_b/scenes/gpio_scene_test.c
diff --git a/applications/external/gpio_reader_b/scenes/gpio_scene_usb_uart.c b/applications/external/gpioreader_b/scenes/gpio_scene_usb_uart.c
similarity index 100%
rename from applications/external/gpio_reader_b/scenes/gpio_scene_usb_uart.c
rename to applications/external/gpioreader_b/scenes/gpio_scene_usb_uart.c
diff --git a/applications/external/gpio_reader_b/scenes/gpio_scene_usb_uart_close_rpc.c b/applications/external/gpioreader_b/scenes/gpio_scene_usb_uart_close_rpc.c
similarity index 100%
rename from applications/external/gpio_reader_b/scenes/gpio_scene_usb_uart_close_rpc.c
rename to applications/external/gpioreader_b/scenes/gpio_scene_usb_uart_close_rpc.c
diff --git a/applications/external/gpio_reader_b/scenes/gpio_scene_usb_uart_config.c b/applications/external/gpioreader_b/scenes/gpio_scene_usb_uart_config.c
similarity index 100%
rename from applications/external/gpio_reader_b/scenes/gpio_scene_usb_uart_config.c
rename to applications/external/gpioreader_b/scenes/gpio_scene_usb_uart_config.c
diff --git a/applications/external/gpio_reader_b/usb_uart_bridge.c b/applications/external/gpioreader_b/usb_uart_bridge.c
similarity index 100%
rename from applications/external/gpio_reader_b/usb_uart_bridge.c
rename to applications/external/gpioreader_b/usb_uart_bridge.c
diff --git a/applications/external/gpio_reader_b/usb_uart_bridge.h b/applications/external/gpioreader_b/usb_uart_bridge.h
similarity index 100%
rename from applications/external/gpio_reader_b/usb_uart_bridge.h
rename to applications/external/gpioreader_b/usb_uart_bridge.h
diff --git a/applications/external/gpio_reader_b/views/gpio_reader.c b/applications/external/gpioreader_b/views/gpio_reader.c
similarity index 100%
rename from applications/external/gpio_reader_b/views/gpio_reader.c
rename to applications/external/gpioreader_b/views/gpio_reader.c
diff --git a/applications/external/gpio_reader_b/views/gpio_reader.h b/applications/external/gpioreader_b/views/gpio_reader.h
similarity index 100%
rename from applications/external/gpio_reader_b/views/gpio_reader.h
rename to applications/external/gpioreader_b/views/gpio_reader.h
diff --git a/applications/external/gpio_reader_b/views/gpio_test.c b/applications/external/gpioreader_b/views/gpio_test.c
similarity index 100%
rename from applications/external/gpio_reader_b/views/gpio_test.c
rename to applications/external/gpioreader_b/views/gpio_test.c
diff --git a/applications/external/gpio_reader_b/views/gpio_test.h b/applications/external/gpioreader_b/views/gpio_test.h
similarity index 100%
rename from applications/external/gpio_reader_b/views/gpio_test.h
rename to applications/external/gpioreader_b/views/gpio_test.h
diff --git a/applications/external/gpio_reader_b/views/gpio_usb_uart.c b/applications/external/gpioreader_b/views/gpio_usb_uart.c
similarity index 98%
rename from applications/external/gpio_reader_b/views/gpio_usb_uart.c
rename to applications/external/gpioreader_b/views/gpio_usb_uart.c
index f71dcccab..14f8c12fe 100644
--- a/applications/external/gpio_reader_b/views/gpio_usb_uart.c
+++ b/applications/external/gpioreader_b/views/gpio_usb_uart.c
@@ -82,7 +82,7 @@ static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) {
if(model->rx_active)
canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180);
else
- canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180);
+ canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpEmpty_14x15, IconRotation180);
}
static bool gpio_usb_uart_input_callback(InputEvent* event, void* context) {
diff --git a/applications/external/gpio_reader_b/views/gpio_usb_uart.h b/applications/external/gpioreader_b/views/gpio_usb_uart.h
similarity index 100%
rename from applications/external/gpio_reader_b/views/gpio_usb_uart.h
rename to applications/external/gpioreader_b/views/gpio_usb_uart.h
diff --git a/applications/external/gps_nmea_uart/gps.c b/applications/external/gps_nmea_uart/gps.c
index 0f4a7a1d5..a0c6ead03 100644
--- a/applications/external/gps_nmea_uart/gps.c
+++ b/applications/external/gps_nmea_uart/gps.c
@@ -139,20 +139,14 @@ int32_t gps_app(void* p) {
switch(event.input.key) {
case InputKeyUp:
gps_uart_deinit_thread(gps_uart);
- switch(gps_uart->baudrate) {
- case GPS_BAUDRATE_9k:
- gps_uart->baudrate = GPS_BAUDRATE_57k;
- break;
- case GPS_BAUDRATE_57k:
- gps_uart->baudrate = GPS_BAUDRATE_115k;
- break;
- case GPS_BAUDRATE_115k:
- gps_uart->baudrate = GPS_BAUDRATE_9k;
- break;
-
- default:
- break;
+ const int baudrate_length =
+ sizeof(gps_baudrates) / sizeof(gps_baudrates[0]);
+ current_gps_baudrate++;
+ if(current_gps_baudrate >= baudrate_length) {
+ current_gps_baudrate = 0;
}
+ gps_uart->baudrate = gps_baudrates[current_gps_baudrate];
+
gps_uart_init_thread(gps_uart);
gps_uart->changing_baudrate = true;
view_port_update(view_port);
diff --git a/applications/external/gps_nmea_uart/gps_uart.c b/applications/external/gps_nmea_uart/gps_uart.c
index 39538b74b..4e66aa284 100644
--- a/applications/external/gps_nmea_uart/gps_uart.c
+++ b/applications/external/gps_nmea_uart/gps_uart.c
@@ -169,7 +169,7 @@ GpsUart* gps_uart_enable() {
gps_uart->notifications = furi_record_open(RECORD_NOTIFICATION);
- gps_uart->baudrate = GPS_BAUDRATE_57k;
+ gps_uart->baudrate = gps_baudrates[current_gps_baudrate];
gps_uart_init_thread(gps_uart);
diff --git a/applications/external/gps_nmea_uart/gps_uart.h b/applications/external/gps_nmea_uart/gps_uart.h
index 5a42b9c58..152f4cd7f 100644
--- a/applications/external/gps_nmea_uart/gps_uart.h
+++ b/applications/external/gps_nmea_uart/gps_uart.h
@@ -3,11 +3,11 @@
#include
#include
-#define GPS_BAUDRATE_9k 9600
-#define GPS_BAUDRATE_57k 57600
-#define GPS_BAUDRATE_115k 115200
#define RX_BUF_SIZE 1024
+static const int gps_baudrates[5] = {9600, 19200, 38400, 57600, 115200};
+static int current_gps_baudrate = 3;
+
typedef struct {
bool valid;
float latitude;
diff --git a/applications/external/hex_viewer/hex_viewer.c b/applications/external/hex_viewer/hex_viewer.c
index 50c34d634..bd4afa1a8 100644
--- a/applications/external/hex_viewer/hex_viewer.c
+++ b/applications/external/hex_viewer/hex_viewer.c
@@ -163,7 +163,7 @@ static bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path) {
FURI_LOG_E(TAG, "Unable to open stream: %s", file_path);
isOk = false;
break;
- };
+ }
hex_viewer->model->file_size = stream_size(hex_viewer->model->stream);
} while(false);
diff --git a/applications/external/hid_app/assets/OutCircles.png b/applications/external/hid_app/assets/OutCircles.png
new file mode 100644
index 000000000..f34d2687a
Binary files /dev/null and b/applications/external/hid_app/assets/OutCircles.png differ
diff --git a/applications/external/hid_app/assets/Pause_icon_9x9.png b/applications/external/hid_app/assets/Pause_icon_9x9.png
new file mode 100644
index 000000000..fe16dc03e
Binary files /dev/null and b/applications/external/hid_app/assets/Pause_icon_9x9.png differ
diff --git a/applications/external/hid_app/assets/Pin_back_arrow_10x10.png b/applications/external/hid_app/assets/Pin_back_arrow_10x10.png
new file mode 100644
index 000000000..e7510fd5d
Binary files /dev/null and b/applications/external/hid_app/assets/Pin_back_arrow_10x10.png differ
diff --git a/applications/external/hid_app/assets/Pressed_Button_19x19.png b/applications/external/hid_app/assets/Pressed_Button_19x19.png
new file mode 100644
index 000000000..42a7dbdcc
Binary files /dev/null and b/applications/external/hid_app/assets/Pressed_Button_19x19.png differ
diff --git a/applications/external/hid_app/assets/S_DOWN.png b/applications/external/hid_app/assets/S_DOWN.png
new file mode 100644
index 000000000..eac667aa0
Binary files /dev/null and b/applications/external/hid_app/assets/S_DOWN.png differ
diff --git a/applications/external/hid_app/assets/S_LEFT.png b/applications/external/hid_app/assets/S_LEFT.png
new file mode 100644
index 000000000..13c9f51b4
Binary files /dev/null and b/applications/external/hid_app/assets/S_LEFT.png differ
diff --git a/applications/external/hid_app/assets/S_RIGHT.png b/applications/external/hid_app/assets/S_RIGHT.png
new file mode 100644
index 000000000..e0ba2afd1
Binary files /dev/null and b/applications/external/hid_app/assets/S_RIGHT.png differ
diff --git a/applications/external/hid_app/assets/S_UP.png b/applications/external/hid_app/assets/S_UP.png
new file mode 100644
index 000000000..e55a03624
Binary files /dev/null and b/applications/external/hid_app/assets/S_UP.png differ
diff --git a/applications/external/hid_app/assets/Voldwn_6x6.png b/applications/external/hid_app/assets/Voldwn_6x6.png
index d7a82a2df..ce487b546 100644
Binary files a/applications/external/hid_app/assets/Voldwn_6x6.png and b/applications/external/hid_app/assets/Voldwn_6x6.png differ
diff --git a/applications/external/hid_app/assets/Volup_8x6.png b/applications/external/hid_app/assets/Volup_8x6.png
index 4b7ec66d6..ee9045f7f 100644
Binary files a/applications/external/hid_app/assets/Volup_8x6.png and b/applications/external/hid_app/assets/Volup_8x6.png differ
diff --git a/applications/external/hid_app/hid.c b/applications/external/hid_app/hid.c
index 949ff63b3..f6b853f9c 100644
--- a/applications/external/hid_app/hid.c
+++ b/applications/external/hid_app/hid.c
@@ -7,10 +7,13 @@
enum HidDebugSubmenuIndex {
HidSubmenuIndexKeynote,
+ HidSubmenuIndexKeynoteVertical,
HidSubmenuIndexKeyboard,
HidSubmenuIndexMedia,
HidSubmenuIndexTikTok,
+ HidSubmenuIndexYTShorts,
HidSubmenuIndexMouse,
+ HidSubmenuIndexMouseClicker,
HidSubmenuIndexMouseJiggler,
};
@@ -20,6 +23,9 @@ static void hid_submenu_callback(void* context, uint32_t index) {
if(index == HidSubmenuIndexKeynote) {
app->view_id = HidViewKeynote;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote);
+ } else if(index == HidSubmenuIndexKeynoteVertical) {
+ app->view_id = HidViewKeynoteVertical;
+ view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynoteVertical);
} else if(index == HidSubmenuIndexKeyboard) {
app->view_id = HidViewKeyboard;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard);
@@ -32,6 +38,12 @@ static void hid_submenu_callback(void* context, uint32_t index) {
} else if(index == HidSubmenuIndexTikTok) {
app->view_id = BtHidViewTikTok;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok);
+ } else if(index == HidSubmenuIndexYTShorts) {
+ app->view_id = BtHidViewYTShorts;
+ view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewYTShorts);
+ } else if(index == HidSubmenuIndexMouseClicker) {
+ app->view_id = HidViewMouseClicker;
+ view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker);
} else if(index == HidSubmenuIndexMouseJiggler) {
app->view_id = HidViewMouseJiggler;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler);
@@ -50,11 +62,14 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con
}
}
hid_keynote_set_connected_status(hid->hid_keynote, connected);
+ hid_keynote_vertical_set_connected_status(hid->hid_keynote_vertical, connected);
hid_keyboard_set_connected_status(hid->hid_keyboard, connected);
hid_media_set_connected_status(hid->hid_media, connected);
hid_mouse_set_connected_status(hid->hid_mouse, connected);
+ hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected);
hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected);
hid_tiktok_set_connected_status(hid->hid_tiktok, connected);
+ hid_ytshorts_set_connected_status(hid->hid_ytshorts, connected);
}
static void hid_dialog_callback(DialogExResult result, void* context) {
@@ -100,6 +115,12 @@ Hid* hid_alloc(HidTransport transport) {
app->device_type_submenu = submenu_alloc();
submenu_add_item(
app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app);
+ submenu_add_item(
+ app->device_type_submenu,
+ "Keynote Vertical",
+ HidSubmenuIndexKeynoteVertical,
+ hid_submenu_callback,
+ app);
submenu_add_item(
app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app);
submenu_add_item(
@@ -113,7 +134,19 @@ Hid* hid_alloc(HidTransport transport) {
HidSubmenuIndexTikTok,
hid_submenu_callback,
app);
+ submenu_add_item(
+ app->device_type_submenu,
+ "YT Shorts Controller",
+ HidSubmenuIndexYTShorts,
+ hid_submenu_callback,
+ app);
}
+ submenu_add_item(
+ app->device_type_submenu,
+ "Mouse Clicker",
+ HidSubmenuIndexMouseClicker,
+ hid_submenu_callback,
+ app);
submenu_add_item(
app->device_type_submenu,
"Mouse Jiggler",
@@ -148,6 +181,15 @@ Hid* hid_app_alloc_view(void* context) {
view_dispatcher_add_view(
app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote));
+ // Keynote Vertical view
+ app->hid_keynote_vertical = hid_keynote_vertical_alloc(app);
+ view_set_previous_callback(
+ hid_keynote_vertical_get_view(app->hid_keynote_vertical), hid_exit_confirm_view);
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ HidViewKeynoteVertical,
+ hid_keynote_vertical_get_view(app->hid_keynote_vertical));
+
// Keyboard view
app->hid_keyboard = hid_keyboard_alloc(app);
view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view);
@@ -166,12 +208,26 @@ Hid* hid_app_alloc_view(void* context) {
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok));
+ // YTShorts view
+ app->hid_ytshorts = hid_ytshorts_alloc(app);
+ view_set_previous_callback(hid_ytshorts_get_view(app->hid_ytshorts), hid_exit_confirm_view);
+ view_dispatcher_add_view(
+ app->view_dispatcher, BtHidViewYTShorts, hid_ytshorts_get_view(app->hid_ytshorts));
+
// Mouse view
app->hid_mouse = hid_mouse_alloc(app);
view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse));
+ // Mouse clicker view
+ app->hid_mouse_clicker = hid_mouse_clicker_alloc(app);
+ view_set_previous_callback(
+ hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_exit_confirm_view);
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ HidViewMouseClicker,
+ hid_mouse_clicker_get_view(app->hid_mouse_clicker));
// Mouse jiggler view
app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app);
view_set_previous_callback(
@@ -199,16 +255,22 @@ void hid_free(Hid* app) {
dialog_ex_free(app->dialog);
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote);
hid_keynote_free(app->hid_keynote);
+ view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynoteVertical);
+ hid_keynote_vertical_free(app->hid_keynote_vertical);
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard);
hid_keyboard_free(app->hid_keyboard);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia);
hid_media_free(app->hid_media);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse);
hid_mouse_free(app->hid_mouse);
+ view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseClicker);
+ hid_mouse_clicker_free(app->hid_mouse_clicker);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler);
hid_mouse_jiggler_free(app->hid_mouse_jiggler);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok);
hid_tiktok_free(app->hid_tiktok);
+ view_dispatcher_remove_view(app->view_dispatcher, BtHidViewYTShorts);
+ hid_ytshorts_free(app->hid_ytshorts);
view_dispatcher_free(app->view_dispatcher);
// Close records
diff --git a/applications/external/hid_app/hid.h b/applications/external/hid_app/hid.h
index 8ed1664a3..6fe7d381c 100644
--- a/applications/external/hid_app/hid.h
+++ b/applications/external/hid_app/hid.h
@@ -17,11 +17,14 @@
#include
#include
#include "views/hid_keynote.h"
+#include "views/hid_keynote_vertical.h"
#include "views/hid_keyboard.h"
#include "views/hid_media.h"
#include "views/hid_mouse.h"
#include "views/hid_mouse_jiggler.h"
#include "views/hid_tiktok.h"
+#include "views/hid_ytshorts.h"
+#include "views/hid_mouse_clicker.h"
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
@@ -40,11 +43,14 @@ struct Hid {
Submenu* device_type_submenu;
DialogEx* dialog;
HidKeynote* hid_keynote;
+ HidKeynoteVertical* hid_keynote_vertical;
HidKeyboard* hid_keyboard;
HidMedia* hid_media;
HidMouse* hid_mouse;
+ HidMouseClicker* hid_mouse_clicker;
HidMouseJiggler* hid_mouse_jiggler;
HidTikTok* hid_tiktok;
+ HidYTShorts* hid_ytshorts;
HidTransport transport;
uint32_t view_id;
@@ -62,4 +68,4 @@ void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy);
void hid_hal_mouse_scroll(Hid* instance, int8_t delta);
void hid_hal_mouse_press(Hid* instance, uint16_t event);
void hid_hal_mouse_release(Hid* instance, uint16_t event);
-void hid_hal_mouse_release_all(Hid* instance);
\ No newline at end of file
+void hid_hal_mouse_release_all(Hid* instance);
diff --git a/applications/external/hid_app/views.h b/applications/external/hid_app/views.h
index 2a44832e1..297fc7bc2 100644
--- a/applications/external/hid_app/views.h
+++ b/applications/external/hid_app/views.h
@@ -1,10 +1,13 @@
typedef enum {
HidViewSubmenu,
HidViewKeynote,
+ HidViewKeynoteVertical,
HidViewKeyboard,
HidViewMedia,
HidViewMouse,
+ HidViewMouseClicker,
HidViewMouseJiggler,
BtHidViewTikTok,
+ BtHidViewYTShorts,
HidViewExitConfirm,
-} HidView;
\ No newline at end of file
+} HidView;
diff --git a/applications/external/hid_app/views/hid_keynote_vertical.c b/applications/external/hid_app/views/hid_keynote_vertical.c
new file mode 100644
index 000000000..7d2303813
--- /dev/null
+++ b/applications/external/hid_app/views/hid_keynote_vertical.c
@@ -0,0 +1,228 @@
+#include "hid_keynote_vertical.h"
+#include
+#include "../hid.h"
+
+#include "hid_icons.h"
+
+#define TAG "HidKeynoteVertical"
+
+struct HidKeynoteVertical {
+ View* view;
+ Hid* hid;
+};
+
+typedef struct {
+ bool left_pressed;
+ bool up_pressed;
+ bool right_pressed;
+ bool down_pressed;
+ bool ok_pressed;
+ bool back_pressed;
+ bool connected;
+ HidTransport transport;
+} HidKeynoteVerticalModel;
+
+static void
+ hid_keynote_vertical_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
+ canvas_draw_triangle(canvas, x, y, 5, 3, dir);
+ if(dir == CanvasDirectionBottomToTop) {
+ canvas_draw_line(canvas, x, y + 6, x, y - 1);
+ } else if(dir == CanvasDirectionTopToBottom) {
+ canvas_draw_line(canvas, x, y - 6, x, y + 1);
+ } else if(dir == CanvasDirectionRightToLeft) {
+ canvas_draw_line(canvas, x + 6, y, x - 1, y);
+ } else if(dir == CanvasDirectionLeftToRight) {
+ canvas_draw_line(canvas, x - 6, y, x + 1, y);
+ }
+}
+
+static void hid_keynote_vertical_draw_callback(Canvas* canvas, void* context) {
+ furi_assert(context);
+ HidKeynoteVerticalModel* model = context;
+
+ // Header
+ if(model->transport == HidTransportBle) {
+ if(model->connected) {
+ canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
+ } else {
+ canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
+ }
+ }
+
+ canvas_set_font(canvas, FontPrimary);
+ elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote");
+ canvas_set_font(canvas, FontSecondary);
+ elements_multiline_text_aligned(
+ canvas, 24, 14, AlignLeft, AlignTop, "Vertical Up --->");
+
+ canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8);
+ canvas_set_font(canvas, FontSecondary);
+ elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit");
+
+ // Up
+ canvas_draw_icon(canvas, 21, 24, &I_Button_18x18);
+ if(model->up_pressed) {
+ elements_slightly_rounded_box(canvas, 24, 26, 13, 13);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ hid_keynote_vertical_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop);
+ canvas_set_color(canvas, ColorBlack);
+
+ // Down
+ canvas_draw_icon(canvas, 21, 45, &I_Button_18x18);
+ if(model->down_pressed) {
+ elements_slightly_rounded_box(canvas, 24, 47, 13, 13);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ hid_keynote_vertical_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom);
+ canvas_set_color(canvas, ColorBlack);
+
+ // Left
+ canvas_draw_icon(canvas, 0, 35, &I_Button_18x18);
+ if(model->left_pressed) {
+ elements_slightly_rounded_box(canvas, 3, 37, 13, 13);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ hid_keynote_vertical_draw_arrow(canvas, 7, 43, CanvasDirectionRightToLeft);
+ canvas_set_color(canvas, ColorBlack);
+
+ // Right
+ canvas_draw_icon(canvas, 42, 35, &I_Button_18x18);
+ if(model->right_pressed) {
+ elements_slightly_rounded_box(canvas, 45, 37, 13, 13);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ hid_keynote_vertical_draw_arrow(canvas, 53, 43, CanvasDirectionLeftToRight);
+ canvas_set_color(canvas, ColorBlack);
+
+ // Ok
+ canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
+ if(model->ok_pressed) {
+ elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
+ elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space");
+ canvas_set_color(canvas, ColorBlack);
+
+ // Back
+ canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
+ if(model->back_pressed) {
+ elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
+ elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back");
+}
+
+static void
+ hid_keynote_vertical_process(HidKeynoteVertical* hid_keynote_vertical, InputEvent* event) {
+ with_view_model(
+ hid_keynote_vertical->view,
+ HidKeynoteVerticalModel * model,
+ {
+ if(event->type == InputTypePress) {
+ if(event->key == InputKeyUp) {
+ model->up_pressed = true;
+ hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_LEFT_ARROW);
+ } else if(event->key == InputKeyDown) {
+ model->down_pressed = true;
+ hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_RIGHT_ARROW);
+ } else if(event->key == InputKeyLeft) {
+ model->left_pressed = true;
+ hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_DOWN_ARROW);
+ } else if(event->key == InputKeyRight) {
+ model->right_pressed = true;
+ hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_UP_ARROW);
+ } else if(event->key == InputKeyOk) {
+ model->ok_pressed = true;
+ hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_SPACEBAR);
+ } else if(event->key == InputKeyBack) {
+ model->back_pressed = true;
+ }
+ } else if(event->type == InputTypeRelease) {
+ if(event->key == InputKeyUp) {
+ model->up_pressed = false;
+ hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_LEFT_ARROW);
+ } else if(event->key == InputKeyDown) {
+ model->down_pressed = false;
+ hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_RIGHT_ARROW);
+ } else if(event->key == InputKeyLeft) {
+ model->left_pressed = false;
+ hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_DOWN_ARROW);
+ } else if(event->key == InputKeyRight) {
+ model->right_pressed = false;
+ hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_UP_ARROW);
+ } else if(event->key == InputKeyOk) {
+ model->ok_pressed = false;
+ hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_SPACEBAR);
+ } else if(event->key == InputKeyBack) {
+ model->back_pressed = false;
+ }
+ } else if(event->type == InputTypeShort) {
+ if(event->key == InputKeyBack) {
+ hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_DELETE);
+ hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_DELETE);
+ hid_hal_consumer_key_press(hid_keynote_vertical->hid, HID_CONSUMER_AC_BACK);
+ hid_hal_consumer_key_release(hid_keynote_vertical->hid, HID_CONSUMER_AC_BACK);
+ }
+ }
+ },
+ true);
+}
+
+static bool hid_keynote_vertical_input_callback(InputEvent* event, void* context) {
+ furi_assert(context);
+ HidKeynoteVertical* hid_keynote_vertical = context;
+ bool consumed = false;
+
+ if(event->type == InputTypeLong && event->key == InputKeyBack) {
+ hid_hal_keyboard_release_all(hid_keynote_vertical->hid);
+ } else {
+ hid_keynote_vertical_process(hid_keynote_vertical, event);
+ consumed = true;
+ }
+
+ return consumed;
+}
+
+HidKeynoteVertical* hid_keynote_vertical_alloc(Hid* hid) {
+ HidKeynoteVertical* hid_keynote_vertical = malloc(sizeof(HidKeynoteVertical));
+ hid_keynote_vertical->view = view_alloc();
+ hid_keynote_vertical->hid = hid;
+ view_set_context(hid_keynote_vertical->view, hid_keynote_vertical);
+ view_allocate_model(
+ hid_keynote_vertical->view, ViewModelTypeLocking, sizeof(HidKeynoteVerticalModel));
+ view_set_draw_callback(hid_keynote_vertical->view, hid_keynote_vertical_draw_callback);
+ view_set_input_callback(hid_keynote_vertical->view, hid_keynote_vertical_input_callback);
+
+ with_view_model(
+ hid_keynote_vertical->view,
+ HidKeynoteVerticalModel * model,
+ { model->transport = hid->transport; },
+ true);
+
+ return hid_keynote_vertical;
+}
+
+void hid_keynote_vertical_free(HidKeynoteVertical* hid_keynote_vertical) {
+ furi_assert(hid_keynote_vertical);
+ view_free(hid_keynote_vertical->view);
+ free(hid_keynote_vertical);
+}
+
+View* hid_keynote_vertical_get_view(HidKeynoteVertical* hid_keynote_vertical) {
+ furi_assert(hid_keynote_vertical);
+ return hid_keynote_vertical->view;
+}
+
+void hid_keynote_vertical_set_connected_status(
+ HidKeynoteVertical* hid_keynote_vertical,
+ bool connected) {
+ furi_assert(hid_keynote_vertical);
+ with_view_model(
+ hid_keynote_vertical->view,
+ HidKeynoteVerticalModel * model,
+ { model->connected = connected; },
+ true);
+}
diff --git a/applications/external/hid_app/views/hid_keynote_vertical.h b/applications/external/hid_app/views/hid_keynote_vertical.h
new file mode 100644
index 000000000..bb7134adb
--- /dev/null
+++ b/applications/external/hid_app/views/hid_keynote_vertical.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include
+
+typedef struct Hid Hid;
+typedef struct HidKeynoteVertical HidKeynoteVertical;
+
+HidKeynoteVertical* hid_keynote_vertical_alloc(Hid* bt_hid);
+
+void hid_keynote_vertical_free(HidKeynoteVertical* hid_keynote_vertical);
+
+View* hid_keynote_vertical_get_view(HidKeynoteVertical* hid_keynote_vertical);
+
+void hid_keynote_vertical_set_connected_status(
+ HidKeynoteVertical* hid_keynote_vertical,
+ bool connected);
diff --git a/applications/external/hid_app/views/hid_media.c b/applications/external/hid_app/views/hid_media.c
index 468529d56..0028ac596 100644
--- a/applications/external/hid_app/views/hid_media.c
+++ b/applications/external/hid_app/views/hid_media.c
@@ -21,6 +21,7 @@ typedef struct {
bool down_pressed;
bool ok_pressed;
bool connected;
+ bool back_pressed;
HidTransport transport;
} HidMediaModel;
@@ -55,61 +56,72 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) {
canvas_set_font(canvas, FontSecondary);
// Keypad circles
- canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
+ canvas_draw_icon(canvas, 58, 3, &I_OutCircles);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 68, 6, &I_S_UP);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6);
+ canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 68, 36, &I_S_DOWN);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6);
+ canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 61, 13, &I_S_LEFT);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft);
- hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft);
+ hid_media_draw_arrow(canvas, 65, 28, CanvasDirectionRightToLeft);
+ hid_media_draw_arrow(canvas, 70, 28, CanvasDirectionRightToLeft);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight);
- hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight);
+ hid_media_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight);
+ hid_media_draw_arrow(canvas, 101, 28, CanvasDirectionLeftToRight);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->ok_pressed) {
- canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13);
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
+ canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight);
- canvas_draw_line(canvas, 100, 29, 100, 33);
- canvas_draw_line(canvas, 102, 29, 102, 33);
+ hid_media_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight);
+ canvas_draw_line(canvas, 84, 26, 84, 30);
+ canvas_draw_line(canvas, 86, 26, 86, 30);
canvas_set_color(canvas, ColorBlack);
// Exit
+ if(model->back_pressed) {
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10);
+ canvas_set_color(canvas, ColorBlack);
+
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
@@ -135,6 +147,8 @@ static void hid_media_process_press(HidMedia* hid_media, InputEvent* event) {
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_PLAY_PAUSE);
+ } else if(event->key == InputKeyBack) {
+ model->back_pressed = true;
}
},
true);
@@ -160,6 +174,8 @@ static void hid_media_process_release(HidMedia* hid_media, InputEvent* event) {
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_PLAY_PAUSE);
+ } else if(event->key == InputKeyBack) {
+ model->back_pressed = false;
}
},
true);
@@ -176,12 +192,7 @@ static bool hid_media_input_callback(InputEvent* event, void* context) {
} else if(event->type == InputTypeRelease) {
hid_media_process_release(hid_media, event);
consumed = true;
- } else if(event->type == InputTypeShort) {
- if(event->key == InputKeyBack) {
- hid_hal_consumer_key_release_all(hid_media->hid);
- }
}
-
return consumed;
}
diff --git a/applications/external/hid_app/views/hid_mouse.c b/applications/external/hid_app/views/hid_mouse.c
index 30a9d9d06..75df53dd1 100644
--- a/applications/external/hid_app/views/hid_mouse.c
+++ b/applications/external/hid_app/views/hid_mouse.c
@@ -49,61 +49,67 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) {
}
// Keypad circles
- canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47);
+ canvas_draw_icon(canvas, 58, 3, &I_OutCircles);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 68, 6, &I_S_UP);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up_7x9);
+ canvas_draw_icon(canvas, 80, 8, &I_Pin_arrow_up_7x9);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 68, 36, &I_S_DOWN);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9);
+ canvas_draw_icon(canvas, 80, 40, &I_Pin_arrow_down_7x9);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 61, 13, &I_S_LEFT);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7);
+ canvas_draw_icon(canvas, 63, 25, &I_Pin_arrow_left_9x7);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7);
+ canvas_draw_icon(canvas, 95, 25, &I_Pin_arrow_right_9x7);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->left_mouse_pressed) {
- canvas_draw_icon(canvas, 81, 25, &I_Ok_btn_pressed_13x13);
- } else {
- canvas_draw_icon(canvas, 83, 27, &I_Left_mouse_icon_9x9);
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
}
+ canvas_draw_icon(canvas, 79, 24, &I_Left_mouse_icon_9x9);
+ canvas_set_color(canvas, ColorBlack);
// Back
if(model->right_mouse_pressed) {
- canvas_draw_icon(canvas, 108, 48, &I_Ok_btn_pressed_13x13);
- } else {
- canvas_draw_icon(canvas, 110, 50, &I_Right_mouse_icon_9x9);
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
}
+ canvas_draw_icon(canvas, 112, 38, &I_Right_mouse_icon_9x9);
+ canvas_set_color(canvas, ColorBlack);
}
static void hid_mouse_process(HidMouse* hid_mouse, InputEvent* event) {
diff --git a/applications/external/hid_app/views/hid_mouse_clicker.c b/applications/external/hid_app/views/hid_mouse_clicker.c
new file mode 100644
index 000000000..efaca190a
--- /dev/null
+++ b/applications/external/hid_app/views/hid_mouse_clicker.c
@@ -0,0 +1,214 @@
+#include "hid_mouse_clicker.h"
+#include
+#include "../hid.h"
+
+#include "hid_icons.h"
+
+#define TAG "HidMouseClicker"
+#define DEFAULT_CLICK_RATE 1
+#define MAXIMUM_CLICK_RATE 60
+
+struct HidMouseClicker {
+ View* view;
+ Hid* hid;
+ FuriTimer* timer;
+};
+
+typedef struct {
+ bool connected;
+ bool running;
+ int rate;
+ HidTransport transport;
+} HidMouseClickerModel;
+
+static void hid_mouse_clicker_start_or_restart_timer(void* context) {
+ furi_assert(context);
+ HidMouseClicker* hid_mouse_clicker = context;
+
+ if(furi_timer_is_running(hid_mouse_clicker->timer)) {
+ furi_timer_stop(hid_mouse_clicker->timer);
+ }
+
+ with_view_model(
+ hid_mouse_clicker->view,
+ HidMouseClickerModel * model,
+ {
+ furi_timer_start(
+ hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate);
+ },
+ true);
+}
+
+static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) {
+ furi_assert(context);
+ HidMouseClickerModel* model = context;
+
+ // Header
+ if(model->transport == HidTransportBle) {
+ if(model->connected) {
+ canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
+ } else {
+ canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
+ }
+ }
+
+ canvas_set_font(canvas, FontPrimary);
+ elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker");
+
+ // Ok
+ canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
+ if(model->running) {
+ canvas_set_font(canvas, FontPrimary);
+
+ FuriString* rate_label = furi_string_alloc();
+ furi_string_printf(rate_label, "%d clicks/s\n\nUp / Down", model->rate);
+ elements_multiline_text(canvas, AlignLeft, 35, furi_string_get_cstr(rate_label));
+ canvas_set_font(canvas, FontSecondary);
+ furi_string_free(rate_label);
+
+ elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
+ canvas_set_color(canvas, ColorWhite);
+ } else {
+ canvas_set_font(canvas, FontPrimary);
+ elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto start\nclicking");
+ canvas_set_font(canvas, FontSecondary);
+ }
+ canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
+ if(model->running) {
+ elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop");
+ } else {
+ elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start");
+ }
+ canvas_set_color(canvas, ColorBlack);
+
+ // Back
+ canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
+ elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit");
+}
+
+static void hid_mouse_clicker_timer_callback(void* context) {
+ furi_assert(context);
+ HidMouseClicker* hid_mouse_clicker = context;
+ with_view_model(
+ hid_mouse_clicker->view,
+ HidMouseClickerModel * model,
+ {
+ if(model->running) {
+ hid_hal_mouse_press(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT);
+ hid_hal_mouse_release(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT);
+ }
+ },
+ false);
+}
+
+static void hid_mouse_clicker_enter_callback(void* context) {
+ hid_mouse_clicker_start_or_restart_timer(context);
+}
+
+static void hid_mouse_clicker_exit_callback(void* context) {
+ furi_assert(context);
+ HidMouseClicker* hid_mouse_clicker = context;
+ furi_timer_stop(hid_mouse_clicker->timer);
+}
+
+static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) {
+ furi_assert(context);
+ HidMouseClicker* hid_mouse_clicker = context;
+
+ bool consumed = false;
+ bool rate_changed = false;
+
+ if(event->type != InputTypeRelease) {
+ return false;
+ }
+
+ with_view_model(
+ hid_mouse_clicker->view,
+ HidMouseClickerModel * model,
+ {
+ switch(event->key) {
+ case InputKeyOk:
+ model->running = !model->running;
+ consumed = true;
+ break;
+ case InputKeyUp:
+ if(model->rate < MAXIMUM_CLICK_RATE) {
+ model->rate++;
+ }
+ rate_changed = true;
+ consumed = true;
+ break;
+ case InputKeyDown:
+ if(model->rate > 1) {
+ model->rate--;
+ }
+ rate_changed = true;
+ consumed = true;
+ break;
+ default:
+ consumed = true;
+ break;
+ }
+ },
+ true);
+
+ if(rate_changed) {
+ hid_mouse_clicker_start_or_restart_timer(context);
+ }
+
+ return consumed;
+}
+
+HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) {
+ HidMouseClicker* hid_mouse_clicker = malloc(sizeof(HidMouseClicker));
+
+ hid_mouse_clicker->view = view_alloc();
+ view_set_context(hid_mouse_clicker->view, hid_mouse_clicker);
+ view_allocate_model(
+ hid_mouse_clicker->view, ViewModelTypeLocking, sizeof(HidMouseClickerModel));
+ view_set_draw_callback(hid_mouse_clicker->view, hid_mouse_clicker_draw_callback);
+ view_set_input_callback(hid_mouse_clicker->view, hid_mouse_clicker_input_callback);
+ view_set_enter_callback(hid_mouse_clicker->view, hid_mouse_clicker_enter_callback);
+ view_set_exit_callback(hid_mouse_clicker->view, hid_mouse_clicker_exit_callback);
+
+ hid_mouse_clicker->hid = hid;
+
+ hid_mouse_clicker->timer = furi_timer_alloc(
+ hid_mouse_clicker_timer_callback, FuriTimerTypePeriodic, hid_mouse_clicker);
+
+ with_view_model(
+ hid_mouse_clicker->view,
+ HidMouseClickerModel * model,
+ {
+ model->transport = hid->transport;
+ model->rate = DEFAULT_CLICK_RATE;
+ },
+ true);
+
+ return hid_mouse_clicker;
+}
+
+void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker) {
+ furi_assert(hid_mouse_clicker);
+
+ furi_timer_stop(hid_mouse_clicker->timer);
+ furi_timer_free(hid_mouse_clicker->timer);
+
+ view_free(hid_mouse_clicker->view);
+
+ free(hid_mouse_clicker);
+}
+
+View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker) {
+ furi_assert(hid_mouse_clicker);
+ return hid_mouse_clicker->view;
+}
+
+void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected) {
+ furi_assert(hid_mouse_clicker);
+ with_view_model(
+ hid_mouse_clicker->view,
+ HidMouseClickerModel * model,
+ { model->connected = connected; },
+ true);
+}
diff --git a/applications/external/hid_app/views/hid_mouse_clicker.h b/applications/external/hid_app/views/hid_mouse_clicker.h
new file mode 100644
index 000000000..d72847baa
--- /dev/null
+++ b/applications/external/hid_app/views/hid_mouse_clicker.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+
+typedef struct Hid Hid;
+typedef struct HidMouseClicker HidMouseClicker;
+
+HidMouseClicker* hid_mouse_clicker_alloc(Hid* bt_hid);
+
+void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker);
+
+View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker);
+
+void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected);
diff --git a/applications/external/hid_app/views/hid_tiktok.c b/applications/external/hid_app/views/hid_tiktok.c
index e1f9f4bed..4dfbde4eb 100644
--- a/applications/external/hid_app/views/hid_tiktok.c
+++ b/applications/external/hid_app/views/hid_tiktok.c
@@ -19,6 +19,7 @@ typedef struct {
bool ok_pressed;
bool connected;
bool is_cursor_set;
+ bool back_mouse_pressed;
HidTransport transport;
} HidTikTokModel;
@@ -40,54 +41,68 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) {
canvas_set_font(canvas, FontSecondary);
// Keypad circles
- canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
+ canvas_draw_icon(canvas, 58, 3, &I_OutCircles);
+
+ // Pause
+ if(model->back_mouse_pressed) {
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_icon(canvas, 113, 37, &I_Pause_icon_9x9);
+ canvas_set_color(canvas, ColorBlack);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 68, 6, &I_S_UP);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- canvas_draw_icon(canvas, 96, 11, &I_Arr_up_7x9);
+ canvas_draw_icon(canvas, 80, 8, &I_Arr_up_7x9);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 68, 36, &I_S_DOWN);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- canvas_draw_icon(canvas, 96, 44, &I_Arr_dwn_7x9);
+ canvas_draw_icon(canvas, 80, 40, &I_Arr_dwn_7x9);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 61, 13, &I_S_LEFT);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- canvas_draw_icon(canvas, 81, 29, &I_Voldwn_6x6);
+ canvas_draw_icon(canvas, 64, 25, &I_Voldwn_6x6);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
- canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13);
+ canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
- canvas_draw_icon(canvas, 111, 29, &I_Volup_8x6);
+ canvas_draw_icon(canvas, 95, 25, &I_Volup_8x6);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->ok_pressed) {
- canvas_draw_icon(canvas, 91, 23, &I_Like_pressed_17x17);
- } else {
- canvas_draw_icon(canvas, 94, 27, &I_Like_def_11x9);
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
}
+ canvas_draw_icon(canvas, 78, 25, &I_Like_def_11x9);
+ canvas_set_color(canvas, ColorBlack);
+
// Exit
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
@@ -102,7 +117,8 @@ static void hid_tiktok_reset_cursor(HidTikTok* hid_tiktok) {
furi_delay_ms(50);
}
// Move cursor from the corner
- hid_hal_mouse_move(hid_tiktok->hid, 20, 120);
+ hid_hal_mouse_move(hid_tiktok->hid, 40, 120);
+ hid_hal_mouse_move(hid_tiktok->hid, 0, 120);
furi_delay_ms(50);
}
@@ -120,6 +136,8 @@ static void
hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
+ } else if(event->key == InputKeyBack) {
+ model->back_mouse_pressed = true;
}
}
@@ -137,6 +155,8 @@ static void
hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
+ } else if(event->key == InputKeyBack) {
+ model->back_mouse_pressed = false;
}
}
@@ -162,32 +182,27 @@ static bool hid_tiktok_input_callback(InputEvent* event, void* context) {
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyOk) {
hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT);
- furi_delay_ms(50);
+ furi_delay_ms(25);
hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT);
- furi_delay_ms(50);
+ furi_delay_ms(100);
+ hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT);
+ furi_delay_ms(25);
+ hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT);
+ consumed = true;
+ } else if(event->key == InputKeyDown) {
+ // Swipe to previous video
+ hid_hal_mouse_scroll(hid_tiktok->hid, 19);
+ consumed = true;
+ } else if(event->key == InputKeyUp) {
+ // Swipe to new video
+ hid_hal_mouse_scroll(hid_tiktok->hid, -19);
+ consumed = true;
+ } else if(event->key == InputKeyBack) {
+ // Pause
hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT);
furi_delay_ms(50);
hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT);
consumed = true;
- } else if(event->key == InputKeyUp) {
- // Emulate up swipe
- hid_hal_mouse_scroll(hid_tiktok->hid, -6);
- hid_hal_mouse_scroll(hid_tiktok->hid, -12);
- hid_hal_mouse_scroll(hid_tiktok->hid, -19);
- hid_hal_mouse_scroll(hid_tiktok->hid, -12);
- hid_hal_mouse_scroll(hid_tiktok->hid, -6);
- consumed = true;
- } else if(event->key == InputKeyDown) {
- // Emulate down swipe
- hid_hal_mouse_scroll(hid_tiktok->hid, 6);
- hid_hal_mouse_scroll(hid_tiktok->hid, 12);
- hid_hal_mouse_scroll(hid_tiktok->hid, 19);
- hid_hal_mouse_scroll(hid_tiktok->hid, 12);
- hid_hal_mouse_scroll(hid_tiktok->hid, 6);
- consumed = true;
- } else if(event->key == InputKeyBack) {
- hid_hal_consumer_key_release_all(hid_tiktok->hid);
- consumed = true;
}
} else if(event->type == InputTypeLong) {
if(event->key == InputKeyBack) {
diff --git a/applications/external/hid_app/views/hid_ytshorts.c b/applications/external/hid_app/views/hid_ytshorts.c
new file mode 100644
index 000000000..359091640
--- /dev/null
+++ b/applications/external/hid_app/views/hid_ytshorts.c
@@ -0,0 +1,272 @@
+#include "hid_ytshorts.h"
+#include "../hid.h"
+#include
+
+#include "hid_icons.h"
+
+#define TAG "HidYTShorts"
+
+struct HidYTShorts {
+ View* view;
+ Hid* hid;
+};
+
+typedef struct {
+ bool left_pressed;
+ bool up_pressed;
+ bool right_pressed;
+ bool down_pressed;
+ bool ok_pressed;
+ bool connected;
+ bool is_cursor_set;
+ bool back_mouse_pressed;
+ HidTransport transport;
+} HidYTShortsModel;
+
+static void hid_ytshorts_draw_callback(Canvas* canvas, void* context) {
+ furi_assert(context);
+ HidYTShortsModel* model = context;
+
+ // Header
+ if(model->transport == HidTransportBle) {
+ if(model->connected) {
+ canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
+ } else {
+ canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
+ }
+ }
+
+ canvas_set_font(canvas, FontPrimary);
+ elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Shorts");
+ canvas_set_font(canvas, FontSecondary);
+
+ // Keypad circles
+ canvas_draw_icon(canvas, 58, 3, &I_OutCircles);
+
+ // Pause
+ if(model->back_mouse_pressed) {
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_icon(canvas, 113, 37, &I_Pause_icon_9x9);
+ canvas_set_color(canvas, ColorBlack);
+
+ // Up
+ if(model->up_pressed) {
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 68, 6, &I_S_UP);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_icon(canvas, 80, 8, &I_Arr_up_7x9);
+ canvas_set_color(canvas, ColorBlack);
+
+ // Down
+ if(model->down_pressed) {
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 68, 36, &I_S_DOWN);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_icon(canvas, 80, 40, &I_Arr_dwn_7x9);
+ canvas_set_color(canvas, ColorBlack);
+
+ // Left
+ if(model->left_pressed) {
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 61, 13, &I_S_LEFT);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_icon(canvas, 64, 25, &I_Voldwn_6x6);
+ canvas_set_color(canvas, ColorBlack);
+
+ // Right
+ if(model->right_pressed) {
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_icon(canvas, 95, 25, &I_Volup_8x6);
+ canvas_set_color(canvas, ColorBlack);
+
+ // Ok
+ if(model->ok_pressed) {
+ canvas_set_bitmap_mode(canvas, 1);
+ canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
+ canvas_set_bitmap_mode(canvas, 0);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_icon(canvas, 78, 25, &I_Like_def_11x9);
+ canvas_set_color(canvas, ColorBlack);
+
+ // Exit
+ canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
+ canvas_set_font(canvas, FontSecondary);
+ elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
+}
+
+static void hid_ytshorts_reset_cursor(HidYTShorts* hid_ytshorts) {
+ // Set cursor to the phone's left up corner
+ // Delays to guarantee one packet per connection interval
+ for(size_t i = 0; i < 8; i++) {
+ hid_hal_mouse_move(hid_ytshorts->hid, -127, -127);
+ furi_delay_ms(50);
+ }
+ // Move cursor from the corner
+ hid_hal_mouse_move(hid_ytshorts->hid, 40, 120);
+ hid_hal_mouse_move(hid_ytshorts->hid, 0, 120);
+ furi_delay_ms(50);
+}
+
+static void hid_ytshorts_process_press(
+ HidYTShorts* hid_ytshorts,
+ HidYTShortsModel* model,
+ InputEvent* event) {
+ if(event->key == InputKeyUp) {
+ model->up_pressed = true;
+ } else if(event->key == InputKeyDown) {
+ model->down_pressed = true;
+ } else if(event->key == InputKeyLeft) {
+ model->left_pressed = true;
+ hid_hal_consumer_key_press(hid_ytshorts->hid, HID_CONSUMER_VOLUME_DECREMENT);
+ } else if(event->key == InputKeyRight) {
+ model->right_pressed = true;
+ hid_hal_consumer_key_press(hid_ytshorts->hid, HID_CONSUMER_VOLUME_INCREMENT);
+ } else if(event->key == InputKeyOk) {
+ model->ok_pressed = true;
+ } else if(event->key == InputKeyBack) {
+ model->back_mouse_pressed = true;
+ }
+}
+
+static void hid_ytshorts_process_release(
+ HidYTShorts* hid_ytshorts,
+ HidYTShortsModel* model,
+ InputEvent* event) {
+ if(event->key == InputKeyUp) {
+ model->up_pressed = false;
+ } else if(event->key == InputKeyDown) {
+ model->down_pressed = false;
+ } else if(event->key == InputKeyLeft) {
+ model->left_pressed = false;
+ hid_hal_consumer_key_release(hid_ytshorts->hid, HID_CONSUMER_VOLUME_DECREMENT);
+ } else if(event->key == InputKeyRight) {
+ model->right_pressed = false;
+ hid_hal_consumer_key_release(hid_ytshorts->hid, HID_CONSUMER_VOLUME_INCREMENT);
+ } else if(event->key == InputKeyOk) {
+ model->ok_pressed = false;
+ } else if(event->key == InputKeyBack) {
+ model->back_mouse_pressed = false;
+ }
+}
+
+static bool hid_ytshorts_input_callback(InputEvent* event, void* context) {
+ furi_assert(context);
+ HidYTShorts* hid_ytshorts = context;
+ bool consumed = false;
+
+ with_view_model(
+ hid_ytshorts->view,
+ HidYTShortsModel * model,
+ {
+ if(event->type == InputTypePress) {
+ hid_ytshorts_process_press(hid_ytshorts, model, event);
+ if(model->connected && !model->is_cursor_set) {
+ hid_ytshorts_reset_cursor(hid_ytshorts);
+ model->is_cursor_set = true;
+ }
+ consumed = true;
+ } else if(event->type == InputTypeRelease) {
+ hid_ytshorts_process_release(hid_ytshorts, model, event);
+ consumed = true;
+ } else if(event->type == InputTypeShort) {
+ if(event->key == InputKeyOk) {
+ hid_hal_mouse_press(hid_ytshorts->hid, HID_MOUSE_BTN_LEFT);
+ furi_delay_ms(50);
+ hid_hal_mouse_release(hid_ytshorts->hid, HID_MOUSE_BTN_LEFT);
+ furi_delay_ms(50);
+ hid_hal_mouse_press(hid_ytshorts->hid, HID_MOUSE_BTN_LEFT);
+ furi_delay_ms(50);
+ hid_hal_mouse_release(hid_ytshorts->hid, HID_MOUSE_BTN_LEFT);
+ consumed = true;
+ } else if(event->key == InputKeyDown) {
+ // Swipe to new video
+ hid_hal_mouse_scroll(hid_ytshorts->hid, 6);
+ hid_hal_mouse_scroll(hid_ytshorts->hid, 8);
+ hid_hal_mouse_scroll(hid_ytshorts->hid, 10);
+ hid_hal_mouse_scroll(hid_ytshorts->hid, 8);
+ hid_hal_mouse_scroll(hid_ytshorts->hid, 6);
+ consumed = true;
+ } else if(event->key == InputKeyUp) {
+ // Swipe to previous video
+ hid_hal_mouse_scroll(hid_ytshorts->hid, -6);
+ hid_hal_mouse_scroll(hid_ytshorts->hid, -8);
+ hid_hal_mouse_scroll(hid_ytshorts->hid, -10);
+ hid_hal_mouse_scroll(hid_ytshorts->hid, -8);
+ hid_hal_mouse_scroll(hid_ytshorts->hid, -6);
+ consumed = true;
+ } else if(event->key == InputKeyBack) {
+ // Pause
+ hid_hal_mouse_press(hid_ytshorts->hid, HID_MOUSE_BTN_LEFT);
+ furi_delay_ms(50);
+ hid_hal_mouse_release(hid_ytshorts->hid, HID_MOUSE_BTN_LEFT);
+ furi_delay_ms(50);
+ consumed = true;
+ }
+ } else if(event->type == InputTypeLong) {
+ if(event->key == InputKeyBack) {
+ hid_hal_consumer_key_release_all(hid_ytshorts->hid);
+ model->is_cursor_set = false;
+ consumed = false;
+ }
+ }
+ },
+ true);
+
+ return consumed;
+}
+
+HidYTShorts* hid_ytshorts_alloc(Hid* bt_hid) {
+ HidYTShorts* hid_ytshorts = malloc(sizeof(HidYTShorts));
+ hid_ytshorts->hid = bt_hid;
+ hid_ytshorts->view = view_alloc();
+ view_set_context(hid_ytshorts->view, hid_ytshorts);
+ view_allocate_model(hid_ytshorts->view, ViewModelTypeLocking, sizeof(HidYTShortsModel));
+ view_set_draw_callback(hid_ytshorts->view, hid_ytshorts_draw_callback);
+ view_set_input_callback(hid_ytshorts->view, hid_ytshorts_input_callback);
+
+ with_view_model(
+ hid_ytshorts->view,
+ HidYTShortsModel * model,
+ { model->transport = bt_hid->transport; },
+ true);
+
+ return hid_ytshorts;
+}
+
+void hid_ytshorts_free(HidYTShorts* hid_ytshorts) {
+ furi_assert(hid_ytshorts);
+ view_free(hid_ytshorts->view);
+ free(hid_ytshorts);
+}
+
+View* hid_ytshorts_get_view(HidYTShorts* hid_ytshorts) {
+ furi_assert(hid_ytshorts);
+ return hid_ytshorts->view;
+}
+
+void hid_ytshorts_set_connected_status(HidYTShorts* hid_ytshorts, bool connected) {
+ furi_assert(hid_ytshorts);
+ with_view_model(
+ hid_ytshorts->view,
+ HidYTShortsModel * model,
+ {
+ model->connected = connected;
+ model->is_cursor_set = false;
+ },
+ true);
+}
diff --git a/applications/external/hid_app/views/hid_ytshorts.h b/applications/external/hid_app/views/hid_ytshorts.h
new file mode 100644
index 000000000..03264dd36
--- /dev/null
+++ b/applications/external/hid_app/views/hid_ytshorts.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+
+typedef struct Hid Hid;
+typedef struct HidYTShorts HidYTShorts;
+
+HidYTShorts* hid_ytshorts_alloc(Hid* bt_hid);
+
+void hid_ytshorts_free(HidYTShorts* hid_ytshorts);
+
+View* hid_ytshorts_get_view(HidYTShorts* hid_ytshorts);
+
+void hid_ytshorts_set_connected_status(HidYTShorts* hid_ytshorts, bool connected);
diff --git a/applications/external/ibtn_fuzzer/ibtnfuzzer.c b/applications/external/ibtn_fuzzer/ibtnfuzzer.c
index 04d18886f..9e6239b69 100644
--- a/applications/external/ibtn_fuzzer/ibtnfuzzer.c
+++ b/applications/external/ibtn_fuzzer/ibtnfuzzer.c
@@ -59,6 +59,14 @@ iBtnFuzzerState* ibtnfuzzer_alloc() {
ibtnfuzzer->proto_name = furi_string_alloc();
ibtnfuzzer->data_str = furi_string_alloc();
+ ibtnfuzzer->main_menu_items[0] = furi_string_alloc_set("Default Values");
+ ibtnfuzzer->main_menu_items[1] = furi_string_alloc_set("Load File");
+ ibtnfuzzer->main_menu_items[2] = furi_string_alloc_set("Load UIDs from file");
+
+ ibtnfuzzer->main_menu_proto_items[0] = furi_string_alloc_set("DS1990");
+ ibtnfuzzer->main_menu_proto_items[1] = furi_string_alloc_set("Metakom");
+ ibtnfuzzer->main_menu_proto_items[2] = furi_string_alloc_set("Cyfral");
+
ibtnfuzzer->previous_scene = NoneScene;
ibtnfuzzer->current_scene = SceneEntryPoint;
ibtnfuzzer->is_running = true;
@@ -105,8 +113,13 @@ void ibtnfuzzer_free(iBtnFuzzerState* ibtnfuzzer) {
furi_string_free(ibtnfuzzer->proto_name);
furi_string_free(ibtnfuzzer->data_str);
- free(ibtnfuzzer->data);
- free(ibtnfuzzer->payload);
+ for(uint32_t i = 0; i < 3; i++) {
+ furi_string_free(ibtnfuzzer->main_menu_items[i]);
+ }
+
+ for(uint32_t i = 0; i < 3; i++) {
+ furi_string_free(ibtnfuzzer->main_menu_proto_items[i]);
+ }
// The rest
free(ibtnfuzzer);
diff --git a/applications/external/ibtn_fuzzer/ibtnfuzzer.h b/applications/external/ibtn_fuzzer/ibtnfuzzer.h
index ed42cc541..3a3a1d21f 100644
--- a/applications/external/ibtn_fuzzer/ibtnfuzzer.h
+++ b/applications/external/ibtn_fuzzer/ibtnfuzzer.h
@@ -73,6 +73,8 @@ typedef struct {
iBtnFuzzerProtos proto;
FuriString* attack_name;
FuriString* proto_name;
+ FuriString* main_menu_items[3];
+ FuriString* main_menu_proto_items[3];
DialogsApp* dialogs;
FuriString* notification_msg;
diff --git a/applications/external/ibtn_fuzzer/scene/ibtnfuzzer_scene_entrypoint.c b/applications/external/ibtn_fuzzer/scene/ibtnfuzzer_scene_entrypoint.c
index 3ea7e49e6..1dd239c3b 100644
--- a/applications/external/ibtn_fuzzer/scene/ibtnfuzzer_scene_entrypoint.c
+++ b/applications/external/ibtn_fuzzer/scene/ibtnfuzzer_scene_entrypoint.c
@@ -1,8 +1,5 @@
#include "ibtnfuzzer_scene_entrypoint.h"
-FuriString* main_menu_items[3];
-FuriString* main_menu_proto_items[3];
-
void ibtnfuzzer_scene_entrypoint_menu_callback(
iBtnFuzzerState* context,
uint32_t index,
@@ -61,30 +58,14 @@ void ibtnfuzzer_scene_entrypoint_on_enter(iBtnFuzzerState* context) {
menu_items[i] = furi_string_alloc();
}*/
- main_menu_items[0] = furi_string_alloc_set("Default Values");
- main_menu_items[1] = furi_string_alloc_set("Load File");
- main_menu_items[2] = furi_string_alloc_set("Load UIDs from file");
-
context->menu_proto_index = 0;
/*for(uint32_t i = 0; i < 4; i++) {
menu_proto_items[i] = furi_string_alloc();
}*/
-
- main_menu_proto_items[0] = furi_string_alloc_set("DS1990");
- main_menu_proto_items[1] = furi_string_alloc_set("Metakom");
- main_menu_proto_items[2] = furi_string_alloc_set("Cyfral");
}
void ibtnfuzzer_scene_entrypoint_on_exit(iBtnFuzzerState* context) {
context->enter_rerun = false;
-
- for(uint32_t i = 0; i < 3; i++) {
- furi_string_free(main_menu_items[i]);
- }
-
- for(uint32_t i = 0; i < 3; i++) {
- furi_string_free(main_menu_proto_items[i]);
- }
}
void ibtnfuzzer_scene_entrypoint_on_tick(iBtnFuzzerState* context) {
@@ -142,74 +123,79 @@ void ibtnfuzzer_scene_entrypoint_on_draw(Canvas* canvas, iBtnFuzzerState* contex
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
- if(main_menu_items[context->menu_index] != NULL) {
- if(context->menu_index > iBtnFuzzerAttackDefaultValues) {
- canvas_set_font(canvas, FontSecondary);
+ if(context->main_menu_items != NULL) {
+ if(context->main_menu_items[context->menu_index] != NULL) {
+ if(context->menu_index > iBtnFuzzerAttackDefaultValues) {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str_aligned(
+ canvas,
+ 64,
+ 24,
+ AlignCenter,
+ AlignTop,
+ furi_string_get_cstr(context->main_menu_items[context->menu_index - 1]));
+ }
+
+ canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas,
64,
- 24,
+ 36,
AlignCenter,
AlignTop,
- furi_string_get_cstr(main_menu_items[context->menu_index - 1]));
- }
+ furi_string_get_cstr(context->main_menu_items[context->menu_index]));
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(
- canvas,
- 64,
- 36,
- AlignCenter,
- AlignTop,
- furi_string_get_cstr(main_menu_items[context->menu_index]));
+ if(context->menu_index < iBtnFuzzerAttackLoadFileCustomUids) {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str_aligned(
+ canvas,
+ 64,
+ 48,
+ AlignCenter,
+ AlignTop,
+ furi_string_get_cstr(context->main_menu_items[context->menu_index + 1]));
+ }
- if(context->menu_index < iBtnFuzzerAttackLoadFileCustomUids) {
- canvas_set_font(canvas, FontSecondary);
- canvas_draw_str_aligned(
- canvas,
- 64,
- 48,
- AlignCenter,
- AlignTop,
- furi_string_get_cstr(main_menu_items[context->menu_index + 1]));
- }
+ if(context->menu_proto_index > DS1990) {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str_aligned(
+ canvas,
+ 64,
+ -12,
+ AlignCenter,
+ AlignTop,
+ furi_string_get_cstr(
+ context->main_menu_proto_items[context->menu_proto_index - 1]));
+ }
- if(context->menu_proto_index > DS1990) {
- canvas_set_font(canvas, FontSecondary);
- canvas_draw_str_aligned(
- canvas,
- 64,
- -12,
- AlignCenter,
- AlignTop,
- furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index - 1]));
- }
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str_aligned(canvas, 27, 4, AlignCenter, AlignTop, "<");
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(canvas, 27, 4, AlignCenter, AlignTop, "<");
+ canvas_set_font(canvas, FontPrimary);
+ if(context->main_menu_proto_items[context->menu_proto_index] != NULL) {
+ canvas_draw_str_aligned(
+ canvas,
+ 64,
+ 4,
+ AlignCenter,
+ AlignTop,
+ furi_string_get_cstr(
+ context->main_menu_proto_items[context->menu_proto_index]));
+ }
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str_aligned(canvas, 101, 4, AlignCenter, AlignTop, ">");
- canvas_set_font(canvas, FontPrimary);
- if(main_menu_proto_items[context->menu_proto_index] != NULL) {
- canvas_draw_str_aligned(
- canvas,
- 64,
- 4,
- AlignCenter,
- AlignTop,
- furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index]));
- }
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(canvas, 101, 4, AlignCenter, AlignTop, ">");
-
- if(context->menu_proto_index < Cyfral) {
- canvas_set_font(canvas, FontSecondary);
- canvas_draw_str_aligned(
- canvas,
- 64,
- -12,
- AlignCenter,
- AlignTop,
- furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index + 1]));
+ if(context->menu_proto_index < Cyfral) {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str_aligned(
+ canvas,
+ 64,
+ -12,
+ AlignCenter,
+ AlignTop,
+ furi_string_get_cstr(
+ context->main_menu_proto_items[context->menu_proto_index + 1]));
+ }
}
}
}
\ No newline at end of file
diff --git a/applications/external/ibtn_fuzzer/scene/ibtnfuzzer_scene_run_attack.c b/applications/external/ibtn_fuzzer/scene/ibtnfuzzer_scene_run_attack.c
index 1cab8b04e..53f2f694f 100644
--- a/applications/external/ibtn_fuzzer/scene/ibtnfuzzer_scene_run_attack.c
+++ b/applications/external/ibtn_fuzzer/scene/ibtnfuzzer_scene_run_attack.c
@@ -3,7 +3,7 @@
uint8_t counter = 0;
-uint8_t id_list_ds1990[25][8] = {
+uint8_t id_list_ds1990[18][8] = {
{0x01, 0xBE, 0x40, 0x11, 0x5A, 0x36, 0x00, 0xE1}, //β ΠΊΠΎΠ΄ ΡΠ½ΠΈΠ²Π΅ΡΡΠ°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΊΠ»ΡΡΠ°, Π΄Π»Ρ Vizit
{0x01, 0xBE, 0x40, 0x11, 0x5A, 0x56, 0x00, 0xBB}, //- ΠΏΡΠΎΠ²Π΅ΡΠ΅Π½ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ
{0x01, 0xBE, 0x40, 0x11, 0x00, 0x00, 0x00, 0x77}, //- ΠΏΡΠΎΠ²Π΅ΡΠ΅Π½ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ
@@ -19,16 +19,9 @@ uint8_t id_list_ds1990[25][8] = {
{0x01, 0x00, 0xBE, 0x11, 0xAA, 0x00, 0x00, 0xFB}, //???-Π΄ΠΎΠΌΠΎΡΠΎΠ½Ρ ΠΠ΅ΠΉΠΌΠ°Π½ (KEYMAN)
{0x01, 0x76, 0xB8, 0x2E, 0x0F, 0x00, 0x00, 0x5C}, //???-Π΄ΠΎΠΌΠΎΡΠΎΠ½Ρ Π€ΠΎΡΠ²Π°ΡΠ΄
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes
- {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // Only FF
- {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, // Only 11
- {0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, // Only 22
- {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}, // Only 33
- {0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, // Only 44
- {0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55}, // Only 55
- {0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}, // Only 66
- {0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77}, // Only 77
- {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88}, // Only 88
- {0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99}, // Only 99
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x14}, // Only FF
+ {0x01, 0x78, 0x00, 0x48, 0xFD, 0xFF, 0xFF, 0xD1}, // StarNew Uni5
+ {0x01, 0xA9, 0xE4, 0x3C, 0x09, 0x00, 0x00, 0xE6}, // Eltis Uni
};
uint8_t id_list_metakom[17][4] = {
@@ -51,7 +44,7 @@ uint8_t id_list_metakom[17][4] = {
{0xCA, 0xCA, 0xCA, 0xCA}, // ??
};
-uint8_t id_list_cyfral[14][2] = {
+uint8_t id_list_cyfral[16][2] = {
{0x00, 0x00}, // Null bytes
{0xFF, 0xFF}, // Only FF
{0x11, 0x11}, // Only 11
@@ -66,6 +59,8 @@ uint8_t id_list_cyfral[14][2] = {
{0x12, 0x34}, // Incremental UID
{0x56, 0x34}, // Decremental UID
{0xCA, 0xCA}, // ??
+ {0x8E, 0xC9}, // Elevator code
+ {0x6A, 0x50}, // VERY fresh code from smartkey
};
void ibtnfuzzer_scene_run_attack_on_enter(iBtnFuzzerState* context) {
@@ -130,7 +125,7 @@ void ibtnfuzzer_scene_run_attack_on_tick(iBtnFuzzerState* context) {
context->payload[6] = id_list_ds1990[context->attack_step][6];
context->payload[7] = id_list_ds1990[context->attack_step][7];
- if(context->attack_step == 24) {
+ if(context->attack_step == 17) {
context->attack_step = 0;
counter = 0;
context->is_attacking = false;
@@ -160,7 +155,7 @@ void ibtnfuzzer_scene_run_attack_on_tick(iBtnFuzzerState* context) {
context->payload[0] = id_list_cyfral[context->attack_step][0];
context->payload[1] = id_list_cyfral[context->attack_step][1];
- if(context->attack_step == 13) {
+ if(context->attack_step == 15) {
context->attack_step = 0;
counter = 0;
context->is_attacking = false;
@@ -247,7 +242,7 @@ void ibtnfuzzer_scene_run_attack_on_tick(iBtnFuzzerState* context) {
stream_rewind(context->uids_stream);
end_of_list = true;
break;
- };
+ }
if(furi_string_get_char(context->data_str, 0) == '#') continue;
if(furi_string_size(context->data_str) != 17) break;
break;
@@ -261,7 +256,7 @@ void ibtnfuzzer_scene_run_attack_on_tick(iBtnFuzzerState* context) {
notification_message(context->notify, &sequence_blink_stop);
notification_message(context->notify, &sequence_error);
break;
- };
+ }
// string is valid, parse it in context->payload
for(uint8_t i = 0; i < 8; i++) {
@@ -285,7 +280,7 @@ void ibtnfuzzer_scene_run_attack_on_tick(iBtnFuzzerState* context) {
stream_rewind(context->uids_stream);
end_of_list = true;
break;
- };
+ }
if(furi_string_get_char(context->data_str, 0) == '#') continue;
if(furi_string_size(context->data_str) != 5) break;
break;
@@ -299,7 +294,7 @@ void ibtnfuzzer_scene_run_attack_on_tick(iBtnFuzzerState* context) {
notification_message(context->notify, &sequence_blink_stop);
notification_message(context->notify, &sequence_error);
break;
- };
+ }
// string is valid, parse it in context->payload
for(uint8_t i = 0; i < 2; i++) {
@@ -323,7 +318,7 @@ void ibtnfuzzer_scene_run_attack_on_tick(iBtnFuzzerState* context) {
stream_rewind(context->uids_stream);
end_of_list = true;
break;
- };
+ }
if(furi_string_get_char(context->data_str, 0) == '#') continue;
if(furi_string_size(context->data_str) != 9) break;
break;
@@ -337,7 +332,7 @@ void ibtnfuzzer_scene_run_attack_on_tick(iBtnFuzzerState* context) {
notification_message(context->notify, &sequence_blink_stop);
notification_message(context->notify, &sequence_error);
break;
- };
+ }
// string is valid, parse it in context->payload
for(uint8_t i = 0; i < 4; i++) {
diff --git a/applications/external/ifttt/ifttt_virtual_button.c b/applications/external/ifttt/ifttt_virtual_button.c
index e23b8715d..1d2d06416 100644
--- a/applications/external/ifttt/ifttt_virtual_button.c
+++ b/applications/external/ifttt/ifttt_virtual_button.c
@@ -38,6 +38,11 @@ void save_settings_file(FlipperFormat* file, Settings* settings) {
Settings* load_settings() {
Settings* settings = malloc(sizeof(Settings));
+ settings->save_ssid = "";
+ settings->save_password = "";
+ settings->save_key = "";
+ settings->save_event = "";
+
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file = flipper_format_file_alloc(storage);
@@ -53,29 +58,14 @@ Settings* load_settings() {
text_event_value = furi_string_alloc();
if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) != FSE_OK) {
- if(!flipper_format_file_open_new(file, CONFIG_FILE_PATH)) {
- flipper_format_file_close(file);
- } else {
- settings->save_ssid = malloc(1);
- settings->save_password = malloc(1);
- settings->save_key = malloc(1);
- settings->save_event = malloc(1);
-
- settings->save_ssid[0] = '\0';
- settings->save_password[0] = '\0';
- settings->save_key[0] = '\0';
- settings->save_event[0] = '\0';
-
+ if(flipper_format_file_open_new(file, CONFIG_FILE_PATH)) {
save_settings_file(file, settings);
- flipper_format_file_close(file);
}
+ flipper_format_file_close(file);
} else {
- if(!flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
- flipper_format_file_close(file);
- } else {
+ if(flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
uint32_t value;
- if(!flipper_format_read_header(file, string_value, &value)) {
- } else {
+ if(flipper_format_read_header(file, string_value, &value)) {
if(flipper_format_read_string(file, CONF_SSID, text_ssid_value)) {
settings->save_ssid = malloc(furi_string_size(text_ssid_value) + 1);
strcpy(settings->save_ssid, furi_string_get_cstr(text_ssid_value));
@@ -93,8 +83,8 @@ Settings* load_settings() {
strcpy(settings->save_event, furi_string_get_cstr(text_event_value));
}
}
- flipper_format_file_close(file);
}
+ flipper_format_file_close(file);
}
furi_string_free(text_ssid_value);
@@ -139,7 +129,7 @@ void send_serial_command_config(ESerialCommand command, Settings* settings) {
break;
default:
return;
- };
+ }
furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
}
@@ -248,4 +238,4 @@ int32_t ifttt_virtual_button_app(void* p) {
view_dispatcher_run(app->view_dispatcher);
ifttt_virtual_button_app_free(app);
return 0;
-}
\ No newline at end of file
+}
diff --git a/applications/external/ifttt/views/send_view.c b/applications/external/ifttt/views/send_view.c
index 6046c39e3..e1638e7a7 100644
--- a/applications/external/ifttt/views/send_view.c
+++ b/applications/external/ifttt/views/send_view.c
@@ -38,7 +38,7 @@ void send_serial_command_send(ESerialCommand command) {
break;
default:
return;
- };
+ }
furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
}
diff --git a/applications/external/metronome/img/screenshot.png b/applications/external/metronome/img/screenshot.png
deleted file mode 100644
index 7b6916e81..000000000
Binary files a/applications/external/metronome/img/screenshot.png and /dev/null differ
diff --git a/applications/external/metronome/metronome.c b/applications/external/metronome/metronome.c
index a01f4418d..46231d66d 100644
--- a/applications/external/metronome/metronome.c
+++ b/applications/external/metronome/metronome.c
@@ -178,7 +178,7 @@ static void timer_callback(void* ctx) {
case Silent:
break;
}
- };
+ }
// this is a bit of a kludge... if we are on vibro and unpronounced, stop vibro after half the usual duration
switch(metronome_state->output_mode) {
diff --git a/applications/external/minesweeper/img/screenshot.png b/applications/external/minesweeper/img/screenshot.png
deleted file mode 100644
index 65b307c55..000000000
Binary files a/applications/external/minesweeper/img/screenshot.png and /dev/null differ
diff --git a/applications/external/music_beeper/music_beeper_worker.c b/applications/external/music_beeper/music_beeper_worker.c
index e06e77447..c95fe8d3a 100644
--- a/applications/external/music_beeper/music_beeper_worker.c
+++ b/applications/external/music_beeper/music_beeper_worker.c
@@ -399,7 +399,7 @@ bool music_beeper_worker_load_rtttl_from_file(MusicBeeperWorker* instance, const
if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_E(TAG, "Unable to open file");
break;
- };
+ }
uint16_t ret = 0;
do {
diff --git a/applications/external/music_player/music_player_worker.c b/applications/external/music_player/music_player_worker.c
index ee350ee80..6a712d3e3 100644
--- a/applications/external/music_player/music_player_worker.c
+++ b/applications/external/music_player/music_player_worker.c
@@ -397,7 +397,7 @@ bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const
if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_E(TAG, "Unable to open file");
break;
- };
+ }
uint16_t ret = 0;
do {
diff --git a/applications/external/musictracker/application.fam b/applications/external/music_tracker/application.fam
similarity index 100%
rename from applications/external/musictracker/application.fam
rename to applications/external/music_tracker/application.fam
diff --git a/applications/external/musictracker/tracker_engine/speaker_hal.c b/applications/external/music_tracker/tracker_engine/speaker_hal.c
similarity index 99%
rename from applications/external/musictracker/tracker_engine/speaker_hal.c
rename to applications/external/music_tracker/tracker_engine/speaker_hal.c
index 94489f1b6..0a506a424 100644
--- a/applications/external/musictracker/tracker_engine/speaker_hal.c
+++ b/applications/external/music_tracker/tracker_engine/speaker_hal.c
@@ -104,4 +104,4 @@ void tracker_debug_set(bool value) {
void tracker_debug_deinit() {
furi_hal_gpio_init(&gpio_ext_pc3, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
-}
\ No newline at end of file
+}
diff --git a/applications/external/musictracker/tracker_engine/speaker_hal.h b/applications/external/music_tracker/tracker_engine/speaker_hal.h
similarity index 100%
rename from applications/external/musictracker/tracker_engine/speaker_hal.h
rename to applications/external/music_tracker/tracker_engine/speaker_hal.h
diff --git a/applications/external/musictracker/tracker_engine/tracker.c b/applications/external/music_tracker/tracker_engine/tracker.c
similarity index 100%
rename from applications/external/musictracker/tracker_engine/tracker.c
rename to applications/external/music_tracker/tracker_engine/tracker.c
diff --git a/applications/external/musictracker/tracker_engine/tracker.h b/applications/external/music_tracker/tracker_engine/tracker.h
similarity index 100%
rename from applications/external/musictracker/tracker_engine/tracker.h
rename to applications/external/music_tracker/tracker_engine/tracker.h
diff --git a/applications/external/musictracker/tracker_engine/tracker_notes.h b/applications/external/music_tracker/tracker_engine/tracker_notes.h
similarity index 100%
rename from applications/external/musictracker/tracker_engine/tracker_notes.h
rename to applications/external/music_tracker/tracker_engine/tracker_notes.h
diff --git a/applications/external/musictracker/tracker_engine/tracker_song.h b/applications/external/music_tracker/tracker_engine/tracker_song.h
similarity index 100%
rename from applications/external/musictracker/tracker_engine/tracker_song.h
rename to applications/external/music_tracker/tracker_engine/tracker_song.h
diff --git a/applications/external/musictracker/view/tracker_view.c b/applications/external/music_tracker/view/tracker_view.c
similarity index 100%
rename from applications/external/musictracker/view/tracker_view.c
rename to applications/external/music_tracker/view/tracker_view.c
diff --git a/applications/external/musictracker/view/tracker_view.h b/applications/external/music_tracker/view/tracker_view.h
similarity index 100%
rename from applications/external/musictracker/view/tracker_view.h
rename to applications/external/music_tracker/view/tracker_view.h
diff --git a/applications/external/musictracker/zero_tracker.c b/applications/external/music_tracker/zero_tracker.c
similarity index 100%
rename from applications/external/musictracker/zero_tracker.c
rename to applications/external/music_tracker/zero_tracker.c
diff --git a/applications/external/musictracker/zero_tracker.h b/applications/external/music_tracker/zero_tracker.h
similarity index 100%
rename from applications/external/musictracker/zero_tracker.h
rename to applications/external/music_tracker/zero_tracker.h
diff --git a/applications/external/musictracker/zero_tracker.png b/applications/external/music_tracker/zero_tracker.png
similarity index 100%
rename from applications/external/musictracker/zero_tracker.png
rename to applications/external/music_tracker/zero_tracker.png
diff --git a/applications/external/nightstand_clock/application.fam b/applications/external/nightstand/application.fam
similarity index 100%
rename from applications/external/nightstand_clock/application.fam
rename to applications/external/nightstand/application.fam
diff --git a/applications/external/nightstand_clock/clock.png b/applications/external/nightstand/clock.png
similarity index 100%
rename from applications/external/nightstand_clock/clock.png
rename to applications/external/nightstand/clock.png
diff --git a/applications/external/nightstand_clock/clock_app.c b/applications/external/nightstand/clock_app.c
similarity index 100%
rename from applications/external/nightstand_clock/clock_app.c
rename to applications/external/nightstand/clock_app.c
diff --git a/applications/external/nightstand_clock/clock_app.h b/applications/external/nightstand/clock_app.h
similarity index 100%
rename from applications/external/nightstand_clock/clock_app.h
rename to applications/external/nightstand/clock_app.h
diff --git a/applications/external/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c
index 06d361fb5..5e85e6eda 100644
--- a/applications/external/picopass/picopass_worker.c
+++ b/applications/external/picopass/picopass_worker.c
@@ -570,7 +570,7 @@ void picopass_worker_elite_dict_attack(PicopassWorker* picopass_worker) {
picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context);
break;
}
- picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context);
+ picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context);
break;
}
@@ -596,6 +596,9 @@ int32_t picopass_worker_task(void* context) {
picopass_worker_write_key(picopass_worker);
} else if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) {
picopass_worker_elite_dict_attack(picopass_worker);
+ } else if(picopass_worker->state == PicopassWorkerStateStop) {
+ FURI_LOG_D(TAG, "Worker state stop");
+ // no-op
} else {
FURI_LOG_W(TAG, "Unknown state %d", picopass_worker->state);
}
diff --git a/applications/external/picopass/rfal_picopass.c b/applications/external/picopass/rfal_picopass.c
index ac66cb92d..e8ca64403 100644
--- a/applications/external/picopass/rfal_picopass.c
+++ b/applications/external/picopass/rfal_picopass.c
@@ -48,7 +48,7 @@ FuriHalNfcReturn rfalPicoPassPollerInitialize(void) {
FuriHalNfcModePollPicopass, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48);
if(ret != FuriHalNfcReturnOk) {
return ret;
- };
+ }
furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc);
furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_PICOPASS);
diff --git a/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c b/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c
index c76a8ffae..e6191d5ba 100644
--- a/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c
+++ b/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c
@@ -116,8 +116,7 @@ bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent
uint32_t state =
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack);
if(event.type == SceneManagerEventTypeCustom) {
- if(event.event == PicopassWorkerEventSuccess ||
- event.event == PicopassWorkerEventAborted) {
+ if(event.event == PicopassWorkerEventSuccess) {
if(state == DictAttackStateUserDictInProgress ||
state == DictAttackStateStandardDictInProgress) {
picopass_worker_stop(picopass->worker);
@@ -127,6 +126,9 @@ bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
consumed = true;
}
+ } else if(event.event == PicopassWorkerEventAborted) {
+ scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
+ consumed = true;
} else if(event.event == PicopassWorkerEventCardDetected) {
dict_attack_set_card_detected(picopass->dict_attack);
consumed = true;
diff --git a/applications/external/picopass/scenes/picopass_scene_read_card_success.c b/applications/external/picopass/scenes/picopass_scene_read_card_success.c
index 198b21d98..cc18ac066 100644
--- a/applications/external/picopass/scenes/picopass_scene_read_card_success.c
+++ b/applications/external/picopass/scenes/picopass_scene_read_card_success.c
@@ -34,7 +34,7 @@ void picopass_scene_read_card_success_on_enter(void* context) {
uint8_t csn[PICOPASS_BLOCK_LEN] = {0};
memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
- furi_string_cat_printf(csn_str, "%02X ", csn[i]);
+ furi_string_cat_printf(csn_str, "%02X", csn[i]);
}
bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN);
diff --git a/applications/external/pocsag_pager/pocsag_pager_app.c b/applications/external/pocsag_pager/pocsag_pager_app.c
index d4b12c466..b52b2f37f 100644
--- a/applications/external/pocsag_pager/pocsag_pager_app.c
+++ b/applications/external/pocsag_pager/pocsag_pager_app.c
@@ -128,7 +128,8 @@ POCSAGPagerApp* pocsag_pager_app_alloc() {
// Auto switch to internal radio if external radio is not available
furi_delay_ms(15);
if(!furi_hal_subghz_check_radio()) {
- furi_hal_subghz_set_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_select_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
}
furi_hal_power_suppress_charge_enter();
@@ -146,6 +147,8 @@ void pocsag_pager_app_free(POCSAGPagerApp* app) {
// Disable power for External CC1101 if it was enabled and module is connected
furi_hal_subghz_disable_ext_power();
+ // Reinit SPI handles for internal radio / nfc
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
// Submenu
view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewSubmenu);
diff --git a/applications/external/pocsag_pager/pocsag_pager_app_i.c b/applications/external/pocsag_pager/pocsag_pager_app_i.c
index ff73ab50e..85e2a4bcf 100644
--- a/applications/external/pocsag_pager/pocsag_pager_app_i.c
+++ b/applications/external/pocsag_pager/pocsag_pager_app_i.c
@@ -131,7 +131,7 @@ void pcsg_hopper_update(POCSAGPagerApp* app) {
if(app->txrx->txrx_state == PCSGTxRxStateRx) {
pcsg_rx_end(app);
- };
+ }
if(app->txrx->txrx_state == PCSGTxRxStateIDLE) {
subghz_receiver_reset(app->txrx->receiver);
app->txrx->preset->frequency =
diff --git a/applications/external/pocsag_pager/scenes/pocsag_pager_receiver.c b/applications/external/pocsag_pager/scenes/pocsag_pager_receiver.c
index 658b70fea..60dca01c9 100644
--- a/applications/external/pocsag_pager/scenes/pocsag_pager_receiver.c
+++ b/applications/external/pocsag_pager/scenes/pocsag_pager_receiver.c
@@ -133,7 +133,7 @@ void pocsag_pager_scene_receiver_on_enter(void* context) {
if(app->txrx->txrx_state == PCSGTxRxStateRx) {
pcsg_rx_end(app);
- };
+ }
if((app->txrx->txrx_state == PCSGTxRxStateIDLE) ||
(app->txrx->txrx_state == PCSGTxRxStateSleep)) {
pcsg_begin(
@@ -158,7 +158,7 @@ bool pocsag_pager_scene_receiver_on_event(void* context, SceneManagerEvent event
if(app->txrx->txrx_state == PCSGTxRxStateRx) {
pcsg_rx_end(app);
pcsg_sleep(app);
- };
+ }
app->txrx->hopper_state = PCSGHopperStateOFF;
app->txrx->idx_menu_chosen = 0;
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app);
diff --git a/applications/external/pomodoro/flipp_pomodoro_app.c b/applications/external/pomodoro/flipp_pomodoro_app.c
index 5adca1edb..3ee3b0277 100644
--- a/applications/external/pomodoro/flipp_pomodoro_app.c
+++ b/applications/external/pomodoro/flipp_pomodoro_app.c
@@ -9,14 +9,14 @@ static bool flipp_pomodoro_app_back_event_callback(void* ctx) {
furi_assert(ctx);
FlippPomodoroApp* app = ctx;
return scene_manager_handle_back_event(app->scene_manager);
-};
+}
static void flipp_pomodoro_app_tick_event_callback(void* ctx) {
furi_assert(ctx);
FlippPomodoroApp* app = ctx;
scene_manager_handle_custom_event(app->scene_manager, FlippPomodoroAppCustomEventTimerTick);
-};
+}
static bool flipp_pomodoro_app_custom_event_callback(void* ctx, uint32_t event) {
furi_assert(ctx);
@@ -40,7 +40,7 @@ static bool flipp_pomodoro_app_custom_event_callback(void* ctx, uint32_t event)
break;
}
return scene_manager_handle_custom_event(app->scene_manager, event);
-};
+}
FlippPomodoroApp* flipp_pomodoro_app_alloc() {
FlippPomodoroApp* app = malloc(sizeof(FlippPomodoroApp));
@@ -71,17 +71,18 @@ FlippPomodoroApp* flipp_pomodoro_app_alloc() {
scene_manager_next_scene(app->scene_manager, FlippPomodoroSceneTimer);
return app;
-};
+}
void flipp_pomodoro_app_free(FlippPomodoroApp* app) {
view_dispatcher_remove_view(app->view_dispatcher, FlippPomodoroAppViewTimer);
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
flipp_pomodoro_view_timer_free(app->timer_view);
+ flipp_pomodoro__destroy(app->state);
free(app);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
-};
+}
int32_t flipp_pomodoro_app(void* p) {
UNUSED(p);
@@ -92,4 +93,4 @@ int32_t flipp_pomodoro_app(void* p) {
flipp_pomodoro_app_free(app);
return 0;
-};
+}
diff --git a/applications/external/pomodoro/helpers/time.c b/applications/external/pomodoro/helpers/time.c
index 7fb0d13c2..02540a939 100644
--- a/applications/external/pomodoro/helpers/time.c
+++ b/applications/external/pomodoro/helpers/time.c
@@ -7,7 +7,7 @@ const int TIME_MINUTES_IN_HOUR = 60;
uint32_t time_now() {
return furi_hal_rtc_get_timestamp();
-};
+}
TimeDifference time_difference_seconds(uint32_t begin, uint32_t end) {
const uint32_t duration_seconds = end - begin;
@@ -17,4 +17,4 @@ TimeDifference time_difference_seconds(uint32_t begin, uint32_t end) {
return (
TimeDifference){.total_seconds = duration_seconds, .minutes = minutes, .seconds = seconds};
-};
+}
diff --git a/applications/external/pomodoro/modules/flipp_pomodoro.c b/applications/external/pomodoro/modules/flipp_pomodoro.c
index 161e862f8..cd417f791 100644
--- a/applications/external/pomodoro/modules/flipp_pomodoro.c
+++ b/applications/external/pomodoro/modules/flipp_pomodoro.c
@@ -38,21 +38,26 @@ void flipp_pomodoro__toggle_stage(FlippPomodoroState* state) {
furi_assert(state);
state->current_stage_index = state->current_stage_index + 1;
state->started_at_timestamp = time_now();
-};
+}
PomodoroStage flipp_pomodoro__get_stage(FlippPomodoroState* state) {
furi_assert(state);
return flipp_pomodoro__stage_by_index(state->current_stage_index);
-};
+}
char* flipp_pomodoro__current_stage_label(FlippPomodoroState* state) {
furi_assert(state);
return current_stage_label[flipp_pomodoro__get_stage(state)];
-};
+}
char* flipp_pomodoro__next_stage_label(FlippPomodoroState* state) {
furi_assert(state);
return next_stage_label[flipp_pomodoro__stage_by_index(state->current_stage_index + 1)];
+}
+
+void flipp_pomodoro__destroy(FlippPomodoroState* state) {
+ furi_assert(state);
+ free(state);
};
uint32_t flipp_pomodoro__current_stage_total_duration(FlippPomodoroState* state) {
@@ -63,22 +68,22 @@ uint32_t flipp_pomodoro__current_stage_total_duration(FlippPomodoroState* state)
};
return stage_duration_seconds_map[flipp_pomodoro__get_stage(state)];
-};
+}
uint32_t flipp_pomodoro__stage_expires_timestamp(FlippPomodoroState* state) {
return state->started_at_timestamp + flipp_pomodoro__current_stage_total_duration(state);
-};
+}
TimeDifference flipp_pomodoro__stage_remaining_duration(FlippPomodoroState* state) {
const uint32_t stage_ends_at = flipp_pomodoro__stage_expires_timestamp(state);
return time_difference_seconds(time_now(), stage_ends_at);
-};
+}
bool flipp_pomodoro__is_stage_expired(FlippPomodoroState* state) {
const uint32_t expired_by = flipp_pomodoro__stage_expires_timestamp(state);
const uint8_t seamless_change_span_seconds = 1;
return (time_now() - seamless_change_span_seconds) >= expired_by;
-};
+}
FlippPomodoroState* flipp_pomodoro__new() {
FlippPomodoroState* state = malloc(sizeof(FlippPomodoroState));
@@ -86,4 +91,4 @@ FlippPomodoroState* flipp_pomodoro__new() {
state->started_at_timestamp = now;
state->current_stage_index = 0;
return state;
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/applications/external/pomodoro/scenes/flipp_pomodoro_scene_timer.c b/applications/external/pomodoro/scenes/flipp_pomodoro_scene_timer.c
index 2190dbdb7..8ed5dd5e7 100644
--- a/applications/external/pomodoro/scenes/flipp_pomodoro_scene_timer.c
+++ b/applications/external/pomodoro/scenes/flipp_pomodoro_scene_timer.c
@@ -15,7 +15,7 @@ void flipp_pomodoro_scene_timer_sync_view_state(void* ctx) {
flipp_pomodoro_view_timer_set_state(
flipp_pomodoro_view_timer_get_view(app->timer_view), app->state);
-};
+}
void flipp_pomodoro_scene_timer_on_next_stage(void* ctx) {
furi_assert(ctx);
@@ -23,7 +23,7 @@ void flipp_pomodoro_scene_timer_on_next_stage(void* ctx) {
FlippPomodoroApp* app = ctx;
view_dispatcher_send_custom_event(app->view_dispatcher, FlippPomodoroAppCustomEventStageSkip);
-};
+}
void flipp_pomodoro_scene_timer_on_enter(void* ctx) {
furi_assert(ctx);
@@ -34,7 +34,7 @@ void flipp_pomodoro_scene_timer_on_enter(void* ctx) {
flipp_pomodoro_scene_timer_sync_view_state(app);
flipp_pomodoro_view_timer_set_on_right_cb(
app->timer_view, flipp_pomodoro_scene_timer_on_next_stage, app);
-};
+}
void flipp_pomodoro_scene_timer_handle_custom_event(
FlippPomodoroApp* app,
@@ -48,7 +48,7 @@ void flipp_pomodoro_scene_timer_handle_custom_event(
if(custom_event == FlippPomodoroAppCustomEventStateUpdated) {
flipp_pomodoro_scene_timer_sync_view_state(app);
}
-};
+}
bool flipp_pomodoro_scene_timer_on_event(void* ctx, SceneManagerEvent event) {
furi_assert(ctx);
@@ -62,10 +62,10 @@ bool flipp_pomodoro_scene_timer_on_event(void* ctx, SceneManagerEvent event) {
return ExitSignal;
default:
break;
- };
+ }
return SceneEventNotConusmed;
-};
+}
void flipp_pomodoro_scene_timer_on_exit(void* ctx) {
UNUSED(ctx);
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/applications/external/pomodoro/views/flipp_pomodoro_timer_view.c b/applications/external/pomodoro/views/flipp_pomodoro_timer_view.c
index e8e0383b7..302380ddd 100644
--- a/applications/external/pomodoro/views/flipp_pomodoro_timer_view.c
+++ b/applications/external/pomodoro/views/flipp_pomodoro_timer_view.c
@@ -58,7 +58,7 @@ static void
remaining_stage_time_string);
furi_string_free(timer_string);
-};
+}
static void draw_str_with_drop_shadow(
Canvas* canvas,
@@ -92,7 +92,7 @@ static void
static void flipp_pomodoro_view_timer_draw_callback(Canvas* canvas, void* _model) {
if(!_model) {
return;
- };
+ }
FlippPomodoroTimerViewModel* model = _model;
@@ -109,7 +109,7 @@ static void flipp_pomodoro_view_timer_draw_callback(Canvas* canvas, void* _model
canvas_set_font(canvas, FontSecondary);
elements_button_right(canvas, flipp_pomodoro__next_stage_label(model->state));
-};
+}
bool flipp_pomodoro_view_timer_input_callback(InputEvent* event, void* ctx) {
furi_assert(ctx);
@@ -125,15 +125,15 @@ bool flipp_pomodoro_view_timer_input_callback(InputEvent* event, void* ctx) {
furi_assert(timer->right_cb_ctx);
timer->right_cb(timer->right_cb_ctx);
return ViewInputConsumed;
- };
+ }
return ViewInputNotConusmed;
-};
+}
View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer) {
furi_assert(timer);
return timer->view;
-};
+}
void flipp_pomodoro_view_timer_assign_animation(View* view) {
with_view_model(
@@ -162,7 +162,7 @@ FlippPomodoroTimerView* flipp_pomodoro_view_timer_alloc() {
view_set_input_callback(timer->view, flipp_pomodoro_view_timer_input_callback);
return timer;
-};
+}
void flipp_pomodoro_view_timer_set_on_right_cb(
FlippPomodoroTimerView* timer,
@@ -172,7 +172,7 @@ void flipp_pomodoro_view_timer_set_on_right_cb(
furi_assert(right_cb_ctx);
timer->right_cb = right_cb;
timer->right_cb_ctx = right_cb_ctx;
-};
+}
void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state) {
furi_assert(view);
@@ -180,7 +180,7 @@ void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state)
with_view_model(
view, FlippPomodoroTimerViewModel * model, { model->state = state; }, false);
flipp_pomodoro_view_timer_assign_animation(view);
-};
+}
void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView* timer) {
furi_assert(timer);
@@ -192,4 +192,4 @@ void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView* timer) {
view_free(timer->view);
free(timer);
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/applications/external/pong/flipper_pong.c b/applications/external/pong/flipper_pong.c
index 55b371ad5..53c6a7e27 100644
--- a/applications/external/pong/flipper_pong.c
+++ b/applications/external/pong/flipper_pong.c
@@ -15,8 +15,7 @@
#define PAD_SIZE_X 3
#define PAD_SIZE_Y 8
-#define PLAYER1_PAD_SPEED 4
-
+#define PLAYER1_PAD_SPEED 2
#define PLAYER2_PAD_SPEED 2
#define BALL_SIZE 4
@@ -39,29 +38,22 @@ typedef struct Players {
static void draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
- Players* playersMutex = ctx;
- furi_mutex_acquire(playersMutex->mutex, FuriWaitForever);
+ Players* players = ctx;
+ furi_mutex_acquire(players->mutex, FuriWaitForever);
canvas_draw_frame(canvas, 0, 0, 128, 64);
- canvas_draw_box(
- canvas, playersMutex->player1_X, playersMutex->player1_Y, PAD_SIZE_X, PAD_SIZE_Y);
- canvas_draw_box(
- canvas, playersMutex->player2_X, playersMutex->player2_Y, PAD_SIZE_X, PAD_SIZE_Y);
- canvas_draw_box(canvas, playersMutex->ball_X, playersMutex->ball_Y, BALL_SIZE, BALL_SIZE);
+ canvas_draw_box(canvas, players->player1_X, players->player1_Y, PAD_SIZE_X, PAD_SIZE_Y);
+ canvas_draw_box(canvas, players->player2_X, players->player2_Y, PAD_SIZE_X, PAD_SIZE_Y);
+ canvas_draw_box(canvas, players->ball_X, players->ball_Y, BALL_SIZE, BALL_SIZE);
canvas_set_font(canvas, FontPrimary);
canvas_set_font_direction(canvas, CanvasDirectionBottomToTop);
char buffer[16];
- snprintf(
- buffer,
- sizeof(buffer),
- "%u - %u",
- playersMutex->player1_score,
- playersMutex->player2_score);
+ snprintf(buffer, sizeof(buffer), "%u - %u", players->player1_score, players->player2_score);
canvas_draw_str_aligned(
canvas, SCREEN_SIZE_X / 2 + 15, SCREEN_SIZE_Y / 2 + 2, AlignCenter, AlignTop, buffer);
- furi_mutex_release(playersMutex->mutex);
+ furi_mutex_release(players->mutex);
}
static void input_callback(InputEvent* input_event, void* ctx) {
@@ -101,8 +93,7 @@ uint8_t changeDirection() {
return randomuint8[0];
}
-int32_t flipper_pong_app(void* p) {
- UNUSED(p);
+int32_t flipper_pong_app() {
EventApp event;
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(EventApp));
@@ -129,7 +120,7 @@ int32_t flipper_pong_app(void* p) {
}
ViewPort* view_port = view_port_alloc();
- view_port_draw_callback_set(view_port, draw_callback, &players);
+ view_port_draw_callback_set(view_port, draw_callback, &players.mutex);
view_port_input_callback_set(view_port, input_callback, event_queue);
Gui* gui = furi_record_open(RECORD_GUI);
@@ -152,7 +143,6 @@ int32_t flipper_pong_app(void* p) {
if(event.type == EventTypeInput) {
if(event.input.key == InputKeyBack) {
furi_mutex_release(players.mutex);
- notification_message(notification, &sequence_set_only_green_255);
break;
} else if(event.input.key == InputKeyUp) {
if(players.player1_Y >= 1 + PLAYER1_PAD_SPEED)
diff --git a/applications/external/protoview/app.c b/applications/external/protoview/app.c
index 2d3acccac..70205a0a4 100644
--- a/applications/external/protoview/app.c
+++ b/applications/external/protoview/app.c
@@ -173,7 +173,8 @@ ProtoViewApp* protoview_app_alloc() {
// Auto switch to internal radio if external radio is not available
furi_delay_ms(15);
if(!furi_hal_subghz_check_radio()) {
- furi_hal_subghz_set_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_select_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
}
furi_hal_power_suppress_charge_enter();
@@ -193,6 +194,8 @@ void protoview_app_free(ProtoViewApp* app) {
// Disable power for External CC1101 if it was enabled and module is connected
furi_hal_subghz_disable_ext_power();
+ // Reinit SPI handles for internal radio / nfc
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
// View related.
view_port_enabled_set(app->view_port, false);
diff --git a/applications/external/rc2014_coleco/coleco.c b/applications/external/rc2014_coleco/coleco.c
index 311b0ceac..f0a4c6188 100644
--- a/applications/external/rc2014_coleco/coleco.c
+++ b/applications/external/rc2014_coleco/coleco.c
@@ -40,15 +40,14 @@ typedef struct {
} PluginEvent;
typedef struct {
- FuriMutex* mutex;
bool dpad;
int row;
int column;
+ FuriMutex* mutex;
} Coleco;
static void render_callback(Canvas* const canvas, void* context) {
- furi_assert(context);
- Coleco* coleco = context;
+ Coleco* coleco = (Coleco*)context;
furi_mutex_acquire(coleco->mutex, FuriWaitForever);
if(coleco->dpad) {
@@ -175,12 +174,20 @@ static Coleco* coleco_alloc() {
coleco->row = 0;
coleco->column = 1;
+ coleco->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+ if(!coleco->mutex) {
+ FURI_LOG_E("Coleco", "cannot create mutex\r\n");
+ free(coleco);
+ return NULL;
+ }
+
return coleco;
}
static void coleco_free(Coleco* coleco) {
furi_assert(coleco);
+ furi_mutex_free(coleco->mutex);
free(coleco);
}
@@ -190,11 +197,7 @@ int32_t coleco_app(void* p) {
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
Coleco* coleco = coleco_alloc();
-
- coleco->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
- if(!coleco->mutex) {
- FURI_LOG_E("Coleco", "cannot create mutex\r\n");
- coleco_free(coleco);
+ if(coleco == NULL) {
return 255;
}
@@ -346,6 +349,8 @@ int32_t coleco_app(void* p) {
view_port_update(view_port);
}
+ } else {
+ FURI_LOG_D("Coleco", "FuriMessageQueue: event timeout");
}
furi_mutex_release(coleco->mutex);
@@ -358,7 +363,6 @@ int32_t coleco_app(void* p) {
furi_record_close("gui");
view_port_free(view_port);
furi_message_queue_free(event_queue);
- furi_mutex_free(coleco->mutex);
coleco_free(coleco);
return 0;
}
diff --git a/applications/external/rubiks_cube_scrambler/rubiks_cube_scrambler.c b/applications/external/rubiks_cube_scrambler/rubiks_cube_scrambler.c
index 4c845b883..c4ceae4ab 100644
--- a/applications/external/rubiks_cube_scrambler/rubiks_cube_scrambler.c
+++ b/applications/external/rubiks_cube_scrambler/rubiks_cube_scrambler.c
@@ -8,11 +8,11 @@
#include "scrambler.h"
#include "furi_hal_random.h"
-int scrambleStarted = 0;
+bool scrambleStarted = false;
char scramble_str[100] = {0};
char scramble_start[100] = {0};
char scramble_end[100] = {0};
-int notifications_enabled = 0;
+bool notifications_enabled = false;
static void success_vibration() {
furi_hal_vibro_on(false);
@@ -22,12 +22,12 @@ static void success_vibration() {
return;
}
void split_array(char original[], int size, char first[], char second[]) {
- int mid = size / 2;
+ int32_t mid = size / 2;
if(size % 2 != 0) {
mid++;
}
- int first_index = 0, second_index = 0;
- for(int i = 0; i < size; i++) {
+ int32_t first_index = 0, second_index = 0;
+ for(int32_t i = 0; i < size; i++) {
if(i < mid) {
first[first_index++] = original[i];
} else {
@@ -40,30 +40,24 @@ void split_array(char original[], int size, char first[], char second[]) {
first[first_index] = '\0';
second[second_index] = '\0';
}
+void genScramble() {
+ scrambleReplace();
+ strcpy(scramble_str, printData());
+ split_array(scramble_str, strlen(scramble_str), scramble_start, scramble_end);
+}
static void draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 4, 13, "Rubik's Cube Scrambler");
-
- if(scrambleStarted) {
- genScramble();
- scrambleReplace();
- strcpy(scramble_str, printData());
- if(notifications_enabled) {
- success_vibration();
- }
- split_array(scramble_str, strlen(scramble_str), scramble_start, scramble_end);
- scrambleStarted = 0;
- }
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 64, 28, AlignCenter, AlignCenter, scramble_start);
canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignCenter, scramble_end);
elements_button_center(canvas, "New");
elements_button_left(canvas, notifications_enabled ? "On" : "Off");
-};
+}
static void input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
@@ -90,13 +84,16 @@ int32_t rubiks_cube_scrambler_main(void* p) {
furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk);
if(event.key == InputKeyOk && event.type == InputTypeShort) {
- scrambleStarted = 1;
+ genScramble();
+ if(notifications_enabled) {
+ success_vibration();
+ }
}
if(event.key == InputKeyLeft && event.type == InputTypeShort) {
if(notifications_enabled) {
- notifications_enabled = 0;
+ notifications_enabled = false;
} else {
- notifications_enabled = 1;
+ notifications_enabled = true;
success_vibration();
}
}
diff --git a/applications/external/rubiks_cube_scrambler/scrambler.c b/applications/external/rubiks_cube_scrambler/scrambler.c
index ea5291940..b97a67400 100644
--- a/applications/external/rubiks_cube_scrambler/scrambler.c
+++ b/applications/external/rubiks_cube_scrambler/scrambler.c
@@ -12,66 +12,37 @@ Authors: Tanish Bhongade and RaZe
// 6 moves along with direction
char moves[6] = {'R', 'U', 'F', 'B', 'L', 'D'};
-char dir[4] = {' ', '\'', '2'};
-const int SLEN = 20;
+char dir[4] = {'\'', '2'};
+const int32_t SLEN = 20;
#define RESULT_SIZE 100
-// Structure which holds main scramble
+
struct GetScramble {
char mainScramble[25][3];
};
-struct GetScramble a; // Its object
-
-// Function prototypes to avoid bugs
-void scrambleReplace();
-void genScramble();
-void valid();
-int getRand(int upr, int lwr);
-char* printData();
-void writeToFile();
-
-// Main function
-/* int main(){
- genScramble ();//Calling genScramble
- scrambleReplace();//Calling scrambleReplace
- valid();//Calling valid to validate the scramble
- printData ();//Printing the final scramble
- //writeToFile();//If you want to write to a file, please uncomment this
-
- return 0;
-} */
-
-void genScramble() {
- // Stage 1
- for(int i = 0; i < SLEN; i++) {
- strcpy(a.mainScramble[i], "00");
- }
- // This makes array like this 00 00 00.......
-}
+struct GetScramble a;
void scrambleReplace() {
- // Stage 2
- // Actual process begins here
-
// Initialize the mainScramble array with all the possible moves
- for(int i = 0; i < SLEN; i++) {
+ for(int32_t i = 0; i < SLEN; i++) {
a.mainScramble[i][0] = moves[furi_hal_random_get() % 6];
a.mainScramble[i][1] = dir[furi_hal_random_get() % 3];
}
- // Perform the Fisher-Yates shuffle
- for(int i = 6 - 1; i > 0; i--) {
- int j = rand() % (i + 1);
+ /* // Perform the Fisher-Yates shuffle
+ for (int32_t i = 6 - 1; i > 0; i--)
+ {
+ int32_t j = rand() % (i + 1);
char temp[3];
strcpy(temp, a.mainScramble[i]);
strcpy(a.mainScramble[i], a.mainScramble[j]);
strcpy(a.mainScramble[j], temp);
- }
+ } */
- // Select the first 10 elements as the scramble, using only the first three elements of the dir array
- for(int i = 0; i < SLEN; i++) {
+ // Select the first 10 elements as the scramble, using only the first two elements of the dir array
+ for(int32_t i = 0; i < SLEN; i++) {
a.mainScramble[i][1] = dir[furi_hal_random_get() % 3];
}
- for(int i = 1; i < SLEN; i++) {
+ for(int32_t i = 1; i < SLEN; i++) {
while(a.mainScramble[i][0] == a.mainScramble[i - 2][0] ||
a.mainScramble[i][0] == a.mainScramble[i - 1][0]) {
a.mainScramble[i][0] = moves[furi_hal_random_get() % 5];
@@ -79,24 +50,11 @@ void scrambleReplace() {
}
}
-// Let this function be here for now till I find out what is causing the extra space bug in the scrambles
-void remove_double_spaces(char* str) {
- int i, j;
- int len = strlen(str);
- for(i = 0, j = 0; i < len; i++, j++) {
- if(str[i] == ' ' && str[i + 1] == ' ') {
- i++;
- }
- str[j] = str[i];
- }
- str[j] = '\0';
-}
char* printData() {
static char result[RESULT_SIZE];
- int offset = 0;
- for(int loop = 0; loop < SLEN; loop++) {
+ int32_t offset = 0;
+ for(int32_t loop = 0; loop < SLEN; loop++) {
offset += snprintf(result + offset, RESULT_SIZE - offset, "%s ", a.mainScramble[loop]);
}
- remove_double_spaces(result);
return result;
-}
+}
\ No newline at end of file
diff --git a/applications/external/rubiks_cube_scrambler/scrambler.h b/applications/external/rubiks_cube_scrambler/scrambler.h
index 4b56c565d..557ef20ae 100644
--- a/applications/external/rubiks_cube_scrambler/scrambler.h
+++ b/applications/external/rubiks_cube_scrambler/scrambler.h
@@ -1,3 +1,2 @@
void scrambleReplace();
-void genScramble();
char* printData();
diff --git a/applications/external/sam/stm32_sam.cpp b/applications/external/sam/stm32_sam.cpp
index 16f6fcaab..1ab73a66d 100644
--- a/applications/external/sam/stm32_sam.cpp
+++ b/applications/external/sam/stm32_sam.cpp
@@ -3945,7 +3945,7 @@ void STM32SAM::Code41240() {
Insert(pos + 1, index + 1, phonemeLengthTable[index + 1], stress[pos]);
Insert(pos + 2, index + 2, phonemeLengthTable[index + 2], stress[pos]);
pos += 3;
- };
+ }
}
// Rewrites the phonemes using the following rules:
diff --git a/applications/external/spectrum_analyzer/spectrum_analyzer.c b/applications/external/spectrum_analyzer/spectrum_analyzer.c
index 7148ad92b..6250ac039 100644
--- a/applications/external/spectrum_analyzer/spectrum_analyzer.c
+++ b/applications/external/spectrum_analyzer/spectrum_analyzer.c
@@ -396,6 +396,8 @@ void spectrum_analyzer_free(SpectrumAnalyzer* instance) {
// Disable power for External CC1101 if it was enabled and module is connected
furi_hal_subghz_disable_ext_power();
+ // Reinit SPI handles for internal radio / nfc
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
}
int32_t spectrum_analyzer_app(void* p) {
@@ -410,7 +412,8 @@ int32_t spectrum_analyzer_app(void* p) {
// Auto switch to internal radio if external radio is not available
furi_delay_ms(15);
if(!furi_hal_subghz_check_radio()) {
- furi_hal_subghz_set_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_select_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
}
furi_hal_power_suppress_charge_enter();
diff --git a/applications/external/subghz_bruteforcer/subbrute.c b/applications/external/subghz_bruteforcer/subbrute.c
index 59d2c1ccb..99334c05e 100644
--- a/applications/external/subghz_bruteforcer/subbrute.c
+++ b/applications/external/subghz_bruteforcer/subbrute.c
@@ -185,7 +185,8 @@ int32_t subbrute_app(void* p) {
// Auto switch to internal radio if external radio is not available
furi_delay_ms(15);
if(!furi_hal_subghz_check_radio()) {
- furi_hal_subghz_set_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_select_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
}
furi_hal_power_suppress_charge_enter();
@@ -195,6 +196,8 @@ int32_t subbrute_app(void* p) {
furi_hal_power_suppress_charge_exit();
// Disable power for External CC1101 if it was enabled and module is connected
furi_hal_subghz_disable_ext_power();
+ // Reinit SPI handles for internal radio / nfc
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
subbrute_free(instance);
diff --git a/applications/external/subghz_bruteforcer/subbrute_device.c b/applications/external/subghz_bruteforcer/subbrute_device.c
index 0971c380e..192b8bfa0 100644
--- a/applications/external/subghz_bruteforcer/subbrute_device.c
+++ b/applications/external/subghz_bruteforcer/subbrute_device.c
@@ -104,7 +104,6 @@ bool subbrute_device_save_file(SubBruteDevice* instance, const char* dev_file_na
instance->current_step,
instance->file_protocol_info->bits,
instance->file_protocol_info->te,
- instance->file_protocol_info->repeat,
instance->bit_index,
instance->key_from_file,
instance->two_bytes);
@@ -116,8 +115,7 @@ bool subbrute_device_save_file(SubBruteDevice* instance, const char* dev_file_na
instance->protocol_info->file,
instance->current_step,
instance->protocol_info->bits,
- instance->protocol_info->te,
- instance->protocol_info->repeat);
+ instance->protocol_info->te);
}
result = true;
diff --git a/applications/external/subghz_bruteforcer/subbrute_protocols.c b/applications/external/subghz_bruteforcer/subbrute_protocols.c
index 6c6781b78..1f3e45129 100644
--- a/applications/external/subghz_bruteforcer/subbrute_protocols.c
+++ b/applications/external/subghz_bruteforcer/subbrute_protocols.c
@@ -522,9 +522,9 @@ static const char* subbrute_protocol_file_types[] = {
* Values to not use less memory for packet parse operations
*/
static const char* subbrute_key_file_start_no_tail =
- "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\nKey: %s\nRepeat: %d\n";
+ "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\nKey: %s\n";
static const char* subbrute_key_file_start_with_tail =
- "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\nKey: %s\nTE: %d\nRepeat: %d\n";
+ "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\nKey: %s\nTE: %d\n";
static const char* subbrute_key_small_no_tail = "Bit: %d\nKey: %s\nRepeat: %d\n";
//static const char* subbrute_key_small_raw =
// "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\n";
@@ -771,8 +771,7 @@ void subbrute_protocol_default_generate_file(
SubBruteFileProtocol file,
uint64_t step,
uint8_t bits,
- uint32_t te,
- uint8_t repeat) {
+ uint32_t te) {
FuriString* candidate = furi_string_alloc();
subbrute_protocol_create_candidate_for_default(candidate, file, step);
@@ -790,8 +789,7 @@ void subbrute_protocol_default_generate_file(
subbrute_protocol_file(file),
bits,
furi_string_get_cstr(candidate),
- te,
- repeat);
+ te);
} else {
stream_write_format(
stream,
@@ -800,8 +798,7 @@ void subbrute_protocol_default_generate_file(
subbrute_protocol_preset(preset),
subbrute_protocol_file(file),
bits,
- furi_string_get_cstr(candidate),
- repeat);
+ furi_string_get_cstr(candidate));
}
furi_string_free(candidate);
@@ -815,7 +812,6 @@ void subbrute_protocol_file_generate_file(
uint64_t step,
uint8_t bits,
uint32_t te,
- uint8_t repeat,
uint8_t bit_index,
uint64_t file_key,
bool two_bytes) {
@@ -826,6 +822,7 @@ void subbrute_protocol_file_generate_file(
candidate, step, bit_index, file_key, two_bytes);
stream_clean(stream);
+
if(te) {
stream_write_format(
stream,
@@ -835,8 +832,7 @@ void subbrute_protocol_file_generate_file(
subbrute_protocol_file(file),
bits,
furi_string_get_cstr(candidate),
- te,
- repeat);
+ te);
} else {
stream_write_format(
stream,
@@ -845,8 +841,7 @@ void subbrute_protocol_file_generate_file(
subbrute_protocol_preset(preset),
subbrute_protocol_file(file),
bits,
- furi_string_get_cstr(candidate),
- repeat);
+ furi_string_get_cstr(candidate));
}
furi_string_free(candidate);
diff --git a/applications/external/subghz_bruteforcer/subbrute_protocols.h b/applications/external/subghz_bruteforcer/subbrute_protocols.h
index 2f41b185b..066040b10 100644
--- a/applications/external/subghz_bruteforcer/subbrute_protocols.h
+++ b/applications/external/subghz_bruteforcer/subbrute_protocols.h
@@ -110,8 +110,7 @@ void subbrute_protocol_default_generate_file(
SubBruteFileProtocol file,
uint64_t step,
uint8_t bits,
- uint32_t te,
- uint8_t repeat);
+ uint32_t te);
void subbrute_protocol_file_generate_file(
Stream* stream,
uint32_t frequency,
@@ -120,7 +119,6 @@ void subbrute_protocol_file_generate_file(
uint64_t step,
uint8_t bits,
uint32_t te,
- uint8_t repeat,
uint8_t bit_index,
uint64_t file_key,
bool two_bytes);
diff --git a/applications/external/subghz_playlist/playlist.c b/applications/external/subghz_playlist/playlist.c
index 345b19927..906df0725 100644
--- a/applications/external/subghz_playlist/playlist.c
+++ b/applications/external/subghz_playlist/playlist.c
@@ -718,7 +718,8 @@ int32_t playlist_app(void* p) {
// Auto switch to internal radio if external radio is not available
furi_delay_ms(15);
if(!furi_hal_subghz_check_radio()) {
- furi_hal_subghz_set_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_select_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
}
furi_hal_power_suppress_charge_enter();
@@ -811,6 +812,8 @@ exit_cleanup:
furi_hal_power_suppress_charge_exit();
// Disable power for External CC1101 if it was enabled and module is connected
furi_hal_subghz_disable_ext_power();
+ // Reinit SPI handles for internal radio / nfc
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
if(app->worker != NULL) {
if(playlist_worker_running(app->worker)) {
diff --git a/applications/external/subghz_remote/subghz_remote_app.c b/applications/external/subghz_remote/subghz_remote_app.c
index 940968592..015d57dac 100644
--- a/applications/external/subghz_remote/subghz_remote_app.c
+++ b/applications/external/subghz_remote/subghz_remote_app.c
@@ -136,7 +136,7 @@ static void cfg_read_file_path(
*is_enabled = 0;
} else {
*text_file_label = extract_filename(furi_string_get_cstr(text_file_path), 16);
- FURI_LOG_D(TAG, "%s file: %s", read_key, furi_string_get_cstr(text_file_path));
+ //FURI_LOG_D(TAG, "%s file: %s", read_key, furi_string_get_cstr(text_file_path));
*is_enabled = 1;
}
flipper_format_rewind(fff_file);
@@ -155,7 +155,7 @@ static void cfg_read_file_label(
if(is_enabled == 1) {
*text_file_label = char_to_str((char*)furi_string_get_cstr(temp_label), 16);
}
- FURI_LOG_D(TAG, "%s label: %s", read_key, *text_file_label);
+ //FURI_LOG_D(TAG, "%s label: %s", read_key, *text_file_label);
}
flipper_format_rewind(fff_file);
furi_string_free(temp_label);
@@ -355,6 +355,10 @@ bool subghz_remote_key_load(
bool res = false;
+ subghz_custom_btn_set(0);
+ keeloq_reset_original_btn();
+ subghz_custom_btns_reset();
+
do {
// load frequency from file
if(!flipper_format_read_uint32(fff_file, "Frequency", &preset->frequency, 1)) {
@@ -386,6 +390,10 @@ bool subghz_remote_key_load(
FURI_LOG_E(TAG, "Could not read Protocol.");
break;
}
+ if(!flipper_format_rewind(fff_data)) {
+ FURI_LOG_E(TAG, "Rewind error");
+ return false;
+ }
if(!furi_string_cmp_str(preset->protocol, "RAW")) {
subghz_protocol_raw_gen_fff_data(fff_data, path);
@@ -394,10 +402,7 @@ bool subghz_remote_key_load(
FURI_LOG_E(TAG, "Unable to insert or update Repeat");
break;
}
- if(!flipper_format_rewind(fff_data)) {
- FURI_LOG_E(TAG, "Rewind error");
- return false;
- }
+
} else {
stream_copy_full(
flipper_format_get_raw_stream(fff_file), flipper_format_get_raw_stream(fff_data));
@@ -406,10 +411,6 @@ bool subghz_remote_key_load(
FURI_LOG_E(TAG, "Unable to insert or update Repeat");
break;
}
- if(!flipper_format_rewind(fff_data)) {
- FURI_LOG_E(TAG, "Rewind error");
- return false;
- }
}
if(!flipper_format_rewind(fff_file)) {
@@ -467,7 +468,7 @@ bool subghz_remote_save_protocol_to_file(FlipperFormat* fff_file, const char* de
stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS);
saved = true;
- FURI_LOG_D(TAG, "(save) OK Save");
+ //FURI_LOG_D(TAG, "(save) OK Save");
} while(0);
furi_string_free(file_dir);
furi_record_close(RECORD_STORAGE);
@@ -527,7 +528,8 @@ static bool subghz_remote_send_sub(SubGHzRemote* app, FlipperFormat* fff_data) {
bool res = false;
do {
if(!furi_hal_subghz_is_tx_allowed(app->txpreset->frequency)) {
- printf(
+ FURI_LOG_E(
+ TAG,
"In your settings, only reception on this frequency (%lu) is allowed,\r\n"
"the actual operation of the subghz remote app is not possible\r\n ",
app->txpreset->frequency);
@@ -544,7 +546,11 @@ static bool subghz_remote_send_sub(SubGHzRemote* app, FlipperFormat* fff_data) {
break;
}
- subghz_transmitter_deserialize(app->tx_transmitter, fff_data);
+ if(subghz_transmitter_deserialize(app->tx_transmitter, fff_data) !=
+ SubGhzProtocolStatusOk) {
+ FURI_LOG_E(TAG, "Deserialize error!");
+ break;
+ }
furi_hal_subghz_reset();
furi_hal_subghz_idle();
@@ -575,7 +581,7 @@ static bool subghz_remote_send_sub(SubGHzRemote* app, FlipperFormat* fff_data) {
}
static void subghz_remote_send_signal(SubGHzRemote* app, Storage* storage, const char* path) {
- FURI_LOG_D(TAG, "Sending: %s", path);
+ //FURI_LOG_D(TAG, "Sending: %s", path);
app->tx_file_path = path;
@@ -615,7 +621,7 @@ static void subghz_remote_send_signal(SubGHzRemote* app, Storage* storage, const
static void subghz_remote_process_signal(SubGHzRemote* app, FuriString* signal) {
view_port_update(app->view_port);
- FURI_LOG_D(TAG, "signal = %s", furi_string_get_cstr(signal));
+ //FURI_LOG_D(TAG, "signal = %s", furi_string_get_cstr(signal));
if(strlen(furi_string_get_cstr(signal)) > 12) {
Storage* storage = furi_record_open(RECORD_STORAGE);
@@ -745,6 +751,8 @@ void subghz_remote_subghz_alloc(SubGHzRemote* app) {
app->environment, EXT_PATH("subghz/assets/came_atomo"));
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
app->environment, EXT_PATH("subghz/assets/nice_flor_s"));
+ subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
+ app->environment, EXT_PATH("subghz/assets/alutech_at_4n"));
subghz_environment_set_protocol_registry(app->environment, (void*)&subghz_protocol_registry);
app->subghz_receiver = subghz_receiver_alloc_init(app->environment);
@@ -758,7 +766,8 @@ SubGHzRemote* subghz_remote_alloc(void) {
// Auto switch to internal radio if external radio is not available
furi_delay_ms(15);
if(!furi_hal_subghz_check_radio()) {
- furi_hal_subghz_set_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_select_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
}
furi_hal_power_suppress_charge_enter();
@@ -785,6 +794,8 @@ void subghz_remote_free(SubGHzRemote* app, bool with_subghz) {
// Disable power for External CC1101 if it was enabled and module is connected
furi_hal_subghz_disable_ext_power();
+ // Reinit SPI handles for internal radio / nfc
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
furi_string_free(app->up_file);
furi_string_free(app->down_file);
@@ -867,14 +878,14 @@ int32_t subghz_remote_app(void* p) {
bool exit_loop = false;
if(app->file_result == 2) {
- FURI_LOG_D(
- TAG,
- "U: %s - D: %s - L: %s - R: %s - O: %s ",
- furi_string_get_cstr(app->up_file),
- furi_string_get_cstr(app->down_file),
- furi_string_get_cstr(app->left_file),
- furi_string_get_cstr(app->right_file),
- furi_string_get_cstr(app->ok_file));
+ //FURI_LOG_D(
+ //TAG,
+ //"U: %s - D: %s - L: %s - R: %s - O: %s ",
+ //furi_string_get_cstr(app->up_file),
+ //furi_string_get_cstr(app->down_file),
+ //furi_string_get_cstr(app->left_file),
+ //furi_string_get_cstr(app->right_file),
+ //furi_string_get_cstr(app->ok_file));
//variables to control multiple button presses and status updates
app->send_status = "Idle";
@@ -892,11 +903,11 @@ int32_t subghz_remote_app(void* p) {
while(1) {
furi_check(
furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk);
- FURI_LOG_D(
- TAG,
- "key: %s type: %s",
- input_get_key_name(input.key),
- input_get_type_name(input.type));
+ //FURI_LOG_D(
+ //TAG,
+ //"key: %s type: %s",
+ //input_get_key_name(input.key),
+ //input_get_type_name(input.type));
switch(input.key) {
case InputKeyUp:
@@ -998,12 +1009,12 @@ int32_t subghz_remote_app(void* p) {
}
if(app->processing == 0) {
- FURI_LOG_D(TAG, "processing 0");
+ //FURI_LOG_D(TAG, "processing 0");
app->send_status = "Idle";
app->send_status_c = 0;
app->button = 0;
} else if(app->processing == 1) {
- FURI_LOG_D(TAG, "processing 1");
+ //FURI_LOG_D(TAG, "processing 1");
app->send_status = "Send";
@@ -1046,11 +1057,11 @@ int32_t subghz_remote_app(void* p) {
while(1) {
furi_check(
furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk);
- FURI_LOG_D(
- TAG,
- "key: %s type: %s",
- input_get_key_name(input.key),
- input_get_type_name(input.type));
+ //FURI_LOG_D(
+ //TAG,
+ //"key: %s type: %s",
+ //input_get_key_name(input.key),
+ //input_get_type_name(input.type));
switch(input.key) {
case InputKeyRight:
diff --git a/applications/external/swd_probe/swd_probe_app.c b/applications/external/swd_probe/swd_probe_app.c
index beaa871dc..ae835fe61 100644
--- a/applications/external/swd_probe/swd_probe_app.c
+++ b/applications/external/swd_probe/swd_probe_app.c
@@ -3164,6 +3164,11 @@ int32_t swd_probe_app_main(void* p) {
furi_message_queue_free(app->event_queue);
furi_mutex_free(app->gui_mutex);
furi_mutex_free(app->swd_mutex);
+
+ // Reset GPIO pins to default state
+ for(int io = 0; io < 8; io++) {
+ furi_hal_gpio_init(gpios[io], GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+ }
free(app);
furi_record_close(RECORD_GUI);
diff --git a/applications/external/text_viewer/text_viewer.c b/applications/external/text_viewer/text_viewer.c
index 59923adb9..b5ccb6ef3 100644
--- a/applications/external/text_viewer/text_viewer.c
+++ b/applications/external/text_viewer/text_viewer.c
@@ -157,7 +157,7 @@ static bool text_viewer_open_file(TextViewer* text_viewer, const char* file_path
FURI_LOG_E(TAG, "Unable to open stream: %s", file_path);
isOk = false;
break;
- };
+ }
text_viewer->model->file_size = stream_size(text_viewer->model->stream);
} while(false);
diff --git a/applications/external/timelapse/icons/ButtonDownHollow_7x4.png b/applications/external/timelapse/icons/ButtonDownHollow_7x4.png
new file mode 100644
index 000000000..2b87c4364
Binary files /dev/null and b/applications/external/timelapse/icons/ButtonDownHollow_7x4.png differ
diff --git a/applications/external/timelapse/icons/ButtonLeftHollow_4x7.png b/applications/external/timelapse/icons/ButtonLeftHollow_4x7.png
new file mode 100644
index 000000000..374dc7d1a
Binary files /dev/null and b/applications/external/timelapse/icons/ButtonLeftHollow_4x7.png differ
diff --git a/applications/external/timelapse/icons/ButtonRightHollow_4x7.png b/applications/external/timelapse/icons/ButtonRightHollow_4x7.png
new file mode 100644
index 000000000..acbb08592
Binary files /dev/null and b/applications/external/timelapse/icons/ButtonRightHollow_4x7.png differ
diff --git a/applications/external/timelapse/icons/ButtonUpHollow_7x4.png b/applications/external/timelapse/icons/ButtonUpHollow_7x4.png
new file mode 100644
index 000000000..e88ee3322
Binary files /dev/null and b/applications/external/timelapse/icons/ButtonUpHollow_7x4.png differ
diff --git a/applications/external/timelapse/zeitraffer.c b/applications/external/timelapse/zeitraffer.c
index cbae4fa44..73ea4424a 100644
--- a/applications/external/timelapse/zeitraffer.c
+++ b/applications/external/timelapse/zeitraffer.c
@@ -69,16 +69,18 @@ static void draw_callback(Canvas* canvas, void* ctx) {
canvas_draw_str(canvas, 13, 55, "AUTO");
}
- //canvas_draw_icon(canvas, 90, 17, &I_ButtonUp_7x4);
- //canvas_draw_icon(canvas, 100, 17, &I_ButtonDown_7x4);
- //canvas_draw_icon(canvas, 27, 17, &I_ButtonLeftSmall_3x5);
- //canvas_draw_icon(canvas, 37, 17, &I_ButtonRightSmall_3x5);
- //canvas_draw_icon(canvas, 3, 48, &I_Pin_star_7x7);
+ if(Work) {
+ canvas_draw_icon(canvas, 85, 41, &I_ButtonUpHollow_7x4);
+ canvas_draw_icon(canvas, 85, 57, &I_ButtonDownHollow_7x4);
+ canvas_draw_icon(canvas, 59, 48, &I_ButtonLeftHollow_4x7);
+ canvas_draw_icon(canvas, 72, 48, &I_ButtonRightHollow_4x7);
+ } else {
+ canvas_draw_icon(canvas, 85, 41, &I_ButtonUp_7x4);
+ canvas_draw_icon(canvas, 85, 57, &I_ButtonDown_7x4);
+ canvas_draw_icon(canvas, 59, 48, &I_ButtonLeft_4x7);
+ canvas_draw_icon(canvas, 72, 48, &I_ButtonRight_4x7);
+ }
- canvas_draw_icon(canvas, 85, 41, &I_ButtonUp_7x4);
- canvas_draw_icon(canvas, 85, 57, &I_ButtonDown_7x4);
- canvas_draw_icon(canvas, 59, 48, &I_ButtonLeft_4x7);
- canvas_draw_icon(canvas, 72, 48, &I_ButtonRight_4x7);
canvas_draw_icon(canvas, 3, 48, &I_Pin_star_7x7);
canvas_set_font(canvas, FontPrimary);
@@ -87,8 +89,8 @@ static void draw_callback(Canvas* canvas, void* ctx) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 85, 55, "S");
- canvas_draw_icon(canvas, 59, 48, &I_ButtonLeft_4x7);
- canvas_draw_icon(canvas, 72, 48, &I_ButtonRight_4x7);
+ //canvas_draw_icon(canvas, 59, 48, &I_ButtonLeft_4x7);
+ //canvas_draw_icon(canvas, 72, 48, &I_ButtonRight_4x7);
if(Work) {
canvas_draw_icon(canvas, 106, 46, &I_loading_10px);
@@ -151,6 +153,10 @@ int32_t zeitraffer_app(void* p) {
FlipperFormat* load = flipper_format_file_alloc(storage);
do {
+ if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
+ notification_message(notifications, &sequence_error);
+ break;
+ }
if(!flipper_format_file_open_existing(load, CONFIG_FILE_PATH)) {
notification_message(notifications, &sequence_error);
break;
@@ -247,6 +253,8 @@ int32_t zeitraffer_app(void* p) {
if(WorkTime == 0) WorkTime = Delay;
+ if(Count == 1) WorkTime = Time;
+
if(Count == 0) {
InfiniteShot = true;
WorkCount = 1;
@@ -390,7 +398,7 @@ int32_t zeitraffer_app(void* p) {
}
if(!flipper_format_write_comment_cstr(
save,
- "Zeitraffer app settings: n of frames, interval time, backlight type, Delay")) {
+ "Zeitraffer app settings: β of frames, interval time, backlight type, Delay")) {
notification_message(notifications, &sequence_error);
break;
}
diff --git a/applications/external/totp/application.fam b/applications/external/totp/application.fam
index 8e56bbe76..94e85aae2 100644
--- a/applications/external/totp/application.fam
+++ b/applications/external/totp/application.fam
@@ -20,7 +20,6 @@ App(
Lib(
name="base64",
),
- Lib(name="linked_list"),
Lib(
name="timezone_utils",
),
diff --git a/applications/external/totp/cli/cli.c b/applications/external/totp/cli/cli.c
index 30876c7e4..c860b5a36 100644
--- a/applications/external/totp/cli/cli.c
+++ b/applications/external/totp/cli/cli.c
@@ -63,7 +63,7 @@ static void totp_cli_handler(Cli* cli, FuriString* args, void* context) {
} else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_AUTOMATION) == 0) {
totp_cli_command_automation_handle(plugin_state, args, cli);
} else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_RESET) == 0) {
- totp_cli_command_reset_handle(cli, cli_context->event_queue);
+ totp_cli_command_reset_handle(plugin_state, cli, cli_context->event_queue);
} else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_UPDATE) == 0) {
totp_cli_command_update_handle(plugin_state, args, cli);
} else if(
diff --git a/applications/external/totp/cli/cli_helpers.c b/applications/external/totp/cli/cli_helpers.c
index 36b98cf65..226917237 100644
--- a/applications/external/totp/cli/cli_helpers.c
+++ b/applications/external/totp/cli/cli_helpers.c
@@ -3,6 +3,11 @@
#include
#include "../types/plugin_event.h"
+const char* TOTP_CLI_COLOR_ERROR = "91m";
+const char* TOTP_CLI_COLOR_WARNING = "93m";
+const char* TOTP_CLI_COLOR_SUCCESS = "92m";
+const char* TOTP_CLI_COLOR_INFO = "96m";
+
bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) {
if(plugin_state->current_scene == TotpSceneAuthentication) {
TOTP_CLI_PRINTF("Pleases enter PIN on your flipper device\r\n");
@@ -13,10 +18,11 @@ bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) {
furi_delay_ms(100);
}
- TOTP_CLI_DELETE_LAST_LINE();
+ totp_cli_delete_last_line();
if(plugin_state->current_scene == TotpSceneAuthentication || //-V560
plugin_state->current_scene == TotpSceneNone) { //-V560
+ TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
return false;
}
}
@@ -54,7 +60,7 @@ bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input) {
} else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) {
size_t out_str_size = furi_string_size(out_str);
if(out_str_size > 0) {
- TOTP_CLI_DELETE_LAST_CHAR();
+ totp_cli_delete_last_char();
furi_string_left(out_str, out_str_size - 1);
}
} else if(c == CliSymbolAsciiCR) {
@@ -83,3 +89,35 @@ void furi_string_secure_free(FuriString* str) {
furi_string_free(str);
}
+
+void totp_cli_print_invalid_arguments() {
+ TOTP_CLI_PRINTF_ERROR(
+ "Invalid command arguments. use \"help\" command to get list of available commands");
+}
+
+void totp_cli_print_error_updating_config_file() {
+ TOTP_CLI_PRINTF_ERROR("An error has occurred during updating config file\r\n");
+}
+
+void totp_cli_print_error_loading_token_info() {
+ TOTP_CLI_PRINTF_ERROR("An error has occurred during loading token information\r\n");
+}
+
+void totp_cli_print_processing() {
+ TOTP_CLI_PRINTF("Processing, please wait...\r\n");
+}
+
+void totp_cli_delete_last_char() {
+ TOTP_CLI_PRINTF("\b \b");
+ fflush(stdout);
+}
+
+void totp_cli_delete_current_line() {
+ TOTP_CLI_PRINTF("\33[2K\r");
+ fflush(stdout);
+}
+
+void totp_cli_delete_last_line() {
+ TOTP_CLI_PRINTF("\033[A\33[2K\r");
+ fflush(stdout);
+}
diff --git a/applications/external/totp/cli/cli_helpers.h b/applications/external/totp/cli/cli_helpers.h
index dd5a282d4..0f8b24364 100644
--- a/applications/external/totp/cli/cli_helpers.h
+++ b/applications/external/totp/cli/cli_helpers.h
@@ -14,6 +14,11 @@
#define DOCOPT_OPTIONS "[options]"
#define DOCOPT_DEFAULT(val) "[default: " val "]"
+extern const char* TOTP_CLI_COLOR_ERROR;
+extern const char* TOTP_CLI_COLOR_WARNING;
+extern const char* TOTP_CLI_COLOR_SUCCESS;
+extern const char* TOTP_CLI_COLOR_INFO;
+
#define TOTP_CLI_PRINTF(format, ...) printf(format, ##__VA_ARGS__)
#define TOTP_CLI_PRINTF_COLORFUL(color, format, ...) \
@@ -22,11 +27,6 @@
printf("\e[0m"); \
fflush(stdout)
-#define TOTP_CLI_COLOR_ERROR "91m"
-#define TOTP_CLI_COLOR_WARNING "93m"
-#define TOTP_CLI_COLOR_SUCCESS "92m"
-#define TOTP_CLI_COLOR_INFO "96m"
-
#define TOTP_CLI_PRINTF_ERROR(format, ...) \
TOTP_CLI_PRINTF_COLORFUL(TOTP_CLI_COLOR_ERROR, format, ##__VA_ARGS__)
#define TOTP_CLI_PRINTF_WARNING(format, ...) \
@@ -36,24 +36,12 @@
#define TOTP_CLI_PRINTF_INFO(format, ...) \
TOTP_CLI_PRINTF_COLORFUL(TOTP_CLI_COLOR_INFO, format, ##__VA_ARGS__)
-#define TOTP_CLI_DELETE_LAST_LINE() \
- TOTP_CLI_PRINTF("\033[A\33[2K\r"); \
- fflush(stdout)
+#define TOTP_CLI_LOCK_UI(plugin_state) \
+ Scene __previous_scene = plugin_state->current_scene; \
+ totp_scene_director_activate_scene(plugin_state, TotpSceneStandby)
-#define TOTP_CLI_DELETE_CURRENT_LINE() \
- TOTP_CLI_PRINTF("\33[2K\r"); \
- fflush(stdout)
-
-#define TOTP_CLI_DELETE_LAST_CHAR() \
- TOTP_CLI_PRINTF("\b \b"); \
- fflush(stdout)
-
-#define TOTP_CLI_PRINT_INVALID_ARGUMENTS() \
- TOTP_CLI_PRINTF_ERROR( \
- "Invalid command arguments. use \"help\" command to get list of available commands")
-
-#define TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE() \
- TOTP_CLI_PRINTF_ERROR("An error has occurred during updating config file\r\n")
+#define TOTP_CLI_UNLOCK_UI(plugin_state) \
+ totp_scene_director_activate_scene(plugin_state, __previous_scene)
/**
* @brief Checks whether user is authenticated and entered correct PIN.
@@ -92,3 +80,38 @@ bool args_read_uint8_and_trim(FuriString* args, uint8_t* value);
* @param str instance to free
*/
void furi_string_secure_free(FuriString* str);
+
+/**
+ * @brief Deletes last printed line in console
+ */
+void totp_cli_delete_last_line();
+
+/**
+ * @brief Deletes current printed line in console
+ */
+void totp_cli_delete_current_line();
+
+/**
+ * @brief Deletes last printed char in console
+ */
+void totp_cli_delete_last_char();
+
+/**
+ * @brief Prints error message about invalid command arguments
+ */
+void totp_cli_print_invalid_arguments();
+
+/**
+ * @brief Prints error message about config file update error
+ */
+void totp_cli_print_error_updating_config_file();
+
+/**
+ * @brief Prints error message about config file loading error
+ */
+void totp_cli_print_error_loading_token_info();
+
+/**
+ * @brief Prints message to let user knwo that command is processing now
+ */
+void totp_cli_print_processing();
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/add/add.c b/applications/external/totp/cli/commands/add/add.c
index 3549e785b..b2cd01352 100644
--- a/applications/external/totp/cli/commands/add/add.c
+++ b/applications/external/totp/cli/commands/add/add.c
@@ -1,7 +1,6 @@
#include "add.h"
#include
#include
-#include
#include "../../../types/token_info.h"
#include "../../../services/config/config.h"
#include "../../../services/convert/convert.h"
@@ -9,6 +8,77 @@
#include "../../../ui/scene_director.h"
#include "../../common_command_arguments.h"
+struct TotpAddContext {
+ FuriString* args;
+ Cli* cli;
+ uint8_t* iv;
+};
+
+enum TotpIteratorUpdateTokenResultsEx {
+ TotpIteratorUpdateTokenResultInvalidSecret = 1,
+ TotpIteratorUpdateTokenResultCancelled = 2,
+ TotpIteratorUpdateTokenResultInvalidArguments = 3
+};
+
+static TotpIteratorUpdateTokenResult
+ add_token_handler(TokenInfo* token_info, const void* context) {
+ const struct TotpAddContext* context_t = context;
+
+ // Reading token name
+ if(!args_read_probably_quoted_string_and_trim(context_t->args, token_info->name)) {
+ return TotpIteratorUpdateTokenResultInvalidArguments;
+ }
+
+ FuriString* temp_str = furi_string_alloc();
+
+ // Read optional arguments
+ bool mask_user_input = true;
+ PlainTokenSecretEncoding token_secret_encoding = PlainTokenSecretEncodingBase32;
+ while(args_read_string_and_trim(context_t->args, temp_str)) {
+ bool parsed = false;
+ if(!totp_cli_try_read_algo(token_info, temp_str, context_t->args, &parsed) &&
+ !totp_cli_try_read_digits(token_info, temp_str, context_t->args, &parsed) &&
+ !totp_cli_try_read_duration(token_info, temp_str, context_t->args, &parsed) &&
+ !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) &&
+ !totp_cli_try_read_automation_features(token_info, temp_str, context_t->args, &parsed) &&
+ !totp_cli_try_read_plain_token_secret_encoding(
+ temp_str, context_t->args, &parsed, &token_secret_encoding)) {
+ totp_cli_printf_unknown_argument(temp_str);
+ }
+
+ if(!parsed) {
+ furi_string_free(temp_str);
+ return TotpIteratorUpdateTokenResultInvalidArguments;
+ }
+ }
+
+ // Reading token secret
+ furi_string_reset(temp_str);
+ TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n");
+ if(!totp_cli_read_line(context_t->cli, temp_str, mask_user_input)) {
+ totp_cli_delete_last_line();
+ furi_string_secure_free(temp_str);
+ return TotpIteratorUpdateTokenResultCancelled;
+ }
+
+ totp_cli_delete_last_line();
+
+ bool secret_set = token_info_set_secret(
+ token_info,
+ furi_string_get_cstr(temp_str),
+ furi_string_size(temp_str),
+ token_secret_encoding,
+ context_t->iv);
+
+ furi_string_secure_free(temp_str);
+
+ if(!secret_set) {
+ return TotpIteratorUpdateTokenResultInvalidSecret;
+ }
+
+ return TotpIteratorUpdateTokenResultSuccess;
+}
+
void totp_cli_command_add_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT
", " TOTP_CLI_COMMAND_ADD_ALT2 " Add new token\r\n");
@@ -75,90 +145,33 @@ void totp_cli_command_add_docopt_options() {
}
void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
- FuriString* temp_str = furi_string_alloc();
- TokenInfo* token_info = token_info_alloc();
-
- // Reading token name
- if(!args_read_probably_quoted_string_and_trim(args, temp_str)) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
- furi_string_free(temp_str);
- token_info_free(token_info);
+ if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
return;
}
- size_t temp_cstr_len = furi_string_size(temp_str);
- token_info->name = malloc(temp_cstr_len + 1);
- furi_check(token_info->name != NULL);
- strlcpy(token_info->name, furi_string_get_cstr(temp_str), temp_cstr_len + 1);
+ TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
- // Read optional arguments
- bool mask_user_input = true;
- PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32;
- while(args_read_string_and_trim(args, temp_str)) {
- bool parsed = false;
- if(!totp_cli_try_read_algo(token_info, temp_str, args, &parsed) &&
- !totp_cli_try_read_digits(token_info, temp_str, args, &parsed) &&
- !totp_cli_try_read_duration(token_info, temp_str, args, &parsed) &&
- !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) &&
- !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed) &&
- !totp_cli_try_read_plain_token_secret_encoding(
- temp_str, args, &parsed, &token_secret_encoding)) {
- totp_cli_printf_unknown_argument(temp_str);
- }
+ TOTP_CLI_LOCK_UI(plugin_state);
- if(!parsed) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
- furi_string_free(temp_str);
- token_info_free(token_info);
- return;
- }
- }
+ struct TotpAddContext add_context = {.args = args, .cli = cli, .iv = &plugin_state->iv[0]};
+ TotpIteratorUpdateTokenResult add_result =
+ totp_token_info_iterator_add_new_token(iterator_context, &add_token_handler, &add_context);
- // Reading token secret
- furi_string_reset(temp_str);
- TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n");
- if(!totp_cli_read_line(cli, temp_str, mask_user_input) ||
- !totp_cli_ensure_authenticated(plugin_state, cli)) {
- TOTP_CLI_DELETE_LAST_LINE();
+ if(add_result == TotpIteratorUpdateTokenResultSuccess) {
+ TOTP_CLI_PRINTF_SUCCESS(
+ "Token \"%s\" has been successfully added\r\n",
+ furi_string_get_cstr(
+ totp_token_info_iterator_get_current_token(iterator_context)->name));
+ } else if(add_result == TotpIteratorUpdateTokenResultCancelled) {
TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
- furi_string_secure_free(temp_str);
- token_info_free(token_info);
- return;
- }
-
- TOTP_CLI_DELETE_LAST_LINE();
-
- bool load_generate_token_scene = false;
- if(plugin_state->current_scene == TotpSceneGenerateToken) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
- load_generate_token_scene = true;
- }
-
- bool secret_set = token_info_set_secret(
- token_info,
- furi_string_get_cstr(temp_str),
- furi_string_size(temp_str),
- token_secret_encoding,
- plugin_state->iv);
-
- furi_string_secure_free(temp_str);
-
- if(secret_set) {
- TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, token_info, furi_check);
- plugin_state->tokens_count++;
-
- if(totp_config_file_save_new_token(token_info) == TotpConfigFileUpdateSuccess) {
- TOTP_CLI_PRINTF_SUCCESS(
- "Token \"%s\" has been successfully added\r\n", token_info->name);
- } else {
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
- }
- } else {
- token_info_free(token_info);
+ } else if(add_result == TotpIteratorUpdateTokenResultInvalidArguments) {
+ totp_cli_print_invalid_arguments();
+ } else if(add_result == TotpIteratorUpdateTokenResultInvalidSecret) {
TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n");
+ } else if(add_result == TotpIteratorUpdateTokenResultFileUpdateFailed) {
+ totp_cli_print_error_updating_config_file();
}
- if(load_generate_token_scene) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
- }
+ TOTP_CLI_UNLOCK_UI(plugin_state);
}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/automation/automation.c b/applications/external/totp/cli/commands/automation/automation.c
index 1af2e5dd7..c9f6ac34b 100644
--- a/applications/external/totp/cli/commands/automation/automation.c
+++ b/applications/external/totp/cli/commands/automation/automation.c
@@ -31,7 +31,7 @@ void totp_cli_command_automation_docopt_arguments() {
"\r\n");
}
-static void totp_cli_command_automation_print_method(AutomationMethod method, char* color) {
+static void totp_cli_command_automation_print_method(AutomationMethod method, const char* color) {
#ifdef TOTP_BADBT_TYPE_ENABLED
bool has_previous_method = false;
#endif
@@ -88,26 +88,20 @@ void totp_cli_command_automation_handle(PluginState* plugin_state, FuriString* a
do {
if(!args_valid) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ totp_cli_print_invalid_arguments();
break;
}
if(new_method_provided) {
- Scene previous_scene = TotpSceneNone;
- if(plugin_state->current_scene == TotpSceneGenerateToken ||
- plugin_state->current_scene == TotpSceneAppSettings) {
- previous_scene = plugin_state->current_scene;
- totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
- }
+ TOTP_CLI_LOCK_UI(plugin_state);
plugin_state->automation_method = new_method;
- if(totp_config_file_update_automation_method(new_method) ==
- TotpConfigFileUpdateSuccess) {
+ if(totp_config_file_update_automation_method(plugin_state)) {
TOTP_CLI_PRINTF_SUCCESS("Automation method is set to ");
totp_cli_command_automation_print_method(new_method, TOTP_CLI_COLOR_SUCCESS);
cli_nl();
} else {
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+ totp_cli_print_error_updating_config_file();
}
#ifdef TOTP_BADBT_TYPE_ENABLED
@@ -118,9 +112,7 @@ void totp_cli_command_automation_handle(PluginState* plugin_state, FuriString* a
}
#endif
- if(previous_scene != TotpSceneNone) {
- totp_scene_director_activate_scene(plugin_state, previous_scene, NULL);
- }
+ TOTP_CLI_UNLOCK_UI(plugin_state);
} else {
TOTP_CLI_PRINTF_INFO("Current automation method is ");
totp_cli_command_automation_print_method(
diff --git a/applications/external/totp/cli/commands/delete/delete.c b/applications/external/totp/cli/commands/delete/delete.c
index a45525e4b..9a084b23f 100644
--- a/applications/external/totp/cli/commands/delete/delete.c
+++ b/applications/external/totp/cli/commands/delete/delete.c
@@ -3,7 +3,6 @@
#include
#include
#include
-#include
#include "../../../services/config/config.h"
#include "../../cli_helpers.h"
#include "../../../ui/scene_director.h"
@@ -37,10 +36,13 @@ void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args,
return;
}
+ TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+
int token_number;
if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
- token_number > plugin_state->tokens_count) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ (size_t)token_number > totp_token_info_iterator_get_total_count(iterator_context)) {
+ totp_cli_print_invalid_arguments();
return;
}
@@ -51,23 +53,27 @@ void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args,
confirm_needed = false;
} else {
totp_cli_printf_unknown_argument(temp_str);
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ totp_cli_print_invalid_arguments();
furi_string_free(temp_str);
return;
}
}
furi_string_free(temp_str);
- ListNode* list_node = list_element_at(plugin_state->tokens_list, token_number - 1);
+ TOTP_CLI_LOCK_UI(plugin_state);
- TokenInfo* token_info = list_node->data;
+ size_t original_token_index =
+ totp_token_info_iterator_get_current_token_index(iterator_context);
+ totp_token_info_iterator_go_to(iterator_context, token_number - 1);
+ const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context);
+ const char* token_info_name = furi_string_get_cstr(token_info->name);
bool confirmed = !confirm_needed;
if(confirm_needed) {
TOTP_CLI_PRINTF_WARNING("WARNING!\r\n");
TOTP_CLI_PRINTF_WARNING(
"TOKEN \"%s\" WILL BE PERMANENTLY DELETED WITHOUT ABILITY TO RECOVER IT.\r\n",
- token_info->name);
+ token_info_name);
TOTP_CLI_PRINTF_WARNING("Confirm? [y/n]\r\n");
fflush(stdout);
char user_pick;
@@ -80,32 +86,21 @@ void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args,
}
if(confirmed) {
- if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
- return;
- }
-
- bool activate_generate_token_scene = false;
- if(plugin_state->current_scene != TotpSceneAuthentication) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
- activate_generate_token_scene = true;
- }
-
- plugin_state->tokens_list = list_remove(plugin_state->tokens_list, list_node);
- plugin_state->tokens_count--;
-
- if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
+ totp_cli_print_processing();
+ if(totp_token_info_iterator_remove_current_token_info(iterator_context)) {
+ totp_cli_delete_last_line();
TOTP_CLI_PRINTF_SUCCESS(
- "Token \"%s\" has been successfully deleted\r\n", token_info->name);
+ "Token \"%s\" has been successfully deleted\r\n", token_info_name);
+ totp_token_info_iterator_go_to(iterator_context, 0);
} else {
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
- }
-
- token_info_free(token_info);
-
- if(activate_generate_token_scene) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+ totp_cli_delete_last_line();
+ totp_cli_print_error_updating_config_file();
+ totp_token_info_iterator_go_to(iterator_context, original_token_index);
}
} else {
TOTP_CLI_PRINTF_INFO("User has not confirmed\r\n");
+ totp_token_info_iterator_go_to(iterator_context, original_token_index);
}
+
+ TOTP_CLI_UNLOCK_UI(plugin_state);
}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/details/details.c b/applications/external/totp/cli/commands/details/details.c
index 1b9289454..1677121b1 100644
--- a/applications/external/totp/cli/commands/details/details.c
+++ b/applications/external/totp/cli/commands/details/details.c
@@ -1,9 +1,10 @@
#include "details.h"
#include
#include
-#include
#include "../../../types/token_info.h"
#include "../../../services/config/constants.h"
+#include "../../../services/config/config.h"
+#include "../../../ui/scene_director.h"
#include "../../cli_helpers.h"
#include "../../common_command_arguments.h"
@@ -17,21 +18,21 @@
} while(false)
static void print_automation_features(const TokenInfo* token_info) {
- if(token_info->automation_features == TOKEN_AUTOMATION_FEATURE_NONE) {
+ if(token_info->automation_features == TokenAutomationFeatureNone) {
TOTP_CLI_PRINTF("| %-20s | %-28.28s |\r\n", "Automation features", "None");
return;
}
bool header_printed = false;
- if(token_info->automation_features & TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END) {
+ if(token_info->automation_features & TokenAutomationFeatureEnterAtTheEnd) {
TOTP_CLI_PRINTF_AUTOMATION_FEATURE("Type key at the end", header_printed);
}
- if(token_info->automation_features & TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END) {
+ if(token_info->automation_features & TokenAutomationFeatureTabAtTheEnd) {
TOTP_CLI_PRINTF_AUTOMATION_FEATURE("Type key at the end", header_printed);
}
- if(token_info->automation_features & TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER) {
+ if(token_info->automation_features & TokenAutomationFeatureTypeSlower) {
TOTP_CLI_PRINTF_AUTOMATION_FEATURE("Type slower", header_printed);
}
}
@@ -53,26 +54,39 @@ void totp_cli_command_details_handle(PluginState* plugin_state, FuriString* args
}
int token_number;
+ TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
- token_number > plugin_state->tokens_count) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ (size_t)token_number > totp_token_info_iterator_get_total_count(iterator_context)) {
+ totp_cli_print_invalid_arguments();
return;
}
- ListNode* list_node = list_element_at(plugin_state->tokens_list, token_number - 1);
+ TOTP_CLI_LOCK_UI(plugin_state);
- TokenInfo* token_info = list_node->data;
+ size_t original_token_index =
+ totp_token_info_iterator_get_current_token_index(iterator_context);
+ if(totp_token_info_iterator_go_to(iterator_context, token_number - 1)) {
+ const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context);
- TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
- TOTP_CLI_PRINTF("| %-20s | %-28s |\r\n", "Property", "Value");
- TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
- TOTP_CLI_PRINTF("| %-20s | %-28d |\r\n", "Index", token_number);
- TOTP_CLI_PRINTF("| %-20s | %-28.28s |\r\n", "Name", token_info->name);
- TOTP_CLI_PRINTF(
- "| %-20s | %-28s |\r\n", "Hashing algorithm", token_info_get_algo_as_cstr(token_info));
- TOTP_CLI_PRINTF("| %-20s | %-28" PRIu8 " |\r\n", "Number of digits", token_info->digits);
- TOTP_CLI_PRINTF(
- "| %-20s | %" PRIu8 " sec.%-21s |\r\n", "Token lifetime", token_info->duration, " ");
- print_automation_features(token_info);
- TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+ TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+ TOTP_CLI_PRINTF("| %-20s | %-28s |\r\n", "Property", "Value");
+ TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+ TOTP_CLI_PRINTF("| %-20s | %-28d |\r\n", "Index", token_number);
+ TOTP_CLI_PRINTF(
+ "| %-20s | %-28.28s |\r\n", "Name", furi_string_get_cstr(token_info->name));
+ TOTP_CLI_PRINTF(
+ "| %-20s | %-28s |\r\n", "Hashing algorithm", token_info_get_algo_as_cstr(token_info));
+ TOTP_CLI_PRINTF("| %-20s | %-28" PRIu8 " |\r\n", "Number of digits", token_info->digits);
+ TOTP_CLI_PRINTF(
+ "| %-20s | %" PRIu8 " sec.%-21s |\r\n", "Token lifetime", token_info->duration, " ");
+ print_automation_features(token_info);
+ TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+ } else {
+ totp_cli_print_error_loading_token_info();
+ }
+
+ totp_token_info_iterator_go_to(iterator_context, original_token_index);
+
+ TOTP_CLI_UNLOCK_UI(plugin_state);
}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/list/list.c b/applications/external/totp/cli/commands/list/list.c
index 951c102a0..bf5a8da52 100644
--- a/applications/external/totp/cli/commands/list/list.c
+++ b/applications/external/totp/cli/commands/list/list.c
@@ -1,8 +1,9 @@
#include "list.h"
#include
-#include
#include "../../../types/token_info.h"
#include "../../../services/config/constants.h"
+#include "../../../services/config/config.h"
+#include "../../../ui/scene_director.h"
#include "../../cli_helpers.h"
void totp_cli_command_list_docopt_commands() {
@@ -20,25 +21,36 @@ void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) {
return;
}
- if(plugin_state->tokens_list == NULL) {
+ TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ size_t total_count = totp_token_info_iterator_get_total_count(iterator_context);
+ if(total_count <= 0) {
TOTP_CLI_PRINTF("There are no tokens");
return;
}
+ TOTP_CLI_LOCK_UI(plugin_state);
+
+ size_t original_index = totp_token_info_iterator_get_current_token_index(iterator_context);
+
TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
TOTP_CLI_PRINTF("| %-3s | %-25s | %-6s | %-s | %-s |\r\n", "#", "Name", "Algo", "Ln", "Dur");
TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
- uint16_t index = 1;
- TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
- TokenInfo* token_info = (TokenInfo*)node->data;
+ for(size_t i = 0; i < total_count; i++) {
+ totp_token_info_iterator_go_to(iterator_context, i);
+ const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context);
TOTP_CLI_PRINTF(
"| %-3" PRIu16 " | %-25.25s | %-6s | %-2" PRIu8 " | %-3" PRIu8 " |\r\n",
- index,
- token_info->name,
+ i + 1,
+ furi_string_get_cstr(token_info->name),
token_info_get_algo_as_cstr(token_info),
token_info->digits,
token_info->duration);
- index++;
- });
+ }
+
TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
+
+ totp_token_info_iterator_go_to(iterator_context, original_index);
+
+ TOTP_CLI_UNLOCK_UI(plugin_state);
}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/move/move.c b/applications/external/totp/cli/commands/move/move.c
index 5c4bdfcd6..f26cda46e 100644
--- a/applications/external/totp/cli/commands/move/move.c
+++ b/applications/external/totp/cli/commands/move/move.c
@@ -2,7 +2,6 @@
#include
#include
-#include
#include "../../../types/token_info.h"
#include "../../../services/config/config.h"
#include "../../cli_helpers.h"
@@ -33,42 +32,52 @@ void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, C
return;
}
- int token_index;
- if(!args_read_int_and_trim(args, &token_index) || token_index < 1 ||
- token_index > plugin_state->tokens_count) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ int token_number;
+ TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ size_t total_count = totp_token_info_iterator_get_total_count(iterator_context);
+ if(!args_read_int_and_trim(args, &token_number) || token_number < 1 ||
+ (size_t)token_number > total_count) {
+ totp_cli_print_invalid_arguments();
return;
}
- int new_token_index = 0;
+ int new_token_number = 0;
- if(!args_read_int_and_trim(args, &new_token_index) || new_token_index < 1 ||
- new_token_index > plugin_state->tokens_count) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ if(!args_read_int_and_trim(args, &new_token_number) || new_token_number < 1 ||
+ (size_t)new_token_number > total_count) {
+ totp_cli_print_invalid_arguments();
return;
}
- bool activate_generate_token_scene = false;
- if(plugin_state->current_scene != TotpSceneAuthentication) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
- activate_generate_token_scene = true;
+ if(token_number == new_token_number) {
+ TOTP_CLI_PRINTF_ERROR("New token number matches current token number\r\n");
+ return;
}
- TokenInfo* token_info = NULL;
- plugin_state->tokens_list =
- list_remove_at(plugin_state->tokens_list, token_index - 1, (void**)&token_info);
- furi_check(token_info != NULL);
- plugin_state->tokens_list =
- list_insert_at(plugin_state->tokens_list, new_token_index - 1, token_info);
+ TOTP_CLI_LOCK_UI(plugin_state);
- if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
+ size_t token_index = token_number - 1;
+ size_t new_token_index = new_token_number - 1;
+
+ size_t original_token_index =
+ totp_token_info_iterator_get_current_token_index(iterator_context);
+
+ totp_cli_print_processing();
+
+ if(totp_token_info_iterator_go_to(iterator_context, token_index) &&
+ totp_token_info_iterator_move_current_token_info(iterator_context, new_token_index)) {
+ totp_cli_delete_last_line();
TOTP_CLI_PRINTF_SUCCESS(
- "Token \"%s\" has been successfully updated\r\n", token_info->name);
+ "Token \"%s\" has been successfully updated\r\n",
+ furi_string_get_cstr(
+ totp_token_info_iterator_get_current_token(iterator_context)->name));
} else {
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+ totp_cli_delete_last_line();
+ totp_cli_print_error_updating_config_file();
}
- if(activate_generate_token_scene) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
- }
+ totp_token_info_iterator_go_to(iterator_context, original_token_index);
+
+ TOTP_CLI_UNLOCK_UI(plugin_state);
}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/notification/notification.c b/applications/external/totp/cli/commands/notification/notification.c
index b81b7371a..f91b1b982 100644
--- a/applications/external/totp/cli/commands/notification/notification.c
+++ b/applications/external/totp/cli/commands/notification/notification.c
@@ -28,7 +28,8 @@ void totp_cli_command_notification_docopt_arguments() {
", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "\r\n");
}
-static void totp_cli_command_notification_print_method(NotificationMethod method, char* color) {
+static void
+ totp_cli_command_notification_print_method(NotificationMethod method, const char* color) {
bool has_previous_method = false;
if(method & NotificationMethodSound) {
TOTP_CLI_PRINTF_COLORFUL(color, "\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND "\"");
@@ -73,31 +74,23 @@ void totp_cli_command_notification_handle(PluginState* plugin_state, FuriString*
do {
if(!args_valid) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ totp_cli_print_invalid_arguments();
break;
}
if(new_method_provided) {
- Scene previous_scene = TotpSceneNone;
- if(plugin_state->current_scene == TotpSceneGenerateToken ||
- plugin_state->current_scene == TotpSceneAppSettings) {
- previous_scene = plugin_state->current_scene;
- totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
- }
+ TOTP_CLI_LOCK_UI(plugin_state);
plugin_state->notification_method = new_method;
- if(totp_config_file_update_notification_method(new_method) ==
- TotpConfigFileUpdateSuccess) {
+ if(totp_config_file_update_notification_method(plugin_state)) {
TOTP_CLI_PRINTF_SUCCESS("Notification method is set to ");
totp_cli_command_notification_print_method(new_method, TOTP_CLI_COLOR_SUCCESS);
cli_nl();
} else {
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+ totp_cli_print_error_updating_config_file();
}
- if(previous_scene != TotpSceneNone) {
- totp_scene_director_activate_scene(plugin_state, previous_scene, NULL);
- }
+ TOTP_CLI_UNLOCK_UI(plugin_state);
} else {
TOTP_CLI_PRINTF_INFO("Current notification method is ");
totp_cli_command_notification_print_method(
diff --git a/applications/external/totp/cli/commands/pin/pin.c b/applications/external/totp/cli/commands/pin/pin.c
index 9b9038ae7..62531b96a 100644
--- a/applications/external/totp/cli/commands/pin/pin.c
+++ b/applications/external/totp/cli/commands/pin/pin.c
@@ -2,7 +2,6 @@
#include
#include
-#include
#include "../../../types/token_info.h"
#include "../../../types/user_pin_codes.h"
#include "../../../services/config/config.h"
@@ -65,14 +64,14 @@ static bool totp_cli_read_pin(Cli* cli, uint8_t* pin, uint8_t* pin_length) {
}
}
} else if(c == CliSymbolAsciiETX) {
- TOTP_CLI_DELETE_CURRENT_LINE();
+ totp_cli_delete_current_line();
TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
return false;
} else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) {
if(*pin_length > 0) {
*pin_length = *pin_length - 1;
pin[*pin_length] = 0;
- TOTP_CLI_DELETE_LAST_CHAR();
+ totp_cli_delete_last_char();
}
} else if(c == CliSymbolAsciiCR) {
cli_nl();
@@ -80,7 +79,7 @@ static bool totp_cli_read_pin(Cli* cli, uint8_t* pin, uint8_t* pin_length) {
}
}
- TOTP_CLI_DELETE_LAST_LINE();
+ totp_cli_delete_last_line();
return true;
}
@@ -97,22 +96,22 @@ void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cl
} else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE) == 0) {
do_remove = true;
} else {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ totp_cli_print_invalid_arguments();
}
} else {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ totp_cli_print_invalid_arguments();
}
if((do_change || do_remove) && totp_cli_ensure_authenticated(plugin_state, cli)) {
- bool load_generate_token_scene = false;
+ TOTP_CLI_LOCK_UI(plugin_state);
do {
uint8_t old_iv[TOTP_IV_SIZE];
memcpy(&old_iv[0], &plugin_state->iv[0], TOTP_IV_SIZE);
uint8_t new_pin[TOTP_IV_SIZE];
+ memset(&new_pin[0], 0, TOTP_IV_SIZE);
uint8_t new_pin_length = 0;
if(do_change) {
- if(!totp_cli_read_pin(cli, &new_pin[0], &new_pin_length) ||
- !totp_cli_ensure_authenticated(plugin_state, cli)) {
+ if(!totp_cli_read_pin(cli, &new_pin[0], &new_pin_length)) {
memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE);
break;
}
@@ -121,7 +120,7 @@ void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cl
memset(&new_pin[0], 0, TOTP_IV_SIZE);
}
- char* backup_path = totp_config_file_backup();
+ char* backup_path = totp_config_file_backup(plugin_state);
if(backup_path != NULL) {
TOTP_CLI_PRINTF_WARNING("Backup conf file %s has been created\r\n", backup_path);
TOTP_CLI_PRINTF_WARNING(
@@ -134,61 +133,28 @@ void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cl
break;
}
- if(plugin_state->current_scene == TotpSceneGenerateToken) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
- load_generate_token_scene = true;
- }
+ TOTP_CLI_PRINTF("Encrypting...\r\n");
- TOTP_CLI_PRINTF("Encrypting, please wait...\r\n");
-
- memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
- memset(&plugin_state->base_iv[0], 0, TOTP_IV_SIZE);
- if(plugin_state->crypto_verify_data != NULL) {
- free(plugin_state->crypto_verify_data);
- plugin_state->crypto_verify_data = NULL;
- }
-
- if(!totp_crypto_seed_iv(
- plugin_state, new_pin_length > 0 ? &new_pin[0] : NULL, new_pin_length)) {
- memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE);
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
- break;
- }
+ bool update_result =
+ totp_config_file_update_encryption(plugin_state, new_pin, new_pin_length);
memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE);
- TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
- TokenInfo* token_info = node->data;
- size_t plain_token_length;
- uint8_t* plain_token = totp_crypto_decrypt(
- token_info->token, token_info->token_length, &old_iv[0], &plain_token_length);
- free(token_info->token);
- token_info->token = totp_crypto_encrypt(
- plain_token,
- plain_token_length,
- &plugin_state->iv[0],
- &token_info->token_length);
- memset_s(plain_token, plain_token_length, 0, plain_token_length);
- free(plain_token);
- });
+ totp_cli_delete_last_line();
- TOTP_CLI_DELETE_LAST_LINE();
-
- if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
+ if(update_result) {
if(do_change) {
TOTP_CLI_PRINTF_SUCCESS("PIN has been successfully changed\r\n");
} else if(do_remove) {
TOTP_CLI_PRINTF_SUCCESS("PIN has been successfully removed\r\n");
}
} else {
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+ totp_cli_print_error_updating_config_file();
}
} while(false);
- if(load_generate_token_scene) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
- }
+ TOTP_CLI_UNLOCK_UI(plugin_state);
}
furi_string_free(temp_str);
diff --git a/applications/external/totp/cli/commands/reset/reset.c b/applications/external/totp/cli/commands/reset/reset.c
index c7928db31..96b2cc56a 100644
--- a/applications/external/totp/cli/commands/reset/reset.c
+++ b/applications/external/totp/cli/commands/reset/reset.c
@@ -1,8 +1,9 @@
#include "reset.h"
#include
-#include
+#include
#include "../../cli_helpers.h"
+#include "../../../ui/scene_director.h"
#include "../../../services/config/config.h"
#define TOTP_CLI_RESET_CONFIRMATION_KEYWORD "YES"
@@ -16,7 +17,11 @@ void totp_cli_command_reset_docopt_usage() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_RESET "\r\n");
}
-void totp_cli_command_reset_handle(Cli* cli, FuriMessageQueue* event_queue) {
+void totp_cli_command_reset_handle(
+ PluginState* plugin_state,
+ Cli* cli,
+ FuriMessageQueue* event_queue) {
+ TOTP_CLI_LOCK_UI(plugin_state);
TOTP_CLI_PRINTF_WARNING(
"As a result of reset all the settings and tokens will be permanently lost.\r\n");
TOTP_CLI_PRINTF_WARNING("Do you really want to reset application?\r\n");
@@ -27,11 +32,12 @@ void totp_cli_command_reset_handle(Cli* cli, FuriMessageQueue* event_queue) {
furi_string_cmpi_str(temp_str, TOTP_CLI_RESET_CONFIRMATION_KEYWORD) == 0;
furi_string_free(temp_str);
if(is_confirmed) {
- totp_config_file_reset();
+ totp_config_file_reset(plugin_state);
TOTP_CLI_PRINTF_SUCCESS("Application has been successfully reset to default.\r\n");
TOTP_CLI_PRINTF_SUCCESS("Now application will be closed to apply all the changes.\r\n");
totp_cli_force_close_app(event_queue);
} else {
TOTP_CLI_PRINTF_INFO("Action was not confirmed by user\r\n");
+ TOTP_CLI_UNLOCK_UI(plugin_state);
}
}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/reset/reset.h b/applications/external/totp/cli/commands/reset/reset.h
index 7c879e13e..3a4675587 100644
--- a/applications/external/totp/cli/commands/reset/reset.h
+++ b/applications/external/totp/cli/commands/reset/reset.h
@@ -5,6 +5,9 @@
#define TOTP_CLI_COMMAND_RESET "reset"
-void totp_cli_command_reset_handle(Cli* cli, FuriMessageQueue* event_queue);
+void totp_cli_command_reset_handle(
+ PluginState* plugin_state,
+ Cli* cli,
+ FuriMessageQueue* event_queue);
void totp_cli_command_reset_docopt_commands();
void totp_cli_command_reset_docopt_usage();
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/timezone/timezone.c b/applications/external/totp/cli/commands/timezone/timezone.c
index 61e4fa065..0f6bc5a76 100644
--- a/applications/external/totp/cli/commands/timezone/timezone.c
+++ b/applications/external/totp/cli/commands/timezone/timezone.c
@@ -33,19 +33,14 @@ void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* arg
char* strtof_endptr;
float tz = strtof(furi_string_get_cstr(temp_str), &strtof_endptr);
if(*strtof_endptr == 0 && tz >= -12.75f && tz <= 12.75f) {
+ TOTP_CLI_LOCK_UI(plugin_state);
plugin_state->timezone_offset = tz;
- if(totp_config_file_update_timezone_offset(tz) == TotpConfigFileUpdateSuccess) {
+ if(totp_config_file_update_timezone_offset(plugin_state)) {
TOTP_CLI_PRINTF_SUCCESS("Timezone is set to %f\r\n", (double)tz);
} else {
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
- }
- if(plugin_state->current_scene == TotpSceneGenerateToken) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
- } else if(plugin_state->current_scene == TotpSceneAppSettings) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
- totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings, NULL);
+ totp_cli_print_error_updating_config_file();
}
+ TOTP_CLI_UNLOCK_UI(plugin_state);
} else {
TOTP_CLI_PRINTF_ERROR("Invalid timezone offset\r\n");
}
diff --git a/applications/external/totp/cli/commands/update/update.c b/applications/external/totp/cli/commands/update/update.c
index bba7cad35..49beb7b6d 100644
--- a/applications/external/totp/cli/commands/update/update.c
+++ b/applications/external/totp/cli/commands/update/update.c
@@ -1,7 +1,6 @@
#include "update.h"
#include
#include
-#include
#include "../../../types/token_info.h"
#include "../../../services/config/config.h"
#include "../../../services/convert/convert.h"
@@ -11,6 +10,103 @@
#define TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX "-s"
+struct TotpUpdateContext {
+ FuriString* args;
+ Cli* cli;
+ uint8_t* iv;
+};
+
+enum TotpIteratorUpdateTokenResultsEx {
+ TotpIteratorUpdateTokenResultInvalidSecret = 1,
+ TotpIteratorUpdateTokenResultCancelled = 2,
+ TotpIteratorUpdateTokenResultInvalidArguments = 3
+};
+
+static bool totp_cli_try_read_name(
+ TokenInfo* token_info,
+ const FuriString* arg,
+ FuriString* args,
+ bool* parsed) {
+ if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_NAME_PREFIX) == 0) {
+ if(!args_read_probably_quoted_string_and_trim(args, token_info->name) ||
+ furi_string_empty(token_info->name)) {
+ totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_NAME_PREFIX);
+ } else {
+ *parsed = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool totp_cli_try_read_change_secret_flag(const FuriString* arg, bool* parsed, bool* flag) {
+ if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) == 0) {
+ *flag = true;
+ *parsed = true;
+ return true;
+ }
+
+ return false;
+}
+
+static TotpIteratorUpdateTokenResult
+ update_token_handler(TokenInfo* token_info, const void* context) {
+ const struct TotpUpdateContext* context_t = context;
+
+ // Read optional arguments
+ FuriString* temp_str = furi_string_alloc();
+ bool mask_user_input = true;
+ bool update_token_secret = false;
+ PlainTokenSecretEncoding token_secret_encoding = PlainTokenSecretEncodingBase32;
+ while(args_read_string_and_trim(context_t->args, temp_str)) {
+ bool parsed = false;
+ if(!totp_cli_try_read_name(token_info, temp_str, context_t->args, &parsed) &&
+ !totp_cli_try_read_algo(token_info, temp_str, context_t->args, &parsed) &&
+ !totp_cli_try_read_digits(token_info, temp_str, context_t->args, &parsed) &&
+ !totp_cli_try_read_duration(token_info, temp_str, context_t->args, &parsed) &&
+ !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) &&
+ !totp_cli_try_read_change_secret_flag(temp_str, &parsed, &update_token_secret) &&
+ !totp_cli_try_read_automation_features(token_info, temp_str, context_t->args, &parsed) &&
+ !totp_cli_try_read_plain_token_secret_encoding(
+ temp_str, context_t->args, &parsed, &token_secret_encoding)) {
+ totp_cli_printf_unknown_argument(temp_str);
+ }
+
+ if(!parsed) {
+ furi_string_free(temp_str);
+ return TotpIteratorUpdateTokenResultInvalidArguments;
+ }
+ }
+
+ if(update_token_secret) {
+ // Reading token secret
+ furi_string_reset(temp_str);
+ TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n");
+ bool token_secret_read = totp_cli_read_line(context_t->cli, temp_str, mask_user_input);
+ totp_cli_delete_last_line();
+ if(!token_secret_read) {
+ furi_string_secure_free(temp_str);
+ return TotpIteratorUpdateTokenResultCancelled;
+ }
+
+ if(!token_info_set_secret(
+ token_info,
+ furi_string_get_cstr(temp_str),
+ furi_string_size(temp_str),
+ token_secret_encoding,
+ context_t->iv)) {
+ furi_string_secure_free(temp_str);
+ return TotpIteratorUpdateTokenResultInvalidSecret;
+ }
+ }
+
+ furi_string_secure_free(temp_str);
+
+ return TotpIteratorUpdateTokenResultSuccess;
+}
+
void totp_cli_command_update_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_UPDATE " Update existing token\r\n");
}
@@ -34,136 +130,46 @@ void totp_cli_command_update_docopt_options() {
TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) " Update token secret\r\n");
}
-static bool
- totp_cli_try_read_name(TokenInfo* token_info, FuriString* arg, FuriString* args, bool* parsed) {
- if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_NAME_PREFIX) == 0) {
- if(!args_read_probably_quoted_string_and_trim(args, arg) || furi_string_empty(arg)) {
- totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_NAME_PREFIX);
- } else {
- if(token_info->name != NULL) {
- free(token_info->name);
- }
-
- size_t temp_cstr_len = furi_string_size(arg);
- token_info->name = malloc(temp_cstr_len + 1);
- furi_check(token_info->name != NULL);
- strlcpy(token_info->name, furi_string_get_cstr(arg), temp_cstr_len + 1);
- *parsed = true;
- }
-
- return true;
- }
-
- return false;
-}
-
-static bool totp_cli_try_read_change_secret_flag(const FuriString* arg, bool* parsed, bool* flag) {
- if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) == 0) {
- *flag = true;
- *parsed = true;
- return true;
- }
-
- return false;
-}
-
void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
- FuriString* temp_str = furi_string_alloc();
-
if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
return;
}
+ TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+
int token_number;
if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
- token_number > plugin_state->tokens_count) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ (size_t)token_number > totp_token_info_iterator_get_total_count(iterator_context)) {
+ totp_cli_print_invalid_arguments();
return;
}
- ListNode* list_item = list_element_at(plugin_state->tokens_list, token_number - 1);
- TokenInfo* existing_token_info = list_item->data;
- TokenInfo* token_info = token_info_clone(existing_token_info);
+ TOTP_CLI_LOCK_UI(plugin_state);
- // Read optional arguments
- bool mask_user_input = true;
- bool update_token_secret = false;
- PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32;
- while(args_read_string_and_trim(args, temp_str)) {
- bool parsed = false;
- if(!totp_cli_try_read_name(token_info, temp_str, args, &parsed) &&
- !totp_cli_try_read_algo(token_info, temp_str, args, &parsed) &&
- !totp_cli_try_read_digits(token_info, temp_str, args, &parsed) &&
- !totp_cli_try_read_duration(token_info, temp_str, args, &parsed) &&
- !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) &&
- !totp_cli_try_read_change_secret_flag(temp_str, &parsed, &update_token_secret) &&
- !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed) &&
- !totp_cli_try_read_plain_token_secret_encoding(
- temp_str, args, &parsed, &token_secret_encoding)) {
- totp_cli_printf_unknown_argument(temp_str);
- }
+ size_t previous_index = totp_token_info_iterator_get_current_token_index(iterator_context);
+ totp_token_info_iterator_go_to(iterator_context, token_number - 1);
- if(!parsed) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
- furi_string_free(temp_str);
- token_info_free(token_info);
- return;
- }
+ struct TotpUpdateContext update_context = {
+ .args = args, .cli = cli, .iv = &plugin_state->iv[0]};
+ TotpIteratorUpdateTokenResult update_result = totp_token_info_iterator_update_current_token(
+ iterator_context, &update_token_handler, &update_context);
+
+ if(update_result == TotpIteratorUpdateTokenResultSuccess) {
+ TOTP_CLI_PRINTF_SUCCESS(
+ "Token \"%s\" has been successfully updated\r\n",
+ furi_string_get_cstr(
+ totp_token_info_iterator_get_current_token(iterator_context)->name));
+ } else if(update_result == TotpIteratorUpdateTokenResultInvalidArguments) {
+ totp_cli_print_invalid_arguments();
+ } else if(update_result == TotpIteratorUpdateTokenResultCancelled) {
+ TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
+ } else if(update_result == TotpIteratorUpdateTokenResultInvalidSecret) {
+ TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n");
+ } else if(update_result == TotpIteratorUpdateTokenResultFileUpdateFailed) {
+ totp_cli_print_error_updating_config_file();
}
- bool token_secret_read = false;
- if(update_token_secret) {
- // Reading token secret
- furi_string_reset(temp_str);
- TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n");
- token_secret_read = totp_cli_read_line(cli, temp_str, mask_user_input);
- TOTP_CLI_DELETE_LAST_LINE();
- if(!token_secret_read) {
- TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
- }
- }
-
- bool load_generate_token_scene = false;
- if(plugin_state->current_scene == TotpSceneGenerateToken) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
- load_generate_token_scene = true;
- }
-
- bool token_secret_set = false;
- if(update_token_secret && token_secret_read) {
- if(token_info->token != NULL) {
- free(token_info->token);
- }
- token_secret_set = token_info_set_secret(
- token_info,
- furi_string_get_cstr(temp_str),
- furi_string_size(temp_str),
- token_secret_encoding,
- plugin_state->iv);
- if(!token_secret_set) {
- TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n");
- }
- }
-
- furi_string_secure_free(temp_str);
-
- if(!update_token_secret || (token_secret_read && token_secret_set)) {
- list_item->data = token_info;
-
- if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
- TOTP_CLI_PRINTF_SUCCESS(
- "Token \"%s\" has been successfully updated\r\n", token_info->name);
- token_info_free(existing_token_info);
- } else {
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
- list_item->data = existing_token_info;
- token_info_free(token_info);
- }
- } else {
- token_info_free(token_info);
- }
-
- if(load_generate_token_scene) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
- }
+ totp_token_info_iterator_go_to(iterator_context, previous_index);
+ TOTP_CLI_UNLOCK_UI(plugin_state);
}
\ No newline at end of file
diff --git a/applications/external/totp/cli/common_command_arguments.c b/applications/external/totp/cli/common_command_arguments.c
index 9ed9f0126..c3129a157 100644
--- a/applications/external/totp/cli/common_command_arguments.c
+++ b/applications/external/totp/cli/common_command_arguments.c
@@ -1,11 +1,11 @@
#include "common_command_arguments.h"
#include
-inline void totp_cli_printf_missed_argument_value(char* arg) {
+void totp_cli_printf_missed_argument_value(char* arg) {
TOTP_CLI_PRINTF_ERROR("Missed or incorrect value for argument \"%s\"\r\n", arg);
}
-inline void totp_cli_printf_unknown_argument(const FuriString* arg) {
+void totp_cli_printf_unknown_argument(const FuriString* arg) {
TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(arg));
}
@@ -121,10 +121,10 @@ bool totp_cli_try_read_plain_token_secret_encoding(
totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX);
} else {
if(furi_string_cmpi_str(arg, PLAIN_TOKEN_ENCODING_BASE32_NAME) == 0) {
- *secret_encoding = PLAIN_TOKEN_ENCODING_BASE32;
+ *secret_encoding = PlainTokenSecretEncodingBase32;
*parsed = true;
} else if(furi_string_cmpi_str(arg, PLAIN_TOKEN_ENCODING_BASE64_NAME) == 0) {
- *secret_encoding = PLAIN_TOKEN_ENCODING_BASE64;
+ *secret_encoding = PlainTokenSecretEncodingBase64;
*parsed = true;
} else {
TOTP_CLI_PRINTF_ERROR(
diff --git a/applications/external/totp/cli/common_command_arguments.h b/applications/external/totp/cli/common_command_arguments.h
index be01c216d..be1c0bdfe 100644
--- a/applications/external/totp/cli/common_command_arguments.h
+++ b/applications/external/totp/cli/common_command_arguments.h
@@ -19,23 +19,29 @@
#define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING "encoding"
void totp_cli_printf_unknown_argument(const FuriString* arg);
+
void totp_cli_printf_missed_argument_value(char* arg);
+
bool totp_cli_try_read_algo(TokenInfo* token_info, FuriString* arg, FuriString* args, bool* parsed);
+
bool totp_cli_try_read_digits(
TokenInfo* token_info,
const FuriString* arg,
FuriString* args,
bool* parsed);
+
bool totp_cli_try_read_duration(
TokenInfo* token_info,
const FuriString* arg,
FuriString* args,
bool* parsed);
+
bool totp_cli_try_read_automation_features(
TokenInfo* token_info,
FuriString* arg,
FuriString* args,
bool* parsed);
+
bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* unsecure_flag);
bool totp_cli_try_read_plain_token_secret_encoding(
diff --git a/applications/external/totp/lib/base64/base64.c b/applications/external/totp/lib/base64/base64.c
index dd0a12b5e..2cb0d2ffc 100644
--- a/applications/external/totp/lib/base64/base64.c
+++ b/applications/external/totp/lib/base64/base64.c
@@ -1,6 +1,7 @@
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005, Jouni Malinen
+ * Modified and optimized for Flipepr Zero device purposes by Alex Kopachov (@akopachov)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
diff --git a/applications/external/totp/lib/linked_list/linked_list.c b/applications/external/totp/lib/linked_list/linked_list.c
deleted file mode 100644
index 23d10c548..000000000
--- a/applications/external/totp/lib/linked_list/linked_list.c
+++ /dev/null
@@ -1,136 +0,0 @@
-#include "linked_list.h"
-
-ListNode* list_init_head(void* data) {
- ListNode* new = malloc(sizeof(ListNode));
- if(new == NULL) return NULL;
- new->data = data;
- new->next = NULL;
- return new;
-}
-
-ListNode* list_add(ListNode* head, void* data) {
- ListNode* new = malloc(sizeof(ListNode));
- if(new == NULL) return NULL;
- new->data = data;
- new->next = NULL;
-
- if(head == NULL)
- head = new;
- else {
- ListNode* it;
-
- for(it = head; it->next != NULL; it = it->next)
- ;
-
- it->next = new;
- }
-
- return head;
-}
-
-ListNode* list_find(ListNode* head, const void* data) {
- ListNode* it = NULL;
-
- for(it = head; it != NULL; it = it->next)
- if(it->data == data) break;
-
- return it;
-}
-
-ListNode* list_element_at(ListNode* head, uint16_t index) {
- ListNode* it;
- uint16_t i;
- for(it = head, i = 0; it != NULL && i < index; it = it->next, i++)
- ;
- return it;
-}
-
-ListNode* list_remove(ListNode* head, ListNode* ep) {
- if(head == NULL) {
- return NULL;
- }
-
- if(head == ep) {
- ListNode* new_head = head->next;
- free(head);
- return new_head;
- }
-
- ListNode* it;
-
- for(it = head; it->next != ep; it = it->next)
- ;
-
- it->next = ep->next;
- free(ep);
-
- return head;
-}
-
-ListNode* list_remove_at(ListNode* head, uint16_t index, void** removed_node_data) {
- if(head == NULL) {
- return NULL;
- }
-
- ListNode* it;
- ListNode* prev = NULL;
-
- uint16_t i;
-
- for(it = head, i = 0; it != NULL && i < index; prev = it, it = it->next, i++)
- ;
-
- if(it == NULL) return head;
-
- ListNode* new_head = head;
- if(prev == NULL) {
- new_head = it->next;
- } else {
- prev->next = it->next;
- }
-
- if(removed_node_data != NULL) {
- *removed_node_data = it->data;
- }
-
- free(it);
-
- return new_head;
-}
-
-ListNode* list_insert_at(ListNode* head, uint16_t index, void* data) {
- if(index == 0 || head == NULL) {
- ListNode* new_head = list_init_head(data);
- if(new_head != NULL) {
- new_head->next = head;
- }
- return new_head;
- }
-
- ListNode* it;
- ListNode* prev = NULL;
-
- uint16_t i;
-
- for(it = head, i = 0; it != NULL && i < index; prev = it, it = it->next, i++)
- ;
-
- ListNode* new = malloc(sizeof(ListNode));
- if(new == NULL) return NULL;
- new->data = data;
- new->next = it;
- prev->next = new;
-
- return head;
-}
-
-void list_free(ListNode* head) {
- ListNode* it = head;
- ListNode* tmp;
-
- while(it != NULL) {
- tmp = it;
- it = it->next;
- free(tmp);
- }
-}
diff --git a/applications/external/totp/lib/linked_list/linked_list.h b/applications/external/totp/lib/linked_list/linked_list.h
deleted file mode 100644
index 3c938e59a..000000000
--- a/applications/external/totp/lib/linked_list/linked_list.h
+++ /dev/null
@@ -1,98 +0,0 @@
-#pragma once
-
-#include
-#include
-
-/**
- * @brief Single linked list node
- */
-typedef struct ListNode {
- /**
- * @brief Pointer to the data assigned to the current list node
- */
- void* data;
-
- /**
- * @brief Pointer to the next list node
- */
- struct ListNode* next;
-} ListNode;
-
-/**
- * @brief Initializes a new list node head
- * @param data data to be assigned to the head list node
- * @return Head list node
- */
-ListNode* list_init_head(void* data);
-
-/**
- * @brief Adds new list node to the end of the list
- * @param head head list node
- * @param data data to be assigned to the newly added list node
- * @return Head list node
- */
-ListNode* list_add(
- ListNode* head,
- void* data); /* adds element with specified data to the end of the list and returns new head node. */
-
-/**
- * @brief Searches list node with the given assigned \p data in the list
- * @param head head list node
- * @param data data to be searched
- * @return List node containing \p data if there is such a node in the list; \c NULL otherwise
- */
-ListNode* list_find(ListNode* head, const void* data);
-
-/**
- * @brief Searches list node with the given \p index in the list
- * @param head head list node
- * @param index desired list node index
- * @return List node with the given \p index in the list if there is such a list node; \c NULL otherwise
- */
-ListNode* list_element_at(ListNode* head, uint16_t index);
-
-/**
- * @brief Removes list node from the list
- * @param head head list node
- * @param ep list node to be removed
- * @return Head list node
- */
-ListNode* list_remove(ListNode* head, ListNode* ep);
-
-/**
- * @brief Removes list node with the given \p index in the list from the list
- * @param head head list node
- * @param index index of the node to be removed
- * @param[out] removed_node_data data which was assigned to the removed list node
- * @return Head list node
- */
-ListNode* list_remove_at(ListNode* head, uint16_t index, void** removed_node_data);
-
-/**
- * @brief Inserts new list node at the given index
- * @param head head list node
- * @param index index in the list where the new list node should be inserted
- * @param data data to be assgned to the new list node
- * @return Head list node
- */
-ListNode* list_insert_at(ListNode* head, uint16_t index, void* data);
-
-/**
- * @brief Disposes all the list nodes in the list
- * @param head head list node
- */
-void list_free(ListNode* head);
-
-#define TOTP_LIST_INIT_OR_ADD(head, item, assert) \
- if(head == NULL) { \
- head = list_init_head(item); \
- assert(head != NULL); \
- } else { \
- assert(list_add(head, item) != NULL); \
- }
-
-#define TOTP_LIST_FOREACH(head, node, action) \
- ListNode* node = head; \
- while(node != NULL) { \
- action node = node->next; \
- }
diff --git a/applications/external/totp/lib/polyfills/strnlen.h b/applications/external/totp/lib/polyfills/strnlen.h
index 7dcef3a18..4fe0d540c 100644
--- a/applications/external/totp/lib/polyfills/strnlen.h
+++ b/applications/external/totp/lib/polyfills/strnlen.h
@@ -1,3 +1,6 @@
+#pragma once
+#pragma weak strnlen
+
#include
size_t strnlen(const char* s, size_t maxlen);
\ No newline at end of file
diff --git a/applications/external/totp/lib/roll_value/roll_value.c b/applications/external/totp/lib/roll_value/roll_value.c
index b8f30e078..563429d0d 100644
--- a/applications/external/totp/lib/roll_value/roll_value.c
+++ b/applications/external/totp/lib/roll_value/roll_value.c
@@ -25,4 +25,4 @@ TOTP_ROLL_VALUE_FN(int8_t, int8_t)
TOTP_ROLL_VALUE_FN(uint8_t, int8_t)
-TOTP_ROLL_VALUE_FN(uint16_t, int16_t);
\ No newline at end of file
+TOTP_ROLL_VALUE_FN(size_t, int16_t)
\ No newline at end of file
diff --git a/applications/external/totp/lib/roll_value/roll_value.h b/applications/external/totp/lib/roll_value/roll_value.h
index 3c270be9a..0c1894fd6 100644
--- a/applications/external/totp/lib/roll_value/roll_value.h
+++ b/applications/external/totp/lib/roll_value/roll_value.h
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
typedef uint8_t TotpRollValueOverflowBehavior;
@@ -47,7 +48,7 @@ TOTP_ROLL_VALUE_FN_HEADER(int8_t, int8_t);
TOTP_ROLL_VALUE_FN_HEADER(uint8_t, int8_t);
/**
- * @brief Rolls \c uint16_t \p value using \p min and \p max as an value constraints with \p step step.
+ * @brief Rolls \c size_t \p value using \p min and \p max as an value constraints with \p step step.
* When value reaches constraint value \p overflow_behavior defines what to do next.
* @param[in,out] value value to roll
* @param step step to be used to change value
@@ -55,4 +56,4 @@ TOTP_ROLL_VALUE_FN_HEADER(uint8_t, int8_t);
* @param max maximum possible value
* @param overflow_behavior defines what to do when value reaches constraint value
*/
-TOTP_ROLL_VALUE_FN_HEADER(uint16_t, int16_t);
\ No newline at end of file
+TOTP_ROLL_VALUE_FN_HEADER(size_t, int16_t);
\ No newline at end of file
diff --git a/applications/external/totp/services/config/config.c b/applications/external/totp/services/config/config.c
index 6d5838b28..b40750134 100644
--- a/applications/external/totp/services/config/config.c
+++ b/applications/external/totp/services/config/config.c
@@ -1,19 +1,41 @@
#include "config.h"
#include
#include
-#include
+#include
+#include
+#include
+#include
+#include
#include "../../types/common.h"
#include "../../types/token_info.h"
#include "../../features_config.h"
+#include "../crypto/crypto.h"
#include "migrations/common_migration.h"
-#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/authenticator")
#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf"
-#define CONFIG_FILE_BACKUP_BASE_PATH CONFIG_FILE_PATH ".backup"
+#define CONFIG_FILE_BACKUP_DIR CONFIG_FILE_DIRECTORY_PATH "/backups"
+#define CONFIG_FILE_BACKUP_BASE_PATH CONFIG_FILE_BACKUP_DIR "/totp.conf"
#define CONFIG_FILE_TEMP_PATH CONFIG_FILE_PATH ".tmp"
#define CONFIG_FILE_ORIG_PATH CONFIG_FILE_PATH ".orig"
#define CONFIG_FILE_PATH_PREVIOUS EXT_PATH("apps/Misc") "/totp.conf"
+struct ConfigFileContext {
+ /**
+ * @brief Config file reference
+ */
+ FlipperFormat* config_file;
+
+ /**
+ * @brief Storage reference
+ */
+ Storage* storage;
+
+ /**
+ * @brief Token list iterator context
+ */
+ TokenInfoIteratorContext* token_info_iterator_context;
+};
+
/**
* @brief Opens storage record
* @return Storage record
@@ -45,19 +67,34 @@ static void totp_close_config_file(FlipperFormat* file) {
* @return backup path if backup successfully taken; \c NULL otherwise
*/
static char* totp_config_file_backup_i(Storage* storage) {
- uint8_t backup_path_size = sizeof(CONFIG_FILE_BACKUP_BASE_PATH) + 5;
+ if(!storage_dir_exists(storage, CONFIG_FILE_BACKUP_DIR) &&
+ !storage_simply_mkdir(storage, CONFIG_FILE_BACKUP_DIR)) {
+ return NULL;
+ }
+
+ FuriHalRtcDateTime current_datetime;
+ furi_hal_rtc_get_datetime(¤t_datetime);
+
+ uint8_t backup_path_size = sizeof(CONFIG_FILE_BACKUP_BASE_PATH) + 14;
char* backup_path = malloc(backup_path_size);
furi_check(backup_path != NULL);
memcpy(backup_path, CONFIG_FILE_BACKUP_BASE_PATH, sizeof(CONFIG_FILE_BACKUP_BASE_PATH));
uint16_t i = 1;
bool backup_file_exists;
- while((backup_file_exists = storage_common_exists(storage, backup_path)) && i <= 9999) {
- snprintf(backup_path, backup_path_size, CONFIG_FILE_BACKUP_BASE_PATH ".%" PRIu16, i);
+ do {
+ snprintf(
+ backup_path,
+ backup_path_size,
+ CONFIG_FILE_BACKUP_BASE_PATH ".%4" PRIu16 "%02" PRIu8 "%02" PRIu8 "-%" PRIu16,
+ current_datetime.year,
+ current_datetime.month,
+ current_datetime.day,
+ i);
i++;
- }
+ } while((backup_file_exists = storage_common_exists(storage, backup_path)) && i <= 9999);
if(backup_file_exists ||
- !storage_common_copy(storage, CONFIG_FILE_PATH, backup_path) == FSE_OK) {
+ storage_common_copy(storage, CONFIG_FILE_PATH, backup_path) != FSE_OK) {
FURI_LOG_E(LOGGING_TAG, "Unable to take a backup");
free(backup_path);
return NULL;
@@ -73,7 +110,7 @@ static char* totp_config_file_backup_i(Storage* storage) {
* @param[out] file opened config file
* @return Config file open result
*/
-static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperFormat** file) {
+static bool totp_open_config_file(Storage* storage, FlipperFormat** file) {
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) == FSE_OK) {
@@ -81,7 +118,7 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
if(!flipper_format_file_open_existing(fff_data_file, CONFIG_FILE_PATH)) {
FURI_LOG_E(LOGGING_TAG, "Error opening existing file %s", CONFIG_FILE_PATH);
totp_close_config_file(fff_data_file);
- return TotpConfigFileOpenError;
+ return false;
}
} else if(storage_common_stat(storage, CONFIG_FILE_PATH_PREVIOUS, NULL) == FSE_OK) {
FURI_LOG_D(LOGGING_TAG, "Old config file %s found", CONFIG_FILE_PATH_PREVIOUS);
@@ -93,13 +130,13 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
FURI_LOG_E(LOGGING_TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
totp_close_config_file(fff_data_file);
- return TotpConfigFileOpenError;
+ return false;
}
}
if(storage_common_rename(storage, CONFIG_FILE_PATH_PREVIOUS, CONFIG_FILE_PATH) != FSE_OK) {
FURI_LOG_E(LOGGING_TAG, "Error moving config to %s", CONFIG_FILE_PATH);
totp_close_config_file(fff_data_file);
- return TotpConfigFileOpenError;
+ return false;
}
FURI_LOG_I(LOGGING_TAG, "Applied config file path migration");
return totp_open_config_file(storage, file);
@@ -112,421 +149,155 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
CONFIG_FILE_DIRECTORY_PATH);
if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
FURI_LOG_E(LOGGING_TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
- return TotpConfigFileOpenError;
+ return false;
}
}
if(!flipper_format_file_open_new(fff_data_file, CONFIG_FILE_PATH)) {
totp_close_config_file(fff_data_file);
FURI_LOG_E(LOGGING_TAG, "Error creating new file %s", CONFIG_FILE_PATH);
- return TotpConfigFileOpenError;
+ return false;
}
flipper_format_write_header_cstr(
fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION);
- float tmp_tz = 0;
- flipper_format_write_comment_cstr(fff_data_file, " ");
+
flipper_format_write_comment_cstr(
fff_data_file,
- "Timezone offset in hours. Important note: do not put '+' sign for positive values");
+ "Config file format specification can be found here: https://github.com/akopachov/flipper-zero_authenticator/blob/master/docs/conf-file_description.md");
+
+ float tmp_tz = 0;
flipper_format_write_float(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &tmp_tz, 1);
uint32_t tmp_uint32 = NotificationMethodSound | NotificationMethodVibro;
- flipper_format_write_comment_cstr(fff_data_file, " ");
- flipper_format_write_comment_cstr(
- fff_data_file,
- "How to notify user when new token is generated or badusb mode is activated (possible values: 0 - do not notify, 1 - sound, 2 - vibro, 3 sound and vibro)");
flipper_format_write_uint32(
fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1);
tmp_uint32 = AutomationMethodBadUsb;
- flipper_format_write_comment_cstr(fff_data_file, " ");
- flipper_format_write_comment_cstr(
- fff_data_file,
- "Automation method (0 - None, 1 - BadUSB, 2 - BadBT, 3 - BadUSB and BadBT)");
flipper_format_write_uint32(
fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1);
- FuriString* temp_str = furi_string_alloc();
-
- flipper_format_write_comment_cstr(fff_data_file, " ");
- flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE BEGIN ===");
- flipper_format_write_comment_cstr(fff_data_file, " ");
- flipper_format_write_comment_cstr(
- fff_data_file, "# Token name which will be visible in the UI.");
- furi_string_printf(temp_str, "%s: Sample token name", TOTP_CONFIG_KEY_TOKEN_NAME);
- flipper_format_write_comment(fff_data_file, temp_str);
- flipper_format_write_comment_cstr(fff_data_file, " ");
-
- flipper_format_write_comment_cstr(
- fff_data_file,
- "# Plain token secret without spaces, dashes and etc, just pure alpha-numeric characters. Important note: plain token will be encrypted and replaced by TOTP app");
- furi_string_printf(temp_str, "%s: plaintokensecret", TOTP_CONFIG_KEY_TOKEN_SECRET);
- flipper_format_write_comment(fff_data_file, temp_str);
- flipper_format_write_comment_cstr(fff_data_file, " ");
-
- furi_string_printf(
- temp_str,
- " # Token hashing algorithm to use during code generation. Supported options are %s, %s, %s, and %s. If you are not use which one to use - use %s",
- TOTP_TOKEN_ALGO_SHA1_NAME,
- TOTP_TOKEN_ALGO_SHA256_NAME,
- TOTP_TOKEN_ALGO_SHA512_NAME,
- TOTP_TOKEN_ALGO_STEAM_NAME,
- TOTP_TOKEN_ALGO_SHA1_NAME);
- flipper_format_write_comment(fff_data_file, temp_str);
- furi_string_printf(
- temp_str, "%s: %s", TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_TOKEN_ALGO_SHA1_NAME);
- flipper_format_write_comment(fff_data_file, temp_str);
- flipper_format_write_comment_cstr(fff_data_file, " ");
-
- flipper_format_write_comment_cstr(
- fff_data_file,
- "# How many digits there should be in generated code. Available options are 5, 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6");
- furi_string_printf(temp_str, "%s: 6", TOTP_CONFIG_KEY_TOKEN_DIGITS);
- flipper_format_write_comment(fff_data_file, temp_str);
- flipper_format_write_comment_cstr(fff_data_file, " ");
-
- flipper_format_write_comment_cstr(
- fff_data_file,
- "# Token lifetime duration in seconds. Should be between 15 and 255. Majority websites requires 30, however some rare websites may require custom lifetime. If you are not sure which one to use - use 30");
- furi_string_printf(temp_str, "%s: 30", TOTP_CONFIG_KEY_TOKEN_DURATION);
- flipper_format_write_comment(fff_data_file, temp_str);
- flipper_format_write_comment_cstr(fff_data_file, " ");
-
- flipper_format_write_comment_cstr(
- fff_data_file,
- "# Token input automation features (0 - None, 1 - press \"Enter\" key at the end of automation)");
- furi_string_printf(temp_str, "%s: 0", TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES);
- flipper_format_write_comment(fff_data_file, temp_str);
- flipper_format_write_comment_cstr(fff_data_file, " ");
-
- flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE END ===");
- flipper_format_write_comment_cstr(fff_data_file, " ");
-
- furi_string_free(temp_str);
if(!flipper_format_rewind(fff_data_file)) {
totp_close_config_file(fff_data_file);
FURI_LOG_E(LOGGING_TAG, "Rewind error");
- return TotpConfigFileOpenError;
+ return false;
}
}
*file = fff_data_file;
- return TotpConfigFileOpenSuccess;
+ return true;
}
-static TotpConfigFileUpdateResult
- totp_config_file_save_new_token_i(FlipperFormat* file, const TokenInfo* token_info) {
- TotpConfigFileUpdateResult update_result;
+char* totp_config_file_backup(const PluginState* plugin_state) {
+ if(plugin_state->config_file_context == NULL) return NULL;
+
+ totp_close_config_file(plugin_state->config_file_context->config_file);
+
+ char* result = totp_config_file_backup_i(plugin_state->config_file_context->storage);
+
+ totp_open_config_file(
+ plugin_state->config_file_context->storage,
+ &plugin_state->config_file_context->config_file);
+
+ totp_token_info_iterator_attach_to_config_file(
+ plugin_state->config_file_context->token_info_iterator_context,
+ plugin_state->config_file_context->config_file);
+
+ return result;
+}
+
+bool totp_config_file_update_timezone_offset(const PluginState* plugin_state) {
+ FlipperFormat* file = plugin_state->config_file_context->config_file;
+ flipper_format_rewind(file);
+ bool update_result = false;
+
do {
- if(!flipper_format_seek_to_end(file)) {
- update_result = TotpConfigFileUpdateError;
+ if(!flipper_format_insert_or_update_float(
+ file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
break;
}
- if(!flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- bool token_is_valid = token_info->token != NULL && token_info->token_length > 0;
- if(!token_is_valid &&
- !flipper_format_write_comment_cstr(file, "!!! WARNING BEGIN: INVALID TOKEN !!!")) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- if(!flipper_format_write_hex(
- file, TOTP_CONFIG_KEY_TOKEN_SECRET, token_info->token, token_info->token_length)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- if(!token_is_valid && !flipper_format_write_comment_cstr(file, "!!! WARNING END !!!")) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- if(!flipper_format_write_string_cstr(
- file, TOTP_CONFIG_KEY_TOKEN_ALGO, token_info_get_algo_as_cstr(token_info))) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- uint32_t tmp_uint32 = token_info->digits;
- if(!flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &tmp_uint32, 1)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- tmp_uint32 = token_info->duration;
- if(!flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DURATION, &tmp_uint32, 1)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- tmp_uint32 = token_info->automation_features;
- if(!flipper_format_write_uint32(
- file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &tmp_uint32, 1)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- update_result = TotpConfigFileUpdateSuccess;
+ update_result = true;
} while(false);
return update_result;
}
-char* totp_config_file_backup() {
- Storage* storage = totp_open_storage();
- char* result = totp_config_file_backup_i(storage);
- totp_close_storage();
- return result;
-}
-
-TotpConfigFileUpdateResult totp_config_file_save_new_token(const TokenInfo* token_info) {
- Storage* cfg_storage = totp_open_storage();
- FlipperFormat* file;
- TotpConfigFileUpdateResult update_result;
-
- if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
- do {
- if(totp_config_file_save_new_token_i(file, token_info) !=
- TotpConfigFileUpdateSuccess) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- update_result = TotpConfigFileUpdateSuccess;
- } while(false);
-
- totp_close_config_file(file);
- } else {
- update_result = TotpConfigFileUpdateError;
- }
-
- totp_close_storage();
- return update_result;
-}
-
-TotpConfigFileUpdateResult totp_config_file_update_timezone_offset(float new_timezone_offset) {
- Storage* cfg_storage = totp_open_storage();
- FlipperFormat* file;
- TotpConfigFileUpdateResult update_result;
-
- if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
- do {
- if(!flipper_format_insert_or_update_float(
- file, TOTP_CONFIG_KEY_TIMEZONE, &new_timezone_offset, 1)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- update_result = TotpConfigFileUpdateSuccess;
- } while(false);
-
- totp_close_config_file(file);
- } else {
- update_result = TotpConfigFileUpdateError;
- }
-
- totp_close_storage();
- return update_result;
-}
-
-TotpConfigFileUpdateResult
- totp_config_file_update_notification_method(NotificationMethod new_notification_method) {
- Storage* cfg_storage = totp_open_storage();
- FlipperFormat* file;
- TotpConfigFileUpdateResult update_result;
-
- if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
- do {
- uint32_t tmp_uint32 = new_notification_method;
- if(!flipper_format_insert_or_update_uint32(
- file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- update_result = TotpConfigFileUpdateSuccess;
- } while(false);
-
- totp_close_config_file(file);
- } else {
- update_result = TotpConfigFileUpdateError;
- }
-
- totp_close_storage();
- return update_result;
-}
-
-TotpConfigFileUpdateResult
- totp_config_file_update_automation_method(AutomationMethod new_automation_method) {
- Storage* cfg_storage = totp_open_storage();
- FlipperFormat* file;
- TotpConfigFileUpdateResult update_result;
-
- if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
- do {
- uint32_t tmp_uint32 = new_automation_method;
- if(!flipper_format_insert_or_update_uint32(
- file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- update_result = TotpConfigFileUpdateSuccess;
- } while(false);
-
- totp_close_config_file(file);
- } else {
- update_result = TotpConfigFileUpdateError;
- }
-
- totp_close_storage();
- return update_result;
-}
-
-TotpConfigFileUpdateResult totp_config_file_update_user_settings(const PluginState* plugin_state) {
- Storage* cfg_storage = totp_open_storage();
- FlipperFormat* file;
- TotpConfigFileUpdateResult update_result;
- if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
- do {
- if(!flipper_format_insert_or_update_float(
- file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
- uint32_t tmp_uint32 = plugin_state->notification_method;
- if(!flipper_format_insert_or_update_uint32(
- file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- tmp_uint32 = plugin_state->automation_method;
- if(!flipper_format_insert_or_update_uint32(
- file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- update_result = TotpConfigFileUpdateSuccess;
- } while(false);
-
- totp_close_config_file(file);
- } else {
- update_result = TotpConfigFileUpdateError;
- }
-
- totp_close_storage();
- return update_result;
-}
-
-TotpConfigFileUpdateResult totp_full_save_config_file(const PluginState* const plugin_state) {
- Storage* storage = totp_open_storage();
- FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
- TotpConfigFileUpdateResult result = TotpConfigFileUpdateSuccess;
+bool totp_config_file_update_notification_method(const PluginState* plugin_state) {
+ FlipperFormat* file = plugin_state->config_file_context->config_file;
+ flipper_format_rewind(file);
+ bool update_result = false;
do {
- if(!flipper_format_file_open_always(fff_data_file, CONFIG_FILE_TEMP_PATH)) {
- result = TotpConfigFileUpdateError;
+ uint32_t tmp_uint32 = plugin_state->notification_method;
+ if(!flipper_format_insert_or_update_uint32(
+ file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
break;
}
- if(!flipper_format_write_header_cstr(
- fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION)) {
- result = TotpConfigFileUpdateError;
+ update_result = true;
+ } while(false);
+
+ return update_result;
+}
+
+bool totp_config_file_update_automation_method(const PluginState* plugin_state) {
+ FlipperFormat* file = plugin_state->config_file_context->config_file;
+ flipper_format_rewind(file);
+ bool update_result = false;
+
+ do {
+ uint32_t tmp_uint32 = plugin_state->automation_method;
+ if(!flipper_format_insert_or_update_uint32(
+ file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) {
break;
}
- if(!flipper_format_write_hex(
- fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], TOTP_IV_SIZE)) {
- result = TotpConfigFileUpdateError;
- break;
- }
+ update_result = true;
+ } while(false);
- if(!flipper_format_write_hex(
- fff_data_file,
- TOTP_CONFIG_KEY_CRYPTO_VERIFY,
- plugin_state->crypto_verify_data,
- plugin_state->crypto_verify_data_length)) {
- result = TotpConfigFileUpdateError;
- break;
- }
+ return update_result;
+}
- if(!flipper_format_write_float(
- fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
- result = TotpConfigFileUpdateError;
- break;
- }
-
- if(!flipper_format_write_bool(
- fff_data_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) {
- result = TotpConfigFileUpdateError;
+bool totp_config_file_update_user_settings(const PluginState* plugin_state) {
+ FlipperFormat* file = plugin_state->config_file_context->config_file;
+ flipper_format_rewind(file);
+ bool update_result = false;
+ do {
+ if(!flipper_format_insert_or_update_float(
+ file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
break;
}
uint32_t tmp_uint32 = plugin_state->notification_method;
- if(!flipper_format_write_uint32(
- fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
- result = TotpConfigFileUpdateError;
+ if(!flipper_format_insert_or_update_uint32(
+ file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
break;
}
tmp_uint32 = plugin_state->automation_method;
- if(!flipper_format_write_uint32(
- fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) {
- result = TotpConfigFileUpdateError;
+ if(!flipper_format_insert_or_update_uint32(
+ file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) {
break;
}
- bool tokens_written = true;
- TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
- const TokenInfo* token_info = node->data;
- tokens_written = tokens_written &&
- totp_config_file_save_new_token_i(fff_data_file, token_info) ==
- TotpConfigFileUpdateSuccess;
- });
-
- if(!tokens_written) {
- result = TotpConfigFileUpdateError;
- break;
- }
+ update_result = true;
} while(false);
- totp_close_config_file(fff_data_file);
-
- if(result == TotpConfigFileUpdateSuccess) {
- if(storage_file_exists(storage, CONFIG_FILE_ORIG_PATH)) {
- storage_simply_remove(storage, CONFIG_FILE_ORIG_PATH);
- }
-
- if(storage_common_rename(storage, CONFIG_FILE_PATH, CONFIG_FILE_ORIG_PATH) != FSE_OK) {
- result = TotpConfigFileUpdateError;
- } else if(storage_common_rename(storage, CONFIG_FILE_TEMP_PATH, CONFIG_FILE_PATH) != FSE_OK) {
- result = TotpConfigFileUpdateError;
- } else if(!storage_simply_remove(storage, CONFIG_FILE_ORIG_PATH)) {
- result = TotpConfigFileUpdateError;
- }
- }
-
- totp_close_storage();
- return result;
+ return update_result;
}
-TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_state) {
+bool totp_config_file_load(PluginState* const plugin_state) {
Storage* storage = totp_open_storage();
FlipperFormat* fff_data_file;
-
- TotpConfigFileOpenResult result;
- if((result = totp_open_config_file(storage, &fff_data_file)) != TotpConfigFileOpenSuccess) {
+ if(!totp_open_config_file(storage, &fff_data_file)) {
totp_close_storage();
- return result;
+ return false;
}
+ flipper_format_rewind(fff_data_file);
+
+ bool result = false;
+
plugin_state->timezone_offset = 0;
FuriString* temp_str = furi_string_alloc();
@@ -535,7 +306,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
uint32_t file_version;
if(!flipper_format_read_header(fff_data_file, temp_str, &file_version)) {
FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header");
- result = TotpConfigFileOpenError;
break;
}
@@ -551,8 +321,7 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
char* backup_path = totp_config_file_backup_i(storage);
if(backup_path != NULL) {
- if(totp_open_config_file(storage, &fff_data_file) != TotpConfigFileOpenSuccess) {
- result = TotpConfigFileOpenError;
+ if(totp_open_config_file(storage, &fff_data_file) != true) {
break;
}
@@ -560,7 +329,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
if(!flipper_format_file_open_existing(fff_backup_data_file, backup_path)) {
flipper_format_file_close(fff_backup_data_file);
flipper_format_free(fff_backup_data_file);
- result = TotpConfigFileOpenError;
break;
}
@@ -575,7 +343,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
LOGGING_TAG,
"An error occurred during migration to version %" PRId16,
CONFIG_FILE_ACTUAL_VERSION);
- result = TotpConfigFileOpenError;
break;
}
@@ -588,7 +355,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
LOGGING_TAG,
"An error occurred during taking backup of %s before migration",
CONFIG_FILE_PATH);
- result = TotpConfigFileOpenError;
break;
}
}
@@ -599,7 +365,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
}
if(!flipper_format_rewind(fff_data_file)) {
- result = TotpConfigFileOpenError;
break;
}
@@ -626,7 +391,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
}
if(!flipper_format_rewind(fff_data_file)) {
- result = TotpConfigFileOpenError;
break;
}
@@ -637,7 +401,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
}
if(!flipper_format_rewind(fff_data_file)) {
- result = TotpConfigFileOpenError;
break;
}
@@ -664,186 +427,176 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
}
plugin_state->automation_method = tmp_uint32;
+
+ plugin_state->config_file_context = malloc(sizeof(ConfigFileContext));
+ furi_check(plugin_state->config_file_context != NULL);
+ plugin_state->config_file_context->storage = storage;
+ plugin_state->config_file_context->config_file = fff_data_file;
+ plugin_state->config_file_context->token_info_iterator_context =
+ totp_token_info_iterator_alloc(
+ storage, plugin_state->config_file_context->config_file, plugin_state->iv);
+ result = true;
} while(false);
furi_string_free(temp_str);
- totp_close_config_file(fff_data_file);
- totp_close_storage();
return result;
}
-TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state) {
- Storage* storage = totp_open_storage();
- FlipperFormat* fff_data_file;
- if(totp_open_config_file(storage, &fff_data_file) != TotpConfigFileOpenSuccess) {
- totp_close_storage();
- return TokenLoadingResultError;
- }
-
- FuriString* temp_str = furi_string_alloc();
- uint32_t temp_data32;
-
- if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
- FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header");
- totp_close_storage();
- furi_string_free(temp_str);
- return TokenLoadingResultError;
- }
-
- TokenLoadingResult result = TokenLoadingResultSuccess;
- uint16_t index = 0;
- bool has_any_plain_secret = false;
-
- while(true) {
- if(!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
+bool totp_config_file_update_crypto_signatures(const PluginState* plugin_state) {
+ FlipperFormat* config_file = plugin_state->config_file_context->config_file;
+ flipper_format_rewind(config_file);
+ bool update_result = false;
+ do {
+ if(!flipper_format_insert_or_update_hex(
+ config_file, TOTP_CONFIG_KEY_BASE_IV, plugin_state->base_iv, TOTP_IV_SIZE)) {
break;
}
- TokenInfo* tokenInfo = token_info_alloc();
-
- size_t temp_cstr_len = furi_string_size(temp_str);
- tokenInfo->name = malloc(temp_cstr_len + 1);
- furi_check(tokenInfo->name != NULL);
- strlcpy(tokenInfo->name, furi_string_get_cstr(temp_str), temp_cstr_len + 1);
-
- uint32_t secret_bytes_count;
- if(!flipper_format_get_value_count(
- fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) {
- secret_bytes_count = 0;
+ if(!flipper_format_insert_or_update_hex(
+ config_file,
+ TOTP_CONFIG_KEY_CRYPTO_VERIFY,
+ plugin_state->crypto_verify_data,
+ plugin_state->crypto_verify_data_length)) {
+ break;
}
- if(secret_bytes_count == 1) { // Plain secret key
- if(flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) {
- if(token_info_set_secret(
- tokenInfo,
- furi_string_get_cstr(temp_str),
- furi_string_size(temp_str),
- PLAIN_TOKEN_ENCODING_BASE32,
- &plugin_state->iv[0])) {
- FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has plain secret", tokenInfo->name);
- } else {
- tokenInfo->token = NULL;
- tokenInfo->token_length = 0;
- FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has invalid secret", tokenInfo->name);
- result = TokenLoadingResultWarning;
- }
- } else {
- tokenInfo->token = NULL;
- tokenInfo->token_length = 0;
- result = TokenLoadingResultWarning;
- }
-
- has_any_plain_secret = true;
- } else { // encrypted
- tokenInfo->token_length = secret_bytes_count;
- if(secret_bytes_count > 0) {
- tokenInfo->token = malloc(tokenInfo->token_length);
- furi_check(tokenInfo->token != NULL);
- if(!flipper_format_read_hex(
- fff_data_file,
- TOTP_CONFIG_KEY_TOKEN_SECRET,
- tokenInfo->token,
- tokenInfo->token_length)) {
- free(tokenInfo->token);
- tokenInfo->token = NULL;
- tokenInfo->token_length = 0;
- result = TokenLoadingResultWarning;
- }
- } else {
- tokenInfo->token = NULL;
- result = TokenLoadingResultWarning;
- }
+ if(!flipper_format_insert_or_update_bool(
+ config_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) {
+ break;
}
- if(!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str) ||
- !token_info_set_algo_from_str(tokenInfo, temp_str)) {
- tokenInfo->algo = SHA1;
- }
+ update_result = true;
+ } while(false);
- if(!flipper_format_read_uint32(
- fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &temp_data32, 1) ||
- !token_info_set_digits_from_int(tokenInfo, temp_data32)) {
- tokenInfo->digits = TOTP_6_DIGITS;
- }
-
- if(!flipper_format_read_uint32(
- fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &temp_data32, 1) ||
- !token_info_set_duration_from_int(tokenInfo, temp_data32)) {
- tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
- }
-
- if(flipper_format_read_uint32(
- fff_data_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &temp_data32, 1)) {
- tokenInfo->automation_features = temp_data32;
- } else {
- tokenInfo->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
- }
-
- FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name);
-
- TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
-
- index++;
- }
-
- plugin_state->tokens_count = index;
- plugin_state->token_list_loaded = true;
-
- FURI_LOG_D(LOGGING_TAG, "Found %" PRIu16 " tokens", index);
-
- furi_string_free(temp_str);
- totp_close_config_file(fff_data_file);
- totp_close_storage();
-
- if(has_any_plain_secret) {
- totp_full_save_config_file(plugin_state);
- }
-
- return result;
-}
-
-TotpConfigFileUpdateResult
- totp_config_file_update_crypto_signatures(const PluginState* plugin_state) {
- Storage* storage = totp_open_storage();
- FlipperFormat* config_file;
- TotpConfigFileUpdateResult update_result;
- if(totp_open_config_file(storage, &config_file) == TotpConfigFileOpenSuccess) {
- do {
- if(!flipper_format_insert_or_update_hex(
- config_file, TOTP_CONFIG_KEY_BASE_IV, plugin_state->base_iv, TOTP_IV_SIZE)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- if(!flipper_format_insert_or_update_hex(
- config_file,
- TOTP_CONFIG_KEY_CRYPTO_VERIFY,
- plugin_state->crypto_verify_data,
- plugin_state->crypto_verify_data_length)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- if(!flipper_format_insert_or_update_bool(
- config_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) {
- update_result = TotpConfigFileUpdateError;
- break;
- }
-
- update_result = TotpConfigFileUpdateSuccess;
- } while(false);
-
- totp_close_config_file(config_file);
- } else {
- update_result = TotpConfigFileUpdateError;
- }
-
- totp_close_storage();
return update_result;
}
-void totp_config_file_reset() {
+void totp_config_file_close(PluginState* const plugin_state) {
+ if(plugin_state->config_file_context == NULL) return;
+ totp_token_info_iterator_free(plugin_state->config_file_context->token_info_iterator_context);
+ totp_close_config_file(plugin_state->config_file_context->config_file);
+ free(plugin_state->config_file_context);
+ plugin_state->config_file_context = NULL;
+ totp_close_storage();
+}
+
+void totp_config_file_reset(PluginState* const plugin_state) {
+ totp_config_file_close(plugin_state);
Storage* storage = totp_open_storage();
storage_simply_remove(storage, CONFIG_FILE_PATH);
totp_close_storage();
}
+
+bool totp_config_file_update_encryption(
+ PluginState* plugin_state,
+ const uint8_t* new_pin,
+ uint8_t new_pin_length) {
+ FlipperFormat* config_file = plugin_state->config_file_context->config_file;
+ Stream* stream = flipper_format_get_raw_stream(config_file);
+ size_t original_offset = stream_tell(stream);
+ if(!stream_rewind(stream)) {
+ return false;
+ }
+
+ uint8_t old_iv[TOTP_IV_SIZE];
+ memcpy(&old_iv[0], &plugin_state->iv[0], TOTP_IV_SIZE);
+
+ memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
+ memset(&plugin_state->base_iv[0], 0, TOTP_IV_SIZE);
+ if(plugin_state->crypto_verify_data != NULL) {
+ free(plugin_state->crypto_verify_data);
+ plugin_state->crypto_verify_data = NULL;
+ }
+
+ CryptoSeedIVResult seed_result =
+ totp_crypto_seed_iv(plugin_state, new_pin_length > 0 ? new_pin : NULL, new_pin_length);
+ if(seed_result & CryptoSeedIVResultFlagSuccess &&
+ seed_result & CryptoSeedIVResultFlagNewCryptoVerifyData) {
+ if(!totp_config_file_update_crypto_signatures(plugin_state)) {
+ return false;
+ }
+ } else if(seed_result == CryptoSeedIVResultFailed) {
+ return false;
+ }
+
+ char buffer[sizeof(TOTP_CONFIG_KEY_TOKEN_SECRET) + 1];
+ bool result = true;
+
+ while(true) {
+ if(!stream_seek_to_char(stream, '\n', StreamDirectionForward)) {
+ break;
+ }
+
+ size_t buffer_read_size;
+ if((buffer_read_size = stream_read(stream, (uint8_t*)&buffer[0], sizeof(buffer))) == 0) {
+ break;
+ }
+
+ if(!stream_seek(stream, -(int32_t)buffer_read_size, StreamOffsetFromCurrent)) {
+ result = false;
+ break;
+ }
+
+ if(strncmp(buffer, "\n" TOTP_CONFIG_KEY_TOKEN_SECRET ":", sizeof(buffer)) == 0) {
+ uint32_t secret_bytes_count;
+ if(!flipper_format_get_value_count(
+ config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) {
+ secret_bytes_count = 0;
+ }
+
+ if(secret_bytes_count > 1) {
+ size_t secret_token_start = stream_tell(stream) + 1;
+ uint8_t* encrypted_token = malloc(secret_bytes_count);
+ furi_check(encrypted_token != NULL);
+
+ if(!flipper_format_read_hex(
+ config_file,
+ TOTP_CONFIG_KEY_TOKEN_SECRET,
+ encrypted_token,
+ secret_bytes_count)) {
+ result = false;
+ free(encrypted_token);
+ break;
+ }
+
+ size_t plain_token_length;
+ uint8_t* plain_token = totp_crypto_decrypt(
+ encrypted_token, secret_bytes_count, &old_iv[0], &plain_token_length);
+
+ free(encrypted_token);
+ size_t encrypted_token_length;
+ encrypted_token = totp_crypto_encrypt(
+ plain_token, plain_token_length, &plugin_state->iv[0], &encrypted_token_length);
+
+ memset_s(plain_token, plain_token_length, 0, plain_token_length);
+ free(plain_token);
+
+ if(!stream_seek(stream, secret_token_start, StreamOffsetFromStart)) {
+ result = false;
+ free(encrypted_token);
+ break;
+ }
+
+ if(!flipper_format_write_hex(
+ config_file,
+ TOTP_CONFIG_KEY_TOKEN_SECRET,
+ encrypted_token,
+ encrypted_token_length)) {
+ free(encrypted_token);
+ result = false;
+ break;
+ }
+
+ free(encrypted_token);
+ }
+ }
+ }
+
+ stream_seek(stream, original_offset, StreamOffsetFromStart);
+
+ return result;
+}
+
+TokenInfoIteratorContext* totp_config_get_token_iterator_context(const PluginState* plugin_state) {
+ return plugin_state->config_file_context->token_info_iterator_context;
+}
diff --git a/applications/external/totp/services/config/config.h b/applications/external/totp/services/config/config.h
index dabeb373a..d2fe957c6 100644
--- a/applications/external/totp/services/config/config.h
+++ b/applications/external/totp/services/config/config.h
@@ -1,138 +1,90 @@
#pragma once
-#include
-#include
#include "../../types/plugin_state.h"
#include "../../types/token_info.h"
+#include "config_file_context.h"
#include "constants.h"
+#include "token_info_iterator.h"
-typedef uint8_t TokenLoadingResult;
typedef uint8_t TotpConfigFileOpenResult;
typedef uint8_t TotpConfigFileUpdateResult;
-/**
- * @brief Token loading results
- */
-enum TokenLoadingResults {
- /**
- * @brief All the tokens loaded successfully
- */
- TokenLoadingResultSuccess,
-
- /**
- * @brief All the tokens loaded, but there are some warnings
- */
- TokenLoadingResultWarning,
-
- /**
- * @brief Tokens not loaded because of error(s)
- */
- TokenLoadingResultError
-};
-
-/**
- * @brief Config file opening result
- */
-enum TotpConfigFileOpenResults {
- /**
- * @brief Config file opened successfully
- */
- TotpConfigFileOpenSuccess = 0,
-
- /**
- * @brief An error has occurred during opening config file
- */
- TotpConfigFileOpenError = 1
-};
-
-/**
- * @brief Config file updating result
- */
-enum TotpConfigFileUpdateResults {
- /**
- * @brief Config file updated successfully
- */
- TotpConfigFileUpdateSuccess,
-
- /**
- * @brief An error has occurred during updating config file
- */
- TotpConfigFileUpdateError
-};
-
/**
* @brief Tries to take a config file backup
+ * @param plugin_state application state
* @return backup path if backup successfully taken; \c NULL otherwise
*/
-char* totp_config_file_backup();
-
-/**
- * @brief Saves all the settings and tokens to an application config file
- * @param plugin_state application state
- * @return Config file update result
- */
-TotpConfigFileUpdateResult totp_full_save_config_file(const PluginState* const plugin_state);
+char* totp_config_file_backup(const PluginState* plugin_state);
/**
* @brief Loads basic information from an application config file into application state without loading all the tokens
* @param plugin_state application state
* @return Config file open result
*/
-TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_state);
-
-/**
- * @brief Loads tokens from an application config file into application state
- * @param plugin_state application state
- * @return Results of the loading
- */
-TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state);
-
-/**
- * @brief Add new token to the end of the application config file
- * @param token_info token information to be saved
- * @return Config file update result
- */
-TotpConfigFileUpdateResult totp_config_file_save_new_token(const TokenInfo* token_info);
+bool totp_config_file_load(PluginState* const plugin_state);
/**
* @brief Updates timezone offset in an application config file
- * @param new_timezone_offset new timezone offset to be set
+ * @param plugin_state application state
* @return Config file update result
*/
-TotpConfigFileUpdateResult totp_config_file_update_timezone_offset(float new_timezone_offset);
+bool totp_config_file_update_timezone_offset(const PluginState* plugin_state);
/**
* @brief Updates notification method in an application config file
- * @param new_notification_method new notification method to be set
+ * @param plugin_state application state
* @return Config file update result
*/
-TotpConfigFileUpdateResult
- totp_config_file_update_notification_method(NotificationMethod new_notification_method);
+bool totp_config_file_update_notification_method(const PluginState* plugin_state);
/**
* @brief Updates automation method in an application config file
- * @param new_automation_method new automation method to be set
+ * @param plugin_state application state
* @return Config file update result
*/
-TotpConfigFileUpdateResult
- totp_config_file_update_automation_method(AutomationMethod new_automation_method);
+bool totp_config_file_update_automation_method(const PluginState* plugin_state);
/**
* @brief Updates application user settings
* @param plugin_state application state
* @return Config file update result
*/
-TotpConfigFileUpdateResult totp_config_file_update_user_settings(const PluginState* plugin_state);
+bool totp_config_file_update_user_settings(const PluginState* plugin_state);
/**
* @brief Updates crypto signatures information
* @param plugin_state application state
* @return Config file update result
*/
-TotpConfigFileUpdateResult
- totp_config_file_update_crypto_signatures(const PluginState* plugin_state);
+bool totp_config_file_update_crypto_signatures(const PluginState* plugin_state);
/**
* @brief Reset all the settings to default
+ * @param plugin_state application state
*/
-void totp_config_file_reset();
\ No newline at end of file
+void totp_config_file_reset(PluginState* const plugin_state);
+
+/**
+ * @brief Closes config file and releases all the resources
+ * @param plugin_state application state
+ */
+void totp_config_file_close(PluginState* const plugin_state);
+
+/**
+ * @brief Updates config file encryption by re-encrypting it using new user's PIN and new randomly generated IV
+ * @param plugin_state application state
+ * @param new_pin new user's PIN
+ * @param new_pin_length new user's PIN length
+ * @return \c true if config file encryption successfully updated; \c false otherwise
+ */
+bool totp_config_file_update_encryption(
+ PluginState* plugin_state,
+ const uint8_t* new_pin,
+ uint8_t new_pin_length);
+
+/**
+ * @brief Gets token info iterator context
+ * @param plugin_state application state
+ * @return token info iterator context
+ */
+TokenInfoIteratorContext* totp_config_get_token_iterator_context(const PluginState* plugin_state);
\ No newline at end of file
diff --git a/applications/external/totp/services/config/config_file_context.h b/applications/external/totp/services/config/config_file_context.h
new file mode 100644
index 000000000..98badbcbb
--- /dev/null
+++ b/applications/external/totp/services/config/config_file_context.h
@@ -0,0 +1,3 @@
+#pragma once
+
+typedef struct ConfigFileContext ConfigFileContext;
\ No newline at end of file
diff --git a/applications/external/totp/services/config/constants.h b/applications/external/totp/services/config/constants.h
index 7137e2374..3a33c80b3 100644
--- a/applications/external/totp/services/config/constants.h
+++ b/applications/external/totp/services/config/constants.h
@@ -1,7 +1,10 @@
#pragma once
+#include
+
+#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("authenticator")
#define CONFIG_FILE_HEADER "Flipper TOTP plugin config file"
-#define CONFIG_FILE_ACTUAL_VERSION (4)
+#define CONFIG_FILE_ACTUAL_VERSION (5)
#define TOTP_CONFIG_KEY_TIMEZONE "Timezone"
#define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName"
diff --git a/applications/external/totp/services/config/migrations/common_migration.c b/applications/external/totp/services/config/migrations/common_migration.c
index 073eaab12..07026fb1f 100644
--- a/applications/external/totp/services/config/migrations/common_migration.c
+++ b/applications/external/totp/services/config/migrations/common_migration.c
@@ -1,6 +1,7 @@
#include "common_migration.h"
#include "../constants.h"
#include "../../../types/token_info.h"
+#include
bool totp_config_migrate_to_latest(
FlipperFormat* fff_data_file,
@@ -57,18 +58,12 @@ bool totp_config_migrate_to_latest(
flipper_format_rewind(fff_backup_data_file);
- FuriString* comment_str = furi_string_alloc();
-
while(true) {
if(!flipper_format_read_string(
fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
break;
}
- furi_string_printf(
- comment_str, "=== BEGIN \"%s\" ===", furi_string_get_cstr(temp_str));
- flipper_format_write_comment(fff_data_file, comment_str);
- furi_string_printf(comment_str, "=== END \"%s\" ===", furi_string_get_cstr(temp_str));
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str);
flipper_format_read_string(
@@ -78,15 +73,32 @@ bool totp_config_migrate_to_latest(
if(current_version > 1) {
flipper_format_read_string(
fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
+
+ if(current_version < 5) {
+ uint32_t algo_as_uint32t = SHA1;
+ if(furi_string_cmpi_str(temp_str, TOTP_TOKEN_ALGO_SHA256_NAME) == 0) {
+ algo_as_uint32t = SHA256;
+ } else if(furi_string_cmpi_str(temp_str, TOTP_TOKEN_ALGO_SHA512_NAME) == 0) {
+ algo_as_uint32t = SHA512;
+ } else if(furi_string_cmpi_str(temp_str, TOTP_TOKEN_ALGO_STEAM_NAME) == 0) {
+ algo_as_uint32t = STEAM;
+ }
+
+ flipper_format_write_uint32(
+ fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, &algo_as_uint32t, 1);
+ } else {
+ flipper_format_write_string(
+ fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
+ }
flipper_format_read_string(
fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
} else {
- flipper_format_write_string_cstr(
- fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_TOKEN_ALGO_SHA1_NAME);
- const uint32_t default_digits = TOTP_6_DIGITS;
+ const uint32_t default_algo = SHA1;
+ flipper_format_write_uint32(
+ fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, &default_algo, 1);
+ const uint32_t default_digits = TotpSixDigitsCount;
flipper_format_write_uint32(
fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1);
}
@@ -108,18 +120,21 @@ bool totp_config_migrate_to_latest(
flipper_format_write_string(
fff_data_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, temp_str);
} else {
- const uint32_t default_automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+ const uint32_t default_automation_features = TokenAutomationFeatureNone;
flipper_format_write_uint32(
fff_data_file,
TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES,
&default_automation_features,
1);
}
-
- flipper_format_write_comment(fff_data_file, comment_str);
}
- furi_string_free(comment_str);
+ Stream* stream = flipper_format_get_raw_stream(fff_data_file);
+ size_t current_pos = stream_tell(stream);
+ size_t total_size = stream_size(stream);
+ if(current_pos < total_size) {
+ stream_delete(stream, total_size - current_pos);
+ }
result = true;
} while(false);
diff --git a/applications/external/totp/services/config/migrations/common_migration.h b/applications/external/totp/services/config/migrations/common_migration.h
index 71defc384..326277f14 100644
--- a/applications/external/totp/services/config/migrations/common_migration.h
+++ b/applications/external/totp/services/config/migrations/common_migration.h
@@ -2,6 +2,12 @@
#include
+/**
+ * @brief Migrates config file to the latest version
+ * @param fff_data_file original config file to be migrated
+ * @param fff_backup_data_file backup copy of original config file
+ * @return \c true if operation succeeded; \c false otherwise
+ */
bool totp_config_migrate_to_latest(
FlipperFormat* fff_data_file,
- FlipperFormat* fff_backup_data_file);
\ No newline at end of file
+ FlipperFormat* fff_backup_data_file);
diff --git a/applications/external/totp/services/config/token_info_iterator.c b/applications/external/totp/services/config/token_info_iterator.c
new file mode 100644
index 000000000..f8cd3c64e
--- /dev/null
+++ b/applications/external/totp/services/config/token_info_iterator.c
@@ -0,0 +1,547 @@
+#include "token_info_iterator.h"
+
+#include
+#include
+#include
+#include "../../types/common.h"
+
+#define CONFIG_FILE_PART_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf.part"
+#define STREAM_COPY_BUFFER_SIZE 128
+
+struct TokenInfoIteratorContext {
+ size_t total_count;
+ size_t current_index;
+ size_t last_seek_offset;
+ size_t last_seek_index;
+ TokenInfo* current_token;
+ FlipperFormat* config_file;
+ uint8_t* iv;
+ Storage* storage;
+};
+
+static bool
+ flipper_format_seek_to_siblinig_token_start(Stream* stream, StreamDirection direction) {
+ char buffer[sizeof(TOTP_CONFIG_KEY_TOKEN_NAME) + 1];
+ bool found = false;
+ while(!found) {
+ if(!stream_seek_to_char(stream, '\n', direction)) {
+ break;
+ }
+
+ size_t buffer_read_size;
+ if((buffer_read_size = stream_read(stream, (uint8_t*)&buffer[0], sizeof(buffer))) == 0) {
+ break;
+ }
+
+ if(!stream_seek(stream, -(int32_t)buffer_read_size, StreamOffsetFromCurrent)) {
+ break;
+ }
+
+ if(strncmp(buffer, "\n" TOTP_CONFIG_KEY_TOKEN_NAME ":", sizeof(buffer)) == 0) {
+ found = true;
+ }
+ }
+
+ return found;
+}
+
+static bool seek_to_token(size_t token_index, TokenInfoIteratorContext* context) {
+ furi_check(context != NULL && context->config_file != NULL);
+ if(token_index >= context->total_count) {
+ return false;
+ }
+
+ Stream* stream = flipper_format_get_raw_stream(context->config_file);
+ long token_index_diff = (long)token_index - (long)context->last_seek_index;
+ size_t token_index_diff_weight = (size_t)labs(token_index_diff);
+ StreamDirection direction = token_index_diff >= 0 ? StreamDirectionForward :
+ StreamDirectionBackward;
+ if(token_index_diff_weight > token_index || context->last_seek_offset == 0) {
+ context->last_seek_offset = 0;
+ context->last_seek_index = 0;
+ token_index_diff = token_index + 1;
+ direction = StreamDirectionForward;
+ } else if(token_index_diff_weight > (context->total_count - token_index - 1)) {
+ context->last_seek_offset = stream_size(stream);
+ context->last_seek_index = context->total_count - 1;
+ token_index_diff = -(long)(context->total_count - token_index);
+ direction = StreamDirectionBackward;
+ }
+
+ if(!stream_seek(stream, context->last_seek_offset, StreamOffsetFromStart)) {
+ return false;
+ }
+
+ if(token_index_diff != 0) {
+ long i = 0;
+ long i_inc = token_index_diff >= 0 ? 1 : -1;
+ do {
+ if(!flipper_format_seek_to_siblinig_token_start(stream, direction)) {
+ break;
+ }
+
+ i += i_inc;
+ } while((i_inc > 0 && i < token_index_diff) || (i_inc < 0 && i > token_index_diff));
+
+ if((i_inc > 0 && i < token_index_diff) || (i_inc < 0 && i > token_index_diff)) {
+ context->last_seek_offset = 0;
+ FURI_LOG_D(LOGGING_TAG, "Was not able to move");
+ return false;
+ }
+
+ context->last_seek_offset = stream_tell(stream);
+ context->last_seek_index = token_index;
+ }
+
+ return true;
+}
+
+static bool stream_insert_stream(Stream* dst, Stream* src) {
+ uint8_t buffer[STREAM_COPY_BUFFER_SIZE];
+ size_t buffer_read_size;
+ while((buffer_read_size = stream_read(src, buffer, sizeof(buffer))) != 0) {
+ if(!stream_insert(dst, buffer, buffer_read_size)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool ensure_stream_ends_with_lf(Stream* stream) {
+ uint8_t last_char;
+ size_t original_pos = stream_tell(stream);
+ if(!stream_seek(stream, -1, StreamOffsetFromEnd) || stream_read(stream, &last_char, 1) < 1) {
+ return false;
+ }
+
+ const uint8_t lf = '\n';
+ if(last_char != lf && !stream_write(stream, &lf, 1)) {
+ return false;
+ }
+
+ if(!stream_seek(stream, original_pos, StreamOffsetFromStart)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+ totp_token_info_iterator_save_current_token_info_changes(TokenInfoIteratorContext* context) {
+ bool is_new_token = context->current_index >= context->total_count;
+ Stream* stream = flipper_format_get_raw_stream(context->config_file);
+ if(is_new_token) {
+ if(!ensure_stream_ends_with_lf(stream) ||
+ !flipper_format_seek_to_end(context->config_file)) {
+ return false;
+ }
+ } else {
+ if(!seek_to_token(context->current_index, context)) {
+ return false;
+ }
+ }
+
+ size_t offset_start = stream_tell(stream);
+
+ size_t offset_end;
+ if(is_new_token) {
+ offset_end = offset_start;
+ } else if(context->current_index + 1 >= context->total_count) {
+ offset_end = stream_size(stream);
+ } else if(seek_to_token(context->current_index + 1, context)) {
+ offset_end = stream_tell(stream);
+ } else {
+ return false;
+ }
+
+ FlipperFormat* temp_ff = flipper_format_file_alloc(context->storage);
+ if(!flipper_format_file_open_always(temp_ff, CONFIG_FILE_PART_FILE_PATH)) {
+ flipper_format_free(temp_ff);
+ return false;
+ }
+
+ TokenInfo* token_info = context->current_token;
+ bool result = false;
+
+ do {
+ if(!flipper_format_write_string(temp_ff, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name)) {
+ break;
+ }
+
+ if(!flipper_format_write_hex(
+ temp_ff,
+ TOTP_CONFIG_KEY_TOKEN_SECRET,
+ token_info->token,
+ token_info->token_length)) {
+ break;
+ }
+
+ uint32_t tmp_uint32 = token_info->algo;
+ if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_ALGO, &tmp_uint32, 1)) {
+ break;
+ }
+
+ tmp_uint32 = token_info->digits;
+ if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_DIGITS, &tmp_uint32, 1)) {
+ break;
+ }
+
+ tmp_uint32 = token_info->duration;
+ if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_DURATION, &tmp_uint32, 1)) {
+ break;
+ }
+
+ tmp_uint32 = token_info->automation_features;
+ if(!flipper_format_write_uint32(
+ temp_ff, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &tmp_uint32, 1)) {
+ break;
+ }
+
+ Stream* temp_stream = flipper_format_get_raw_stream(temp_ff);
+
+ if(!stream_rewind(temp_stream)) {
+ break;
+ }
+
+ if(!stream_seek(stream, offset_start, StreamOffsetFromStart)) {
+ break;
+ }
+
+ if(offset_end != offset_start && !stream_delete(stream, offset_end - offset_start)) {
+ break;
+ }
+
+ if(!is_new_token && !stream_write_char(stream, '\n')) {
+ break;
+ }
+
+ if(!stream_insert_stream(stream, temp_stream)) {
+ break;
+ }
+
+ if(is_new_token) {
+ context->total_count++;
+ }
+
+ result = true;
+ } while(false);
+
+ flipper_format_free(temp_ff);
+ storage_common_remove(context->storage, CONFIG_FILE_PART_FILE_PATH);
+
+ stream_seek(stream, offset_start, StreamOffsetFromStart);
+ context->last_seek_offset = offset_start;
+ context->last_seek_index = context->current_index;
+
+ return result;
+}
+
+TokenInfoIteratorContext*
+ totp_token_info_iterator_alloc(Storage* storage, FlipperFormat* config_file, uint8_t* iv) {
+ Stream* stream = flipper_format_get_raw_stream(config_file);
+ stream_rewind(stream);
+ size_t tokens_count = 0;
+ while(true) {
+ if(!flipper_format_seek_to_siblinig_token_start(stream, StreamDirectionForward)) {
+ break;
+ }
+
+ tokens_count++;
+ }
+
+ TokenInfoIteratorContext* context = malloc(sizeof(TokenInfoIteratorContext));
+ furi_check(context != NULL);
+
+ context->total_count = tokens_count;
+ context->current_token = token_info_alloc();
+ context->config_file = config_file;
+ context->iv = iv;
+ context->storage = storage;
+ return context;
+}
+
+void totp_token_info_iterator_free(TokenInfoIteratorContext* context) {
+ if(context == NULL) return;
+ token_info_free(context->current_token);
+ free(context);
+}
+
+bool totp_token_info_iterator_remove_current_token_info(TokenInfoIteratorContext* context) {
+ if(!seek_to_token(context->current_index, context)) {
+ return false;
+ }
+
+ Stream* stream = flipper_format_get_raw_stream(context->config_file);
+ size_t begin_offset = stream_tell(stream);
+ size_t end_offset;
+ if(!ensure_stream_ends_with_lf(stream)) {
+ return false;
+ }
+
+ if(context->current_index >= context->total_count - 1) {
+ end_offset = stream_size(stream) - 1;
+ } else if(seek_to_token(context->current_index + 1, context)) {
+ end_offset = stream_tell(stream);
+ } else {
+ return false;
+ }
+
+ if(!stream_seek(stream, begin_offset, StreamOffsetFromStart) ||
+ !stream_delete(stream, end_offset - begin_offset)) {
+ return false;
+ }
+
+ context->total_count--;
+ if(context->current_index >= context->total_count) {
+ context->current_index = context->total_count - 1;
+ }
+
+ return true;
+}
+
+bool totp_token_info_iterator_move_current_token_info(
+ TokenInfoIteratorContext* context,
+ size_t new_index) {
+ if(context->current_index == new_index) return true;
+
+ Stream* stream = flipper_format_get_raw_stream(context->config_file);
+
+ if(!ensure_stream_ends_with_lf(stream)) {
+ return false;
+ }
+
+ if(!seek_to_token(context->current_index, context)) {
+ return false;
+ }
+
+ size_t begin_offset = stream_tell(stream);
+ size_t end_offset;
+ if(context->current_index >= context->total_count - 1) {
+ end_offset = stream_size(stream) - 1;
+ } else if(seek_to_token(context->current_index + 1, context)) {
+ end_offset = stream_tell(stream);
+ } else {
+ return false;
+ }
+
+ Stream* temp_stream = file_stream_alloc(context->storage);
+ if(!file_stream_open(
+ temp_stream, CONFIG_FILE_PART_FILE_PATH, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) {
+ stream_free(temp_stream);
+ return false;
+ }
+
+ size_t moving_size = end_offset - begin_offset;
+
+ bool result = false;
+ do {
+ if(!stream_seek(stream, begin_offset, StreamOffsetFromStart)) {
+ break;
+ }
+
+ if(stream_copy(stream, temp_stream, moving_size) < moving_size) {
+ break;
+ }
+
+ if(!stream_rewind(temp_stream)) {
+ break;
+ }
+
+ if(!stream_seek(stream, begin_offset, StreamOffsetFromStart)) {
+ break;
+ }
+
+ if(!stream_delete(stream, moving_size)) {
+ break;
+ }
+
+ context->last_seek_offset = 0;
+ context->last_seek_index = 0;
+ if(new_index >= context->total_count - 1) {
+ if(!stream_seek(stream, stream_size(stream) - 1, StreamOffsetFromStart)) {
+ break;
+ }
+ } else if(!seek_to_token(new_index, context)) {
+ break;
+ }
+
+ result = stream_insert_stream(stream, temp_stream);
+ } while(false);
+
+ stream_free(temp_stream);
+ storage_common_remove(context->storage, CONFIG_FILE_PART_FILE_PATH);
+
+ context->last_seek_offset = 0;
+ context->last_seek_index = 0;
+
+ return result;
+}
+
+TotpIteratorUpdateTokenResult totp_token_info_iterator_update_current_token(
+ TokenInfoIteratorContext* context,
+ TOTP_ITERATOR_UPDATE_TOKEN_ACTION update,
+ const void* update_context) {
+ TotpIteratorUpdateTokenResult result = update(context->current_token, update_context);
+ if(result == TotpIteratorUpdateTokenResultSuccess) {
+ if(!totp_token_info_iterator_save_current_token_info_changes(context)) {
+ result = TotpIteratorUpdateTokenResultFileUpdateFailed;
+ }
+
+ return result;
+ }
+
+ totp_token_info_iterator_go_to(context, context->current_index);
+ return result;
+}
+
+TotpIteratorUpdateTokenResult totp_token_info_iterator_add_new_token(
+ TokenInfoIteratorContext* context,
+ TOTP_ITERATOR_UPDATE_TOKEN_ACTION update,
+ const void* update_context) {
+ size_t previous_index = context->current_index;
+ context->current_index = context->total_count;
+ token_info_set_defaults(context->current_token);
+ TotpIteratorUpdateTokenResult result = update(context->current_token, update_context);
+ if(result == TotpIteratorUpdateTokenResultSuccess &&
+ !totp_token_info_iterator_save_current_token_info_changes(context)) {
+ result = TotpIteratorUpdateTokenResultFileUpdateFailed;
+ }
+
+ if(result != TotpIteratorUpdateTokenResultSuccess) {
+ totp_token_info_iterator_go_to(context, previous_index);
+ }
+
+ return result;
+}
+
+bool totp_token_info_iterator_go_to(TokenInfoIteratorContext* context, size_t token_index) {
+ furi_check(context != NULL);
+ context->current_index = token_index;
+ if(!seek_to_token(context->current_index, context)) {
+ return false;
+ }
+
+ Stream* stream = flipper_format_get_raw_stream(context->config_file);
+ size_t original_offset = stream_tell(stream);
+
+ if(!flipper_format_read_string(
+ context->config_file, TOTP_CONFIG_KEY_TOKEN_NAME, context->current_token->name)) {
+ stream_seek(stream, original_offset, StreamOffsetFromStart);
+ return false;
+ }
+
+ uint32_t secret_bytes_count;
+ if(!flipper_format_get_value_count(
+ context->config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) {
+ secret_bytes_count = 0;
+ }
+ TokenInfo* tokenInfo = context->current_token;
+ bool token_update_needed = false;
+ if(tokenInfo->token != NULL) {
+ free(tokenInfo->token);
+ tokenInfo->token_length = 0;
+ }
+
+ if(secret_bytes_count == 1) { // Plain secret key
+ FuriString* temp_str = furi_string_alloc();
+
+ if(flipper_format_read_string(
+ context->config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) {
+ if(token_info_set_secret(
+ tokenInfo,
+ furi_string_get_cstr(temp_str),
+ furi_string_size(temp_str),
+ PlainTokenSecretEncodingBase32,
+ context->iv)) {
+ FURI_LOG_W(
+ LOGGING_TAG,
+ "Token \"%s\" has plain secret",
+ furi_string_get_cstr(tokenInfo->name));
+ token_update_needed = true;
+ } else {
+ tokenInfo->token = NULL;
+ tokenInfo->token_length = 0;
+ FURI_LOG_W(
+ LOGGING_TAG,
+ "Token \"%s\" has invalid secret",
+ furi_string_get_cstr(tokenInfo->name));
+ }
+ } else {
+ tokenInfo->token = NULL;
+ tokenInfo->token_length = 0;
+ }
+
+ furi_string_free(temp_str);
+ } else { // encrypted
+ tokenInfo->token_length = secret_bytes_count;
+ if(secret_bytes_count > 0) {
+ tokenInfo->token = malloc(tokenInfo->token_length);
+ furi_check(tokenInfo->token != NULL);
+ if(!flipper_format_read_hex(
+ context->config_file,
+ TOTP_CONFIG_KEY_TOKEN_SECRET,
+ tokenInfo->token,
+ tokenInfo->token_length)) {
+ free(tokenInfo->token);
+ tokenInfo->token = NULL;
+ tokenInfo->token_length = 0;
+ }
+ } else {
+ tokenInfo->token = NULL;
+ }
+ }
+
+ uint32_t temp_data32;
+ if(!flipper_format_read_uint32(
+ context->config_file, TOTP_CONFIG_KEY_TOKEN_ALGO, &temp_data32, 1) ||
+ !token_info_set_algo_from_int(tokenInfo, temp_data32)) {
+ tokenInfo->algo = SHA1;
+ }
+
+ if(!flipper_format_read_uint32(
+ context->config_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &temp_data32, 1) ||
+ !token_info_set_digits_from_int(tokenInfo, temp_data32)) {
+ tokenInfo->digits = TotpSixDigitsCount;
+ }
+
+ if(!flipper_format_read_uint32(
+ context->config_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &temp_data32, 1) ||
+ !token_info_set_duration_from_int(tokenInfo, temp_data32)) {
+ tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
+ }
+
+ if(flipper_format_read_uint32(
+ context->config_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &temp_data32, 1)) {
+ tokenInfo->automation_features = temp_data32;
+ } else {
+ tokenInfo->automation_features = TokenAutomationFeatureNone;
+ }
+
+ stream_seek(stream, original_offset, StreamOffsetFromStart);
+
+ if(token_update_needed && !totp_token_info_iterator_save_current_token_info_changes(context)) {
+ return false;
+ }
+
+ return true;
+}
+
+const TokenInfo*
+ totp_token_info_iterator_get_current_token(const TokenInfoIteratorContext* context) {
+ return context->current_token;
+}
+
+size_t totp_token_info_iterator_get_current_token_index(const TokenInfoIteratorContext* context) {
+ return context->current_index;
+}
+
+size_t totp_token_info_iterator_get_total_count(const TokenInfoIteratorContext* context) {
+ return context->total_count;
+}
+
+void totp_token_info_iterator_attach_to_config_file(
+ TokenInfoIteratorContext* context,
+ FlipperFormat* config_file) {
+ context->config_file = config_file;
+}
\ No newline at end of file
diff --git a/applications/external/totp/services/config/token_info_iterator.h b/applications/external/totp/services/config/token_info_iterator.h
new file mode 100644
index 000000000..7e9a65853
--- /dev/null
+++ b/applications/external/totp/services/config/token_info_iterator.h
@@ -0,0 +1,121 @@
+#pragma once
+
+#include "../../types/token_info.h"
+#include
+#include "constants.h"
+
+typedef int TotpIteratorUpdateTokenResult;
+
+typedef TotpIteratorUpdateTokenResult (
+ *TOTP_ITERATOR_UPDATE_TOKEN_ACTION)(TokenInfo* const token_info, const void* context);
+
+typedef struct TokenInfoIteratorContext TokenInfoIteratorContext;
+
+enum TotpIteratorUpdateTokenResults {
+
+ /**
+ * @brief Token successfully updated
+ */
+ TotpIteratorUpdateTokenResultSuccess = 0,
+
+ /**
+ * @brief An error ocurred during updating config file
+ */
+ TotpIteratorUpdateTokenResultFileUpdateFailed = -1
+};
+
+/**
+ * @brief Initializes a new token info iterator
+ * @param storage storage reference
+ * @param config_file config file to use
+ * @param iv initialization vector (IV) to be used for encryption\decryption
+ * @return Token info iterator context
+ */
+TokenInfoIteratorContext*
+ totp_token_info_iterator_alloc(Storage* storage, FlipperFormat* config_file, uint8_t* iv);
+
+/**
+ * @brief Navigates iterator to the token with given index
+ * @param context token info iterator context
+ * @param token_index token index to navigate to
+ * @return \c true if navigation succeeded; \c false otherwise
+ */
+bool totp_token_info_iterator_go_to(TokenInfoIteratorContext* context, size_t token_index);
+
+/**
+ * @brief Moves current token to a given new index
+ * @param context token info iterator context
+ * @param new_index new token index to move current token to
+ * @return \c true if operation succeeded; \c false otherwise
+ */
+bool totp_token_info_iterator_move_current_token_info(
+ TokenInfoIteratorContext* context,
+ size_t new_index);
+
+/**
+ * @brief Updates current token info using given update action
+ * @param context token info iterator context
+ * @param update action which is responsible to make all the necessary updates to token info
+ * @param update_context update action context
+ * @return \c true if operation succeeded; \c false otherwise
+ */
+TotpIteratorUpdateTokenResult totp_token_info_iterator_update_current_token(
+ TokenInfoIteratorContext* context,
+ TOTP_ITERATOR_UPDATE_TOKEN_ACTION update,
+ const void* update_context);
+
+/**
+ * @brief Adds new token info to the end of the list using given update action
+ * @param context token info iterator context
+ * @param update action which is responsible to make all the necessary updates to token info
+ * @param update_context update action context
+ * @return \c true if operation succeeded; \c false otherwise
+ */
+TotpIteratorUpdateTokenResult totp_token_info_iterator_add_new_token(
+ TokenInfoIteratorContext* context,
+ TOTP_ITERATOR_UPDATE_TOKEN_ACTION update,
+ const void* update_context);
+
+/**
+ * @brief Remvoves current token info
+ * @param context token info iterator context
+ * @return \c true if operation succeeded; \c false otherwise
+ */
+bool totp_token_info_iterator_remove_current_token_info(TokenInfoIteratorContext* context);
+
+/**
+ * @brief Disposes token info iterator and releases all the resources
+ * @param context token info iterator context
+ */
+void totp_token_info_iterator_free(TokenInfoIteratorContext* context);
+
+/**
+ * @brief Gets current token info
+ * @param context token info iterator context
+ * @return current token info
+ */
+const TokenInfo*
+ totp_token_info_iterator_get_current_token(const TokenInfoIteratorContext* context);
+
+/**
+ * @brief Gets current token info index
+ * @param context token info iterator context
+ * @return current token info index
+ */
+size_t totp_token_info_iterator_get_current_token_index(const TokenInfoIteratorContext* context);
+
+/**
+ * @brief Gets total amount of token infos found
+ * @param context token info iterator context
+ * @return amount of token infos found
+ */
+size_t totp_token_info_iterator_get_total_count(const TokenInfoIteratorContext* context);
+
+/**
+ * @brief Attaches token info iterator to another config file
+ * @param context token info iterator context
+ * @param config_file config file reference to attach token info iterator to
+ */
+void totp_token_info_iterator_attach_to_config_file(
+ TokenInfoIteratorContext* context,
+ FlipperFormat* config_file);
diff --git a/applications/external/totp/services/crypto/crypto.c b/applications/external/totp/services/crypto/crypto.c
index 55fb1df6a..03d9c9d51 100644
--- a/applications/external/totp/services/crypto/crypto.c
+++ b/applications/external/totp/services/crypto/crypto.c
@@ -1,7 +1,7 @@
#include "crypto.h"
-#include
-#include
-#include "../config/config.h"
+#include
+#include
+#include
#include "../../types/common.h"
#include "memset_s.h"
@@ -61,9 +61,11 @@ uint8_t* totp_crypto_decrypt(
return decrypted_data;
}
-bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length) {
+CryptoSeedIVResult
+ totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length) {
+ CryptoSeedIVResult result;
if(plugin_state->crypto_verify_data == NULL) {
- FURI_LOG_D(LOGGING_TAG, "Generating new IV");
+ FURI_LOG_I(LOGGING_TAG, "Generating new IV");
furi_hal_random_fill_buf(&plugin_state->base_iv[0], TOTP_IV_SIZE);
}
@@ -94,9 +96,9 @@ bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t
}
}
- bool result = true;
+ result = CryptoSeedIVResultFlagSuccess;
if(plugin_state->crypto_verify_data == NULL) {
- FURI_LOG_D(LOGGING_TAG, "Generating crypto verify data");
+ FURI_LOG_I(LOGGING_TAG, "Generating crypto verify data");
plugin_state->crypto_verify_data = malloc(CRYPTO_VERIFY_KEY_LENGTH);
furi_check(plugin_state->crypto_verify_data != NULL);
plugin_state->crypto_verify_data_length = CRYPTO_VERIFY_KEY_LENGTH;
@@ -109,8 +111,7 @@ bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t
plugin_state->pin_set = pin != NULL && pin_length > 0;
- result = totp_config_file_update_crypto_signatures(plugin_state) ==
- TotpConfigFileUpdateSuccess;
+ result |= CryptoSeedIVResultFlagNewCryptoVerifyData;
}
return result;
diff --git a/applications/external/totp/services/crypto/crypto.h b/applications/external/totp/services/crypto/crypto.h
index 3442b9a6e..ab27191a8 100644
--- a/applications/external/totp/services/crypto/crypto.h
+++ b/applications/external/totp/services/crypto/crypto.h
@@ -2,6 +2,26 @@
#include "../../types/plugin_state.h"
+typedef uint8_t CryptoSeedIVResult;
+
+enum CryptoSeedIVResults {
+
+ /**
+ * @brief IV seeding operation failed
+ */
+ CryptoSeedIVResultFailed = 0b00,
+
+ /**
+ * @brief IV seeding operation succeeded
+ */
+ CryptoSeedIVResultFlagSuccess = 0b01,
+
+ /**
+ * @brief As a part of IV seeding operation new crypto verify data has been generated
+ */
+ CryptoSeedIVResultFlagNewCryptoVerifyData = 0b10
+};
+
/**
* @brief Encrypts plain data using built-in certificate and given initialization vector (IV)
* @param plain_data plain data to be encrypted
@@ -35,9 +55,10 @@ uint8_t* totp_crypto_decrypt(
* @param plugin_state application state
* @param pin user's PIN
* @param pin_length user's PIN length
- * @return \c true on success; \c false otherwise
+ * @return Results of seeding IV
*/
-bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length);
+CryptoSeedIVResult
+ totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length);
/**
* @brief Verifies whether cryptographic information (certificate + IV) is valid and can be used for encryption and decryption
diff --git a/applications/external/totp/services/hmac/hmac_common.h b/applications/external/totp/services/hmac/hmac_common.h
index 0cd56ed99..3499cb800 100644
--- a/applications/external/totp/services/hmac/hmac_common.h
+++ b/applications/external/totp/services/hmac/hmac_common.h
@@ -1,5 +1,4 @@
#include
-#include "sha256.h"
#include "memxor.h"
#define IPAD 0x36
diff --git a/applications/external/totp/services/hmac/hmac_sha256.c b/applications/external/totp/services/hmac/hmac_sha256.c
index c51f24b4d..00ac2a177 100644
--- a/applications/external/totp/services/hmac/hmac_sha256.c
+++ b/applications/external/totp/services/hmac/hmac_sha256.c
@@ -15,6 +15,7 @@
along with this program. If not, see . */
#include "hmac_sha256.h"
+#include "sha256.h"
#define GL_HMAC_NAME 256
#define GL_HMAC_BLOCKSIZE 64
diff --git a/applications/external/totp/services/hmac/sha1.c b/applications/external/totp/services/hmac/sha1.c
index ecf22fc97..29f22e3c3 100644
--- a/applications/external/totp/services/hmac/sha1.c
+++ b/applications/external/totp/services/hmac/sha1.c
@@ -27,6 +27,8 @@
#include
#include
+#include "sha_pad_buffer.h"
+
#ifdef WORDS_BIGENDIAN
#define SWAP(n) (n)
#else
@@ -34,10 +36,6 @@
#define SWAP(n) swap_uint32(n)
#endif
-/* This array contains the bytes used to pad the buffer to the next
- 64-byte boundary. (RFC 1321, 3.1: Step 1) */
-static const unsigned char fillbuf[64] = {0x80, 0 /* , 0, 0, ... */};
-
/* Take a pointer to a 160 bit block of data (five 32 bit ints) and
initialize it to the start constants of the SHA1 algorithm. This
must be called before using hash in the call to sha1_hash. */
@@ -87,7 +85,7 @@ void* sha1_finish_ctx(struct sha1_ctx* ctx, void* resbuf) {
ctx->buffer[size - 2] = SWAP((ctx->total[1] << 3) | (ctx->total[0] >> 29));
ctx->buffer[size - 1] = SWAP(ctx->total[0] << 3);
- memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes);
+ sha_pad_buffer(&((uint8_t*)ctx->buffer)[bytes], (size - 2) * 4 - bytes);
/* Process last bytes. */
sha1_process_block(ctx->buffer, size * 4, ctx);
diff --git a/applications/external/totp/services/hmac/sha256.c b/applications/external/totp/services/hmac/sha256.c
index 89ca67c2b..09ba272e7 100644
--- a/applications/external/totp/services/hmac/sha256.c
+++ b/applications/external/totp/services/hmac/sha256.c
@@ -25,6 +25,7 @@
#include
#include
+#include "sha_pad_buffer.h"
#ifdef WORDS_BIGENDIAN
#define SWAP(n) (n)
@@ -33,10 +34,6 @@
#define SWAP(n) swap_uint32(n)
#endif
-/* This array contains the bytes used to pad the buffer to the next
- 64-byte boundary. */
-static const unsigned char fillbuf[64] = {0x80, 0 /* , 0, 0, ... */};
-
/*
Takes a pointer to a 256 bit block of data (eight 32 bit ints) and
initializes it to the start constants of the SHA256 algorithm. This
@@ -91,7 +88,7 @@ static void sha256_conclude_ctx(struct sha256_ctx* ctx) {
set_uint32((char*)&ctx->buffer[size - 2], SWAP((ctx->total[1] << 3) | (ctx->total[0] >> 29)));
set_uint32((char*)&ctx->buffer[size - 1], SWAP(ctx->total[0] << 3));
- memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes);
+ sha_pad_buffer(&((uint8_t*)ctx->buffer)[bytes], (size - 2) * 4 - bytes);
/* Process last bytes. */
sha256_process_block(ctx->buffer, size * 4, ctx);
diff --git a/applications/external/totp/services/hmac/sha512.c b/applications/external/totp/services/hmac/sha512.c
index b56dd0f2e..ffe2864fb 100644
--- a/applications/external/totp/services/hmac/sha512.c
+++ b/applications/external/totp/services/hmac/sha512.c
@@ -27,13 +27,10 @@
#include
#include "byteswap.h"
+#include "sha_pad_buffer.h"
#define SWAP(n) swap_uint64(n)
-/* This array contains the bytes used to pad the buffer to the next
- 128-byte boundary. */
-static const unsigned char fillbuf[128] = {0x80, 0 /* , 0, 0, ... */};
-
/*
Takes a pointer to a 512 bit block of data (eight 64 bit ints) and
initializes it to the start constants of the SHA512 algorithm. This
@@ -90,7 +87,7 @@ static void sha512_conclude_ctx(struct sha512_ctx* ctx) {
SWAP(u64or(u64shl(ctx->total[1], 3), u64shr(ctx->total[0], 61))));
set_uint64((char*)&ctx->buffer[size - 1], SWAP(u64shl(ctx->total[0], 3)));
- memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 8 - bytes);
+ sha_pad_buffer(&((uint8_t*)ctx->buffer)[bytes], (size - 2) * 8 - bytes);
/* Process last bytes. */
sha512_process_block(ctx->buffer, size * 8, ctx);
diff --git a/applications/external/totp/services/hmac/sha_pad_buffer.c b/applications/external/totp/services/hmac/sha_pad_buffer.c
new file mode 100644
index 000000000..badedbcc7
--- /dev/null
+++ b/applications/external/totp/services/hmac/sha_pad_buffer.c
@@ -0,0 +1,11 @@
+#include "sha_pad_buffer.h"
+#include
+
+void sha_pad_buffer(uint8_t* buffer, size_t size) {
+ if(size > 0) {
+ buffer[0] = 0x80;
+ if(size > 1) {
+ memset(&buffer[1], 0, size - 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/applications/external/totp/services/hmac/sha_pad_buffer.h b/applications/external/totp/services/hmac/sha_pad_buffer.h
new file mode 100644
index 000000000..7dba40fa9
--- /dev/null
+++ b/applications/external/totp/services/hmac/sha_pad_buffer.h
@@ -0,0 +1,4 @@
+#include
+#include
+
+void sha_pad_buffer(uint8_t* buffer, size_t size);
\ No newline at end of file
diff --git a/applications/external/totp/services/totp/totp.c b/applications/external/totp/services/totp/totp.c
index f6e0401e6..45a283b06 100644
--- a/applications/external/totp/services/totp/totp.c
+++ b/applications/external/totp/services/totp/totp.c
@@ -1,15 +1,13 @@
#include "totp.h"
-#include
#include
#include
-#include
#include
+#include
#include "../hmac/hmac_sha1.h"
#include "../hmac/hmac_sha256.h"
#include "../hmac/hmac_sha512.h"
#include "../hmac/byteswap.h"
-#include "../../lib/timezone_utils/timezone_utils.h"
#define HMAC_MAX_RESULT_SIZE HMAC_SHA512_RESULT_SIZE
diff --git a/applications/external/totp/totp_app.c b/applications/external/totp/totp_app.c
index 8a014628d..4e0bbbd25 100644
--- a/applications/external/totp/totp_app.c
+++ b/applications/external/totp/totp_app.c
@@ -1,10 +1,7 @@
-#include
-#include
#include
#include
#include
#include
-#include
#include
#include
#include "features_config.h"
@@ -53,23 +50,39 @@ static bool totp_activate_initial_scene(PluginState* const plugin_state) {
dialog_message_show(plugin_state->dialogs_app, message);
dialog_message_free(message);
if(dialog_result == DialogMessageButtonRight) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL);
+ totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication);
} else {
- if(!totp_crypto_seed_iv(plugin_state, NULL, 0)) {
+ CryptoSeedIVResult seed_result = totp_crypto_seed_iv(plugin_state, NULL, 0);
+ if(seed_result & CryptoSeedIVResultFlagSuccess &&
+ seed_result & CryptoSeedIVResultFlagNewCryptoVerifyData) {
+ if(!totp_config_file_update_crypto_signatures(plugin_state)) {
+ totp_dialogs_config_loading_error(plugin_state);
+ return false;
+ }
+ } else if(seed_result == CryptoSeedIVResultFailed) {
totp_dialogs_config_loading_error(plugin_state);
return false;
}
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+
+ totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
}
} else if(plugin_state->pin_set) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL);
+ totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication);
} else {
- if(!totp_crypto_seed_iv(plugin_state, NULL, 0)) {
+ CryptoSeedIVResult seed_result = totp_crypto_seed_iv(plugin_state, NULL, 0);
+ if(seed_result & CryptoSeedIVResultFlagSuccess &&
+ seed_result & CryptoSeedIVResultFlagNewCryptoVerifyData) {
+ if(!totp_config_file_update_crypto_signatures(plugin_state)) {
+ totp_dialogs_config_loading_error(plugin_state);
+ return false;
+ }
+ } else if(seed_result == CryptoSeedIVResultFailed) {
totp_dialogs_config_loading_error(plugin_state);
return false;
}
+
if(totp_crypto_verify_key(plugin_state)) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+ totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
} else {
FURI_LOG_E(
LOGGING_TAG,
@@ -98,16 +111,12 @@ static bool totp_plugin_state_init(PluginState* const plugin_state) {
plugin_state->dialogs_app = furi_record_open(RECORD_DIALOGS);
memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
- if(totp_config_file_load_base(plugin_state) != TotpConfigFileOpenSuccess) {
+ if(!totp_config_file_load(plugin_state)) {
totp_dialogs_config_loading_error(plugin_state);
return false;
}
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
- if(plugin_state->mutex == NULL) {
- FURI_LOG_E(LOGGING_TAG, "Cannot create mutex\r\n");
- return false;
- }
#ifdef TOTP_BADBT_TYPE_ENABLED
if(plugin_state->automation_method & AutomationMethodBadBt) {
@@ -125,15 +134,7 @@ static void totp_plugin_state_free(PluginState* plugin_state) {
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_DIALOGS);
- ListNode* node = plugin_state->tokens_list;
- ListNode* tmp;
- while(node != NULL) {
- tmp = node->next;
- TokenInfo* tokenInfo = node->data;
- token_info_free(tokenInfo);
- free(node);
- node = tmp;
- }
+ totp_config_file_close(plugin_state);
if(plugin_state->crypto_verify_data != NULL) {
free(plugin_state->crypto_verify_data);
@@ -196,8 +197,9 @@ int32_t totp_app() {
}
} else if(
plugin_state->pin_set && plugin_state->current_scene != TotpSceneAuthentication &&
+ plugin_state->current_scene != TotpSceneStandby &&
furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL);
+ totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication);
}
view_port_update(view_port);
diff --git a/applications/external/totp/types/common.c b/applications/external/totp/types/common.c
new file mode 100644
index 000000000..ec5eb3ebd
--- /dev/null
+++ b/applications/external/totp/types/common.c
@@ -0,0 +1,3 @@
+#include "common.h"
+
+const char* LOGGING_TAG = "TOTP APP";
\ No newline at end of file
diff --git a/applications/external/totp/types/common.h b/applications/external/totp/types/common.h
index 2c6d6b293..737adb82d 100644
--- a/applications/external/totp/types/common.h
+++ b/applications/external/totp/types/common.h
@@ -1,3 +1,3 @@
#pragma once
-#define LOGGING_TAG "TOTP APP"
+extern const char* LOGGING_TAG;
diff --git a/applications/external/totp/types/nullable.h b/applications/external/totp/types/nullable.h
deleted file mode 100644
index 4f9b7bc01..000000000
--- a/applications/external/totp/types/nullable.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include
-#include
-
-#define TOTP_NULLABLE_STRUCT(value_type) \
- typedef struct TotpNullable_##value_type { \
- bool is_null; \
- value_type value; \
- } TotpNullable_##value_type
-
-#define TOTP_NULLABLE_NULL(s) s.is_null = true
-#define TOTP_NULLABLE_VALUE(s, v) \
- s.is_null = false; \
- s.value = v
-
-TOTP_NULLABLE_STRUCT(uint16_t);
diff --git a/applications/external/totp/types/plugin_state.h b/applications/external/totp/types/plugin_state.h
index cacf68426..c20594f37 100644
--- a/applications/external/totp/types/plugin_state.h
+++ b/applications/external/totp/types/plugin_state.h
@@ -4,8 +4,8 @@
#include
#include
#include "../features_config.h"
-#include
#include "../ui/totp_scenes_enum.h"
+#include "../services/config/config_file_context.h"
#include "notification_method.h"
#include "automation_method.h"
#ifdef TOTP_BADBT_TYPE_ENABLED
@@ -48,20 +48,7 @@ typedef struct {
*/
float timezone_offset;
- /**
- * @brief Token list head node
- */
- ListNode* tokens_list;
-
- /**
- * @brief Whether token list is loaded or not
- */
- bool token_list_loaded;
-
- /**
- * @brief Tokens list length
- */
- uint16_t tokens_count;
+ ConfigFileContext* config_file_context;
/**
* @brief Encrypted well-known string data
diff --git a/applications/external/totp/types/token_info.c b/applications/external/totp/types/token_info.c
index b8196c56b..6810d0211 100644
--- a/applications/external/totp/types/token_info.c
+++ b/applications/external/totp/types/token_info.c
@@ -1,26 +1,22 @@
#include "token_info.h"
-#include
#include
#include
#include
-#include
#include "common.h"
#include "../services/crypto/crypto.h"
TokenInfo* token_info_alloc() {
TokenInfo* tokenInfo = malloc(sizeof(TokenInfo));
furi_check(tokenInfo != NULL);
- tokenInfo->algo = SHA1;
- tokenInfo->digits = TOTP_6_DIGITS;
- tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
- tokenInfo->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+ tokenInfo->name = furi_string_alloc();
+ token_info_set_defaults(tokenInfo);
return tokenInfo;
}
void token_info_free(TokenInfo* token_info) {
if(token_info == NULL) return;
- free(token_info->name);
free(token_info->token);
+ furi_string_free(token_info->name);
free(token_info);
}
@@ -34,13 +30,13 @@ bool token_info_set_secret(
uint8_t* plain_secret;
size_t plain_secret_length;
size_t plain_secret_size;
- if(plain_token_secret_encoding == PLAIN_TOKEN_ENCODING_BASE32) {
+ if(plain_token_secret_encoding == PlainTokenSecretEncodingBase32) {
plain_secret_size = token_secret_length;
plain_secret = malloc(plain_secret_size);
furi_check(plain_secret != NULL);
plain_secret_length =
base32_decode((const uint8_t*)plain_token_secret, plain_secret, plain_secret_size);
- } else if(plain_token_secret_encoding == PLAIN_TOKEN_ENCODING_BASE64) {
+ } else if(plain_token_secret_encoding == PlainTokenSecretEncodingBase64) {
plain_secret_length = 0;
plain_secret = base64_decode(
(const uint8_t*)plain_token_secret,
@@ -54,6 +50,10 @@ bool token_info_set_secret(
bool result;
if(plain_secret_length > 0) {
+ if(token_info->token != NULL) {
+ free(token_info->token);
+ }
+
token_info->token =
totp_crypto_encrypt(plain_secret, plain_secret_length, iv, &token_info->token_length);
result = true;
@@ -69,13 +69,13 @@ bool token_info_set_secret(
bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) {
switch(digits) {
case 5:
- token_info->digits = TOTP_5_DIGITS;
+ token_info->digits = TotpFiveDigitsCount;
return true;
case 6:
- token_info->digits = TOTP_6_DIGITS;
+ token_info->digits = TotpSixDigitsCount;
return true;
case 8:
- token_info->digits = TOTP_8_DIGITS;
+ token_info->digits = TotpEightDigitsCount;
return true;
default:
break;
@@ -117,6 +117,27 @@ bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str)
return false;
}
+bool token_info_set_algo_from_int(TokenInfo* token_info, uint8_t algo_code) {
+ switch(algo_code) {
+ case SHA1:
+ token_info->algo = SHA1;
+ break;
+ case SHA256:
+ token_info->algo = SHA256;
+ break;
+ case SHA512:
+ token_info->algo = SHA512;
+ break;
+ case STEAM:
+ token_info->algo = STEAM;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
char* token_info_get_algo_as_cstr(const TokenInfo* token_info) {
switch(token_info->algo) {
case SHA1:
@@ -136,22 +157,22 @@ char* token_info_get_algo_as_cstr(const TokenInfo* token_info) {
bool token_info_set_automation_feature_from_str(TokenInfo* token_info, const FuriString* str) {
if(furi_string_cmpi_str(str, TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME) == 0) {
- token_info->automation_features |= TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END;
+ token_info->automation_features |= TokenAutomationFeatureEnterAtTheEnd;
return true;
}
if(furi_string_cmpi_str(str, TOTP_TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME) == 0) {
- token_info->automation_features |= TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END;
+ token_info->automation_features |= TokenAutomationFeatureTabAtTheEnd;
return true;
}
if(furi_string_cmpi_str(str, TOTP_TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER_NAME) == 0) {
- token_info->automation_features |= TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER;
+ token_info->automation_features |= TokenAutomationFeatureTypeSlower;
return true;
}
if(furi_string_cmpi_str(str, TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME) == 0) {
- token_info->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+ token_info->automation_features = TokenAutomationFeatureNone;
return true;
}
@@ -166,10 +187,17 @@ TokenInfo* token_info_clone(const TokenInfo* src) {
furi_check(clone->token != NULL);
memcpy(clone->token, src->token, src->token_length);
- int name_length = strnlen(src->name, TOTP_TOKEN_MAX_LENGTH);
- clone->name = malloc(name_length + 1);
- furi_check(clone->name != NULL);
- strlcpy(clone->name, src->name, name_length + 1);
+ clone->name = furi_string_alloc();
+ furi_string_set(clone->name, src->name);
return clone;
+}
+
+void token_info_set_defaults(TokenInfo* token_info) {
+ furi_check(token_info != NULL);
+ token_info->algo = SHA1;
+ token_info->digits = TotpSixDigitsCount;
+ token_info->duration = TOTP_TOKEN_DURATION_DEFAULT;
+ token_info->automation_features = TokenAutomationFeatureNone;
+ furi_string_reset(token_info->name);
}
\ No newline at end of file
diff --git a/applications/external/totp/types/token_info.h b/applications/external/totp/types/token_info.h
index 9ca3528dc..0d73dd061 100644
--- a/applications/external/totp/types/token_info.h
+++ b/applications/external/totp/types/token_info.h
@@ -2,7 +2,7 @@
#include
#include
-#include
+#include
#define TOTP_TOKEN_DURATION_DEFAULT (30)
@@ -20,6 +20,8 @@
#define TOTP_TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME "tab"
#define TOTP_TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER_NAME "slower"
+#define TOTP_TOKEN_DIGITS_MAX_COUNT (8)
+
typedef uint8_t TokenHashAlgo;
typedef uint8_t TokenDigitsCount;
typedef uint8_t TokenAutomationFeature;
@@ -32,22 +34,22 @@ enum TokenHashAlgos {
/**
* @brief SHA1 hashing algorithm
*/
- SHA1,
+ SHA1 = 0,
/**
* @brief SHA256 hashing algorithm
*/
- SHA256,
+ SHA256 = 1,
/**
* @brief SHA512 hashing algorithm
*/
- SHA512,
+ SHA512 = 2,
/**
* @brief Algorithm used by Steam (Valve)
*/
- STEAM
+ STEAM = 3
};
/**
@@ -55,19 +57,19 @@ enum TokenHashAlgos {
*/
enum TokenDigitsCounts {
/**
- * @brief 6 digits
+ * @brief 5 digits
*/
- TOTP_5_DIGITS = 5,
+ TotpFiveDigitsCount = 5,
/**
* @brief 6 digits
*/
- TOTP_6_DIGITS = 6,
+ TotpSixDigitsCount = 6,
/**
* @brief 8 digits
*/
- TOTP_8_DIGITS = 8
+ TotpEightDigitsCount = 8
};
/**
@@ -77,22 +79,22 @@ enum TokenAutomationFeatures {
/**
* @brief No features enabled
*/
- TOKEN_AUTOMATION_FEATURE_NONE = 0b000,
+ TokenAutomationFeatureNone = 0b000,
/**
* @brief Press "Enter" key at the end as a part of token input automation
*/
- TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END = 0b001,
+ TokenAutomationFeatureEnterAtTheEnd = 0b001,
/**
* @brief Press "Tab" key at the end as a part of token input automation
*/
- TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END = 0b010,
+ TokenAutomationFeatureTabAtTheEnd = 0b010,
/**
* @brief Press keys slower and wait longer between keystrokes
*/
- TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER = 0b100
+ TokenAutomationFeatureTypeSlower = 0b100
};
/**
@@ -103,16 +105,14 @@ enum PlainTokenSecretEncodings {
/**
* @brief Base32 encoding
*/
- PLAIN_TOKEN_ENCODING_BASE32 = 0,
+ PlainTokenSecretEncodingBase32 = 0,
/**
* @brief Base64 encoding
*/
- PLAIN_TOKEN_ENCODING_BASE64 = 1
+ PlainTokenSecretEncodingBase64 = 1
};
-#define TOTP_TOKEN_DIGITS_MAX_COUNT (8)
-
/**
* @brief TOTP token information
*/
@@ -130,7 +130,7 @@ typedef struct {
/**
* @brief User-friendly token name
*/
- char* name;
+ FuriString* name;
/**
* @brief Hashing algorithm
@@ -168,7 +168,7 @@ void token_info_free(TokenInfo* token_info);
/**
* @brief Encrypts & sets plain token secret to the given instance of \c TokenInfo
* @param token_info instance where secret should be updated
- * @param base32_token_secret plain token secret in Base32 format
+ * @param plain_token_secret plain token secret
* @param token_secret_length plain token secret length
* @param plain_token_secret_encoding plain token secret encoding
* @param iv initialization vecor (IV) to be used for encryption
@@ -201,10 +201,18 @@ bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration);
* @brief Sets token hashing algorithm from \c str value
* @param token_info instance whichs token hashing algorithm should be updated
* @param str desired token algorithm
- * @return \c true if token hahsing algorithm has been updated; \c false otherwise
+ * @return \c true if token hashing algorithm has been updated; \c false otherwise
*/
bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str);
+/**
+ * @brief Sets token hashing algorithm from \c algo_code code
+ * @param token_info instance whichs token hashing algorithm should be updated
+ * @param algo_code desired token algorithm code
+ * @return \c true if token hashing algorithm has been updated; \c false otherwise
+ */
+bool token_info_set_algo_from_int(TokenInfo* token_info, uint8_t algo_code);
+
/**
* @brief Gets token hahsing algorithm name as C-string
* @param token_info instance which token hahsing algorithm name should be returned
@@ -225,4 +233,10 @@ bool token_info_set_automation_feature_from_str(TokenInfo* token_info, const Fur
* @param src instance to clone
* @return cloned instance
*/
-TokenInfo* token_info_clone(const TokenInfo* src);
\ No newline at end of file
+TokenInfo* token_info_clone(const TokenInfo* src);
+
+/**
+ * @brief Sets default values to all the properties of \c token_info
+ * @param token_info instance to set defaults to
+ */
+void token_info_set_defaults(TokenInfo* token_info);
diff --git a/applications/external/totp/ui/scene_director.c b/applications/external/totp/ui/scene_director.c
index c6f709006..657762a94 100644
--- a/applications/external/totp/ui/scene_director.c
+++ b/applications/external/totp/ui/scene_director.c
@@ -5,29 +5,28 @@
#include "scenes/add_new_token/totp_scene_add_new_token.h"
#include "scenes/token_menu/totp_scene_token_menu.h"
#include "scenes/app_settings/totp_app_settings.h"
+#include "scenes/standby/standby.h"
-void totp_scene_director_activate_scene(
- PluginState* const plugin_state,
- Scene scene,
- const void* context) {
+void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene) {
totp_scene_director_deactivate_active_scene(plugin_state);
switch(scene) {
case TotpSceneGenerateToken:
- totp_scene_generate_token_activate(plugin_state, context);
+ totp_scene_generate_token_activate(plugin_state);
break;
case TotpSceneAuthentication:
totp_scene_authenticate_activate(plugin_state);
break;
case TotpSceneAddNewToken:
- totp_scene_add_new_token_activate(plugin_state, context);
+ totp_scene_add_new_token_activate(plugin_state);
break;
case TotpSceneTokenMenu:
- totp_scene_token_menu_activate(plugin_state, context);
+ totp_scene_token_menu_activate(plugin_state);
break;
case TotpSceneAppSettings:
- totp_scene_app_settings_activate(plugin_state, context);
+ totp_scene_app_settings_activate(plugin_state);
break;
case TotpSceneNone:
+ case TotpSceneStandby:
break;
default:
break;
@@ -56,6 +55,7 @@ void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state
totp_scene_app_settings_deactivate(plugin_state);
break;
case TotpSceneNone:
+ case TotpSceneStandby:
break;
default:
break;
@@ -81,6 +81,9 @@ void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_
break;
case TotpSceneNone:
break;
+ case TotpSceneStandby:
+ totp_scene_standby_render(canvas);
+ break;
default:
break;
}
@@ -105,6 +108,7 @@ bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* con
processing = totp_scene_app_settings_handle_event(event, plugin_state);
break;
case TotpSceneNone:
+ case TotpSceneStandby:
break;
default:
break;
diff --git a/applications/external/totp/ui/scene_director.h b/applications/external/totp/ui/scene_director.h
index 71709978f..e45223997 100644
--- a/applications/external/totp/ui/scene_director.h
+++ b/applications/external/totp/ui/scene_director.h
@@ -11,10 +11,7 @@
* @param scene scene to be activated
* @param context scene context to be passed to the scene activation method
*/
-void totp_scene_director_activate_scene(
- PluginState* const plugin_state,
- Scene scene,
- const void* context);
+void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene);
/**
* @brief Deactivate current scene
diff --git a/applications/external/totp/ui/scenes/add_new_token/totp_input_text.c b/applications/external/totp/ui/scenes/add_new_token/totp_input_text.c
index 6956ec1ad..bbe0b7726 100644
--- a/applications/external/totp/ui/scenes/add_new_token/totp_input_text.c
+++ b/applications/external/totp/ui/scenes/add_new_token/totp_input_text.c
@@ -1,6 +1,5 @@
#include "totp_input_text.h"
#include
-#include "../../../lib/polyfills/strnlen.h"
void view_draw(View* view, Canvas* canvas) {
furi_assert(view);
diff --git a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c
index 3f8e4fd93..d525e3399 100644
--- a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c
+++ b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c
@@ -4,17 +4,18 @@
#include "../../scene_director.h"
#include "totp_input_text.h"
#include "../../../types/token_info.h"
-#include
#include "../../../services/config/config.h"
#include "../../ui_controls.h"
#include "../../common_dialogs.h"
#include
-#include "../../../types/nullable.h"
#include "../generate_token/totp_scene_generate_token.h"
char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512", "Steam"};
char* TOKEN_DIGITS_TEXT_LIST[] = {"5 digits", "6 digits", "8 digits"};
-TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = {TOTP_5_DIGITS, TOTP_6_DIGITS, TOTP_8_DIGITS};
+TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = {
+ TotpFiveDigitsCount,
+ TotpSixDigitsCount,
+ TotpEightDigitsCount};
typedef enum {
TokenNameTextBox,
@@ -36,7 +37,6 @@ typedef struct {
InputTextSceneContext* token_secret_input_context;
InputTextSceneState* input_state;
uint32_t input_started_at;
- TotpNullable_uint16_t current_token_index;
int16_t screen_y_offset;
TokenHashAlgo algo;
uint8_t digits_count_index;
@@ -44,6 +44,13 @@ typedef struct {
FuriString* duration_text;
} SceneState;
+struct TotpAddContext {
+ SceneState* scene_state;
+ uint8_t* iv;
+};
+
+enum TotpIteratorUpdateTokenResultsEx { TotpIteratorUpdateTokenResultInvalidSecret = 1 };
+
static void on_token_name_user_comitted(InputTextSceneCallbackResult* result) {
SceneState* scene_state = result->callback_data;
free(scene_state->token_name);
@@ -66,9 +73,29 @@ static void update_duration_text(SceneState* scene_state) {
furi_string_printf(scene_state->duration_text, "%d sec.", scene_state->duration);
}
-void totp_scene_add_new_token_activate(
- PluginState* plugin_state,
- const TokenAddEditSceneContext* context) {
+static TotpIteratorUpdateTokenResult add_token_handler(TokenInfo* tokenInfo, const void* context) {
+ const struct TotpAddContext* context_t = context;
+ if(!token_info_set_secret(
+ tokenInfo,
+ context_t->scene_state->token_secret,
+ context_t->scene_state->token_secret_length,
+ PlainTokenSecretEncodingBase32,
+ context_t->iv)) {
+ return TotpIteratorUpdateTokenResultInvalidSecret;
+ }
+
+ furi_string_set_strn(
+ tokenInfo->name,
+ context_t->scene_state->token_name,
+ context_t->scene_state->token_name_length + 1);
+ tokenInfo->algo = context_t->scene_state->algo;
+ tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[context_t->scene_state->digits_count_index];
+ tokenInfo->duration = context_t->scene_state->duration;
+
+ return TotpIteratorUpdateTokenResultSuccess;
+}
+
+void totp_scene_add_new_token_activate(PluginState* plugin_state) {
SceneState* scene_state = malloc(sizeof(SceneState));
furi_check(scene_state != NULL);
plugin_state->current_scene_state = scene_state;
@@ -97,12 +124,6 @@ void totp_scene_add_new_token_activate(
scene_state->duration = TOTP_TOKEN_DURATION_DEFAULT;
scene_state->duration_text = furi_string_alloc();
update_duration_text(scene_state);
-
- if(context == NULL) {
- TOTP_NULLABLE_NULL(scene_state->current_token_index);
- } else {
- TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
- }
}
void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state) {
@@ -260,38 +281,16 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
case TokenDurationSelect:
break;
case ConfirmButton: {
- TokenInfo* tokenInfo = token_info_alloc();
- bool token_secret_set = token_info_set_secret(
- tokenInfo,
- scene_state->token_secret,
- scene_state->token_secret_length,
- PLAIN_TOKEN_ENCODING_BASE32,
- &plugin_state->iv[0]);
+ struct TotpAddContext add_context = {
+ .iv = plugin_state->iv, .scene_state = scene_state};
+ TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ TotpIteratorUpdateTokenResult add_result = totp_token_info_iterator_add_new_token(
+ iterator_context, &add_token_handler, &add_context);
- if(token_secret_set) {
- tokenInfo->name = malloc(scene_state->token_name_length + 1);
- furi_check(tokenInfo->name != NULL);
- strlcpy(
- tokenInfo->name, scene_state->token_name, scene_state->token_name_length + 1);
- tokenInfo->algo = scene_state->algo;
- tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[scene_state->digits_count_index];
- tokenInfo->duration = scene_state->duration;
-
- TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
- plugin_state->tokens_count++;
-
- if(totp_config_file_save_new_token(tokenInfo) != TotpConfigFileUpdateSuccess) {
- token_info_free(tokenInfo);
- totp_dialogs_config_updating_error(plugin_state);
- return false;
- }
-
- GenerateTokenSceneContext generate_scene_context = {
- .current_token_index = plugin_state->tokens_count - 1};
- totp_scene_director_activate_scene(
- plugin_state, TotpSceneGenerateToken, &generate_scene_context);
- } else {
- token_info_free(tokenInfo);
+ if(add_result == TotpIteratorUpdateTokenResultSuccess) {
+ totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
+ } else if(add_result == TotpIteratorUpdateTokenResultInvalidSecret) {
DialogMessage* message = dialog_message_alloc();
dialog_message_set_buttons(message, "Back", NULL, NULL);
dialog_message_set_text(
@@ -305,7 +304,10 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
dialog_message_free(message);
scene_state->selected_control = TokenSecretTextBox;
update_screen_y_offset(scene_state);
+ } else if(add_result == TotpIteratorUpdateTokenResultFileUpdateFailed) {
+ totp_dialogs_config_updating_error(plugin_state);
}
+
break;
}
default:
@@ -313,14 +315,7 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
}
break;
case InputKeyBack:
- if(!scene_state->current_token_index.is_null) {
- GenerateTokenSceneContext generate_scene_context = {
- .current_token_index = scene_state->current_token_index.value};
- totp_scene_director_activate_scene(
- plugin_state, TotpSceneGenerateToken, &generate_scene_context);
- } else {
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
- }
+ totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
break;
default:
break;
diff --git a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h
index 07098111a..dd6b32994 100644
--- a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h
+++ b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h
@@ -1,18 +1,10 @@
#pragma once
#include
-#include
-#include
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
-typedef struct {
- uint16_t current_token_index;
-} TokenAddEditSceneContext;
-
-void totp_scene_add_new_token_activate(
- PluginState* plugin_state,
- const TokenAddEditSceneContext* context);
+void totp_scene_add_new_token_activate(PluginState* plugin_state);
void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state);
bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState* plugin_state);
void totp_scene_add_new_token_deactivate(PluginState* plugin_state);
diff --git a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c
index d2cf629d2..6dcf0dbc9 100644
--- a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c
+++ b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c
@@ -9,7 +9,6 @@
#include "../../../services/config/config.h"
#include "../../../services/convert/convert.h"
#include
-#include "../../../types/nullable.h"
#include "../../../features_config.h"
#ifdef TOTP_BADBT_TYPE_ENABLED
#include "../../../workers/bt_type_code/bt_type_code.h"
@@ -40,21 +39,13 @@ typedef struct {
bool badbt_enabled;
#endif
uint8_t y_offset;
- TotpNullable_uint16_t current_token_index;
Control selected_control;
} SceneState;
-void totp_scene_app_settings_activate(
- PluginState* plugin_state,
- const AppSettingsSceneContext* context) {
+void totp_scene_app_settings_activate(PluginState* plugin_state) {
SceneState* scene_state = malloc(sizeof(SceneState));
furi_check(scene_state != NULL);
plugin_state->current_scene_state = scene_state;
- if(context != NULL) {
- TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
- } else {
- TOTP_NULLABLE_NULL(scene_state->current_token_index);
- }
float off_int;
float off_dec = modff(plugin_state->timezone_offset, &off_int);
@@ -281,8 +272,7 @@ bool totp_scene_app_settings_handle_event(
AutomationMethodNone;
#endif
- if(totp_config_file_update_user_settings(plugin_state) !=
- TotpConfigFileUpdateSuccess) {
+ if(!totp_config_file_update_user_settings(plugin_state)) {
totp_dialogs_config_updating_error(plugin_state);
return false;
}
@@ -294,25 +284,11 @@ bool totp_scene_app_settings_handle_event(
}
#endif
- if(!scene_state->current_token_index.is_null) {
- TokenMenuSceneContext generate_scene_context = {
- .current_token_index = scene_state->current_token_index.value};
- totp_scene_director_activate_scene(
- plugin_state, TotpSceneTokenMenu, &generate_scene_context);
- } else {
- totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
- }
+ totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu);
}
break;
case InputKeyBack: {
- if(!scene_state->current_token_index.is_null) {
- TokenMenuSceneContext generate_scene_context = {
- .current_token_index = scene_state->current_token_index.value};
- totp_scene_director_activate_scene(
- plugin_state, TotpSceneTokenMenu, &generate_scene_context);
- } else {
- totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
- }
+ totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu);
break;
}
default:
diff --git a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.h b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.h
index a0e408b00..e54aab87b 100644
--- a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.h
+++ b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.h
@@ -4,13 +4,7 @@
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
-typedef struct {
- uint16_t current_token_index;
-} AppSettingsSceneContext;
-
-void totp_scene_app_settings_activate(
- PluginState* plugin_state,
- const AppSettingsSceneContext* context);
+void totp_scene_app_settings_activate(PluginState* plugin_state);
void totp_scene_app_settings_render(Canvas* const canvas, const PluginState* plugin_state);
bool totp_scene_app_settings_handle_event(
const PluginEvent* const event,
diff --git a/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.c b/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.c
index c0a0b5744..86e1e8e2b 100644
--- a/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.c
+++ b/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.c
@@ -114,12 +114,18 @@ bool totp_scene_authenticate_handle_event(
scene_state->code_length++;
}
break;
- case InputKeyOk:
- totp_crypto_seed_iv(plugin_state, &scene_state->code_input[0], scene_state->code_length);
+ case InputKeyOk: {
+ CryptoSeedIVResult seed_result = totp_crypto_seed_iv(
+ plugin_state, &scene_state->code_input[0], scene_state->code_length);
+
+ if(seed_result & CryptoSeedIVResultFlagSuccess &&
+ seed_result & CryptoSeedIVResultFlagNewCryptoVerifyData) {
+ totp_config_file_update_crypto_signatures(plugin_state);
+ }
if(totp_crypto_verify_key(plugin_state)) {
FURI_LOG_D(LOGGING_TAG, "PIN is valid");
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+ totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
} else {
FURI_LOG_D(LOGGING_TAG, "PIN is NOT valid");
memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH);
@@ -140,6 +146,7 @@ bool totp_scene_authenticate_handle_event(
dialog_message_free(message);
}
break;
+ }
case InputKeyBack:
if(scene_state->code_length > 0) {
scene_state->code_input[scene_state->code_length - 1] = 0;
diff --git a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c
index 22e6ebc33..a316e9955 100644
--- a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c
+++ b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c
@@ -2,19 +2,16 @@
#include
#include
#include
+#include
#include "totp_scene_generate_token.h"
#include "../../../types/token_info.h"
#include "../../../types/common.h"
#include "../../constants.h"
-#include "../../../services/totp/totp.h"
#include "../../../services/config/config.h"
-#include "../../../services/crypto/crypto.h"
-#include "../../../services/convert/convert.h"
-#include "../../../lib/polyfills/memset_s.h"
-#include "../../../lib/roll_value/roll_value.h"
#include "../../scene_director.h"
#include "../token_menu/totp_scene_token_menu.h"
#include "../../../features_config.h"
+#include "../../../workers/generate_totp_code/generate_totp_code.h"
#include "../../../workers/usb_type_code/usb_type_code.h"
#ifdef TOTP_BADBT_TYPE_ENABLED
#include "../../../workers/bt_type_code/bt_type_code.h"
@@ -23,18 +20,24 @@
#define PROGRESS_BAR_MARGIN (3)
#define PROGRESS_BAR_HEIGHT (4)
-static const char* STEAM_ALGO_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY";
typedef struct {
- uint16_t current_token_index;
+ uint8_t progress_bar_x;
+ uint8_t progress_bar_width;
+ uint8_t code_total_length;
+ uint8_t code_offset_x;
+ uint8_t code_offset_x_inc;
+ uint8_t code_offset_y;
+} UiPrecalculatedDimensions;
+
+typedef struct {
char last_code[TOTP_TOKEN_DIGITS_MAX_COUNT + 1];
- bool need_token_update;
- TokenInfo* current_token;
- uint32_t last_token_gen_time;
TotpUsbTypeCodeWorkerContext* usb_type_code_worker_context;
NotificationMessage const** notification_sequence_new_token;
- NotificationMessage const** notification_sequence_badusb;
+ NotificationMessage const** notification_sequence_automation;
FuriMutex* last_code_update_sync;
+ TotpGenerateCodeWorkerContext* generate_code_worker_context;
+ UiPrecalculatedDimensions ui_precalculated_dimensions;
} SceneState;
static const NotificationSequence*
@@ -80,7 +83,7 @@ static const NotificationSequence*
static const NotificationSequence*
get_notification_sequence_automation(const PluginState* plugin_state, SceneState* scene_state) {
- if(scene_state->notification_sequence_badusb == NULL) {
+ if(scene_state->notification_sequence_automation == NULL) {
uint8_t i = 0;
uint8_t length = 3;
if(plugin_state->notification_method & NotificationMethodVibro) {
@@ -91,155 +94,123 @@ static const NotificationSequence*
length += 6;
}
- scene_state->notification_sequence_badusb = malloc(sizeof(void*) * length);
- furi_check(scene_state->notification_sequence_badusb != NULL);
+ scene_state->notification_sequence_automation = malloc(sizeof(void*) * length);
+ furi_check(scene_state->notification_sequence_automation != NULL);
- scene_state->notification_sequence_badusb[i++] = &message_blue_255;
+ scene_state->notification_sequence_automation[i++] = &message_blue_255;
if(plugin_state->notification_method & NotificationMethodVibro) {
- scene_state->notification_sequence_badusb[i++] = &message_vibro_on;
+ scene_state->notification_sequence_automation[i++] = &message_vibro_on;
}
if(plugin_state->notification_method & NotificationMethodSound) {
- scene_state->notification_sequence_badusb[i++] = &message_note_d5; //-V525
- scene_state->notification_sequence_badusb[i++] = &message_delay_50;
- scene_state->notification_sequence_badusb[i++] = &message_note_e4;
- scene_state->notification_sequence_badusb[i++] = &message_delay_50;
- scene_state->notification_sequence_badusb[i++] = &message_note_f3;
+ scene_state->notification_sequence_automation[i++] = &message_note_d5; //-V525
+ scene_state->notification_sequence_automation[i++] = &message_delay_50;
+ scene_state->notification_sequence_automation[i++] = &message_note_e4;
+ scene_state->notification_sequence_automation[i++] = &message_delay_50;
+ scene_state->notification_sequence_automation[i++] = &message_note_f3;
}
- scene_state->notification_sequence_badusb[i++] = &message_delay_50;
+ scene_state->notification_sequence_automation[i++] = &message_delay_50;
if(plugin_state->notification_method & NotificationMethodVibro) {
- scene_state->notification_sequence_badusb[i++] = &message_vibro_off;
+ scene_state->notification_sequence_automation[i++] = &message_vibro_off;
}
if(plugin_state->notification_method & NotificationMethodSound) {
- scene_state->notification_sequence_badusb[i++] = &message_sound_off;
+ scene_state->notification_sequence_automation[i++] = &message_sound_off;
}
- scene_state->notification_sequence_badusb[i++] = NULL;
+ scene_state->notification_sequence_automation[i++] = NULL;
}
- return (NotificationSequence*)scene_state->notification_sequence_badusb;
+ return (NotificationSequence*)scene_state->notification_sequence_automation;
}
-static void
- int_token_to_str(uint64_t i_token_code, char* str, TokenDigitsCount len, TokenHashAlgo algo) {
- if(i_token_code == OTP_ERROR) {
- memset(&str[0], '-', len);
- } else {
- if(algo == STEAM) {
- for(uint8_t i = 0; i < len; i++) {
- str[i] = STEAM_ALGO_ALPHABET[i_token_code % 26];
- i_token_code = i_token_code / 26;
- }
- } else {
- for(int8_t i = len - 1; i >= 0; i--) {
- str[i] = CONVERT_DIGIT_TO_CHAR(i_token_code % 10);
- i_token_code = i_token_code / 10;
- }
- }
- }
-
- str[len] = '\0';
-}
-
-static TOTP_ALGO get_totp_algo_impl(TokenHashAlgo algo) {
- switch(algo) {
- case SHA1:
- case STEAM:
- return TOTP_ALGO_SHA1;
- case SHA256:
- return TOTP_ALGO_SHA256;
- case SHA512:
- return TOTP_ALGO_SHA512;
- default:
- break;
- }
-
- return NULL;
-}
-
-static void update_totp_params(PluginState* const plugin_state) {
+static void update_totp_params(PluginState* const plugin_state, size_t token_index) {
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
-
- if(scene_state->current_token_index < plugin_state->tokens_count) {
- TokenInfo* tokenInfo =
- list_element_at(plugin_state->tokens_list, scene_state->current_token_index)->data;
-
- scene_state->need_token_update = true;
- scene_state->current_token = tokenInfo;
+ TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ if(totp_token_info_iterator_go_to(iterator_context, token_index)) {
+ totp_generate_code_worker_notify(
+ scene_state->generate_code_worker_context, TotpGenerateCodeWorkerEventForceUpdate);
}
}
-static void draw_totp_code(Canvas* const canvas, const SceneState* const scene_state) {
- uint8_t code_length = scene_state->current_token->digits;
+static void draw_totp_code(Canvas* const canvas, const PluginState* const plugin_state) {
+ const SceneState* scene_state = plugin_state->current_scene_state;
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ uint8_t code_length = totp_token_info_iterator_get_current_token(iterator_context)->digits;
+ uint8_t offset_x = scene_state->ui_precalculated_dimensions.code_offset_x;
uint8_t char_width = modeNine_15ptFontInfo.charInfo[0].width;
- uint8_t total_length = code_length * (char_width + modeNine_15ptFontInfo.spacePixels);
- uint8_t offset_x = (SCREEN_WIDTH - total_length) >> 1;
- uint8_t offset_x_inc = char_width + modeNine_15ptFontInfo.spacePixels;
- uint8_t offset_y = SCREEN_HEIGHT_CENTER - (modeNine_15ptFontInfo.height >> 1);
+ uint8_t offset_x_inc = scene_state->ui_precalculated_dimensions.code_offset_x_inc;
for(uint8_t i = 0; i < code_length; i++) {
char ch = scene_state->last_code[i];
- uint8_t char_index = ch - modeNine_15ptFontInfo.startChar;
- canvas_draw_xbm(
- canvas,
- offset_x,
- offset_y,
- char_width,
- modeNine_15ptFontInfo.height,
- &modeNine_15ptFontInfo.data[modeNine_15ptFontInfo.charInfo[char_index].offset]);
+ if(ch >= modeNine_15ptFontInfo.startChar && ch <= modeNine_15ptFontInfo.endChar) {
+ uint8_t char_index = ch - modeNine_15ptFontInfo.startChar;
+ canvas_draw_xbm(
+ canvas,
+ offset_x,
+ scene_state->ui_precalculated_dimensions.code_offset_y,
+ char_width,
+ modeNine_15ptFontInfo.height,
+ &modeNine_15ptFontInfo.data[modeNine_15ptFontInfo.charInfo[char_index].offset]);
+ }
offset_x += offset_x_inc;
}
}
-void totp_scene_generate_token_activate(
- PluginState* plugin_state,
- const GenerateTokenSceneContext* context) {
- if(!plugin_state->token_list_loaded) {
- TokenLoadingResult token_load_result = totp_config_file_load_tokens(plugin_state);
- if(token_load_result != TokenLoadingResultSuccess) {
- DialogMessage* message = dialog_message_alloc();
- dialog_message_set_buttons(message, NULL, "Okay", NULL);
- if(token_load_result == TokenLoadingResultWarning) {
- dialog_message_set_text(
- message,
- "Unable to load some tokens\nPlease review conf file",
- SCREEN_WIDTH_CENTER,
- SCREEN_HEIGHT_CENTER,
- AlignCenter,
- AlignCenter);
- } else if(token_load_result == TokenLoadingResultError) {
- dialog_message_set_text(
- message,
- "Unable to load tokens\nPlease review conf file",
- SCREEN_WIDTH_CENTER,
- SCREEN_HEIGHT_CENTER,
- AlignCenter,
- AlignCenter);
- }
-
- dialog_message_show(plugin_state->dialogs_app, message);
- dialog_message_free(message);
- }
+static void on_new_token_code_generated(bool time_left, void* context) {
+ const PluginState* plugin_state = context;
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ if(totp_token_info_iterator_get_total_count(iterator_context) == 0) {
+ return;
}
+
+ SceneState* scene_state = plugin_state->current_scene_state;
+ const TokenInfo* current_token = totp_token_info_iterator_get_current_token(iterator_context);
+
+ uint8_t char_width = modeNine_15ptFontInfo.charInfo[0].width;
+ scene_state->ui_precalculated_dimensions.code_total_length =
+ current_token->digits * (char_width + modeNine_15ptFontInfo.spacePixels);
+ scene_state->ui_precalculated_dimensions.code_offset_x =
+ (SCREEN_WIDTH - scene_state->ui_precalculated_dimensions.code_total_length) >> 1;
+ scene_state->ui_precalculated_dimensions.code_offset_x_inc =
+ char_width + modeNine_15ptFontInfo.spacePixels;
+ scene_state->ui_precalculated_dimensions.code_offset_y =
+ SCREEN_HEIGHT_CENTER - (modeNine_15ptFontInfo.height >> 1);
+
+ if(time_left) {
+ notification_message(
+ plugin_state->notification_app,
+ get_notification_sequence_new_token(plugin_state, plugin_state->current_scene_state));
+ }
+}
+
+static void on_code_lifetime_updated_generated(float code_lifetime_percent, void* context) {
+ SceneState* scene_state = context;
+ scene_state->ui_precalculated_dimensions.progress_bar_width =
+ (uint8_t)((float)(SCREEN_WIDTH - (PROGRESS_BAR_MARGIN << 1)) * code_lifetime_percent);
+ scene_state->ui_precalculated_dimensions.progress_bar_x =
+ ((SCREEN_WIDTH - (PROGRESS_BAR_MARGIN << 1) -
+ scene_state->ui_precalculated_dimensions.progress_bar_width) >>
+ 1) +
+ PROGRESS_BAR_MARGIN;
+}
+
+void totp_scene_generate_token_activate(PluginState* plugin_state) {
SceneState* scene_state = malloc(sizeof(SceneState));
furi_check(scene_state != NULL);
- if(context == NULL || context->current_token_index > plugin_state->tokens_count) {
- scene_state->current_token_index = 0;
- } else {
- scene_state->current_token_index = context->current_token_index;
- }
- scene_state->need_token_update = true;
+
plugin_state->current_scene_state = scene_state;
FURI_LOG_D(LOGGING_TAG, "Timezone set to: %f", (double)plugin_state->timezone_offset);
- update_totp_params(plugin_state);
scene_state->last_code_update_sync = furi_mutex_alloc(FuriMutexTypeNormal);
if(plugin_state->automation_method & AutomationMethodBadUsb) {
scene_state->usb_type_code_worker_context = totp_usb_type_code_worker_start(
- &scene_state->last_code[0],
+ scene_state->last_code,
TOTP_TOKEN_DIGITS_MAX_COUNT + 1,
scene_state->last_code_update_sync);
}
@@ -252,15 +223,36 @@ void totp_scene_generate_token_activate(
}
totp_bt_type_code_worker_start(
plugin_state->bt_type_code_worker_context,
- &scene_state->last_code[0],
+ scene_state->last_code,
TOTP_TOKEN_DIGITS_MAX_COUNT + 1,
scene_state->last_code_update_sync);
}
#endif
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ scene_state->generate_code_worker_context = totp_generate_code_worker_start(
+ scene_state->last_code,
+ totp_token_info_iterator_get_current_token(iterator_context),
+ scene_state->last_code_update_sync,
+ plugin_state->timezone_offset,
+ plugin_state->iv);
+
+ totp_generate_code_worker_set_code_generated_handler(
+ scene_state->generate_code_worker_context, &on_new_token_code_generated, plugin_state);
+
+ totp_generate_code_worker_set_lifetime_changed_handler(
+ scene_state->generate_code_worker_context,
+ &on_code_lifetime_updated_generated,
+ scene_state);
+
+ update_totp_params(
+ plugin_state, totp_token_info_iterator_get_current_token_index(iterator_context));
}
void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state) {
- if(plugin_state->tokens_count == 0) {
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ if(totp_token_info_iterator_get_total_count(iterator_context) == 0) {
canvas_draw_str_aligned(
canvas,
SCREEN_WIDTH_CENTER,
@@ -278,57 +270,12 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
return;
}
- SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
- FuriHalRtcDateTime curr_dt;
- furi_hal_rtc_get_datetime(&curr_dt);
- uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
-
- bool is_new_token_time = curr_ts % scene_state->current_token->duration == 0;
- if(is_new_token_time && scene_state->last_token_gen_time != curr_ts) {
- scene_state->need_token_update = true;
- }
-
- if(scene_state->need_token_update) {
- scene_state->need_token_update = false;
- scene_state->last_token_gen_time = curr_ts;
-
- const TokenInfo* tokenInfo = scene_state->current_token;
-
- if(tokenInfo->token != NULL && tokenInfo->token_length > 0) {
- furi_mutex_acquire(scene_state->last_code_update_sync, FuriWaitForever);
- size_t key_length;
- uint8_t* key = totp_crypto_decrypt(
- tokenInfo->token, tokenInfo->token_length, &plugin_state->iv[0], &key_length);
-
- int_token_to_str(
- totp_at(
- get_totp_algo_impl(tokenInfo->algo),
- key,
- key_length,
- curr_ts,
- plugin_state->timezone_offset,
- tokenInfo->duration),
- scene_state->last_code,
- tokenInfo->digits,
- tokenInfo->algo);
- memset_s(key, key_length, 0, key_length);
- free(key);
- } else {
- furi_mutex_acquire(scene_state->last_code_update_sync, FuriWaitForever);
- int_token_to_str(0, scene_state->last_code, tokenInfo->digits, tokenInfo->algo);
- }
-
- furi_mutex_release(scene_state->last_code_update_sync);
-
- if(is_new_token_time) {
- notification_message(
- plugin_state->notification_app,
- get_notification_sequence_new_token(plugin_state, scene_state));
- }
- }
+ const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
canvas_set_font(canvas, FontPrimary);
- uint16_t token_name_width = canvas_string_width(canvas, scene_state->current_token->name);
+ const char* token_name_cstr =
+ furi_string_get_cstr(totp_token_info_iterator_get_current_token(iterator_context)->name);
+ uint16_t token_name_width = canvas_string_width(canvas, token_name_cstr);
if(SCREEN_WIDTH - token_name_width > 18) {
canvas_draw_str_aligned(
canvas,
@@ -336,40 +283,28 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
SCREEN_HEIGHT_CENTER - 20,
AlignCenter,
AlignCenter,
- scene_state->current_token->name);
+ token_name_cstr);
} else {
canvas_draw_str_aligned(
- canvas,
- 9,
- SCREEN_HEIGHT_CENTER - 20,
- AlignLeft,
- AlignCenter,
- scene_state->current_token->name);
+ canvas, 9, SCREEN_HEIGHT_CENTER - 20, AlignLeft, AlignCenter, token_name_cstr);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 9, 9);
canvas_draw_box(canvas, SCREEN_WIDTH - 10, SCREEN_HEIGHT_CENTER - 24, 9, 9);
canvas_set_color(canvas, ColorBlack);
}
- draw_totp_code(canvas, scene_state);
-
- const uint8_t TOKEN_LIFETIME = scene_state->current_token->duration;
- float percentDone = (float)(TOKEN_LIFETIME - curr_ts % TOKEN_LIFETIME) / (float)TOKEN_LIFETIME;
- uint8_t barWidth = (uint8_t)((float)(SCREEN_WIDTH - (PROGRESS_BAR_MARGIN << 1)) * percentDone);
- uint8_t barX =
- ((SCREEN_WIDTH - (PROGRESS_BAR_MARGIN << 1) - barWidth) >> 1) + PROGRESS_BAR_MARGIN;
+ draw_totp_code(canvas, plugin_state);
canvas_draw_box(
canvas,
- barX,
+ scene_state->ui_precalculated_dimensions.progress_bar_x,
SCREEN_HEIGHT - PROGRESS_BAR_MARGIN - PROGRESS_BAR_HEIGHT,
- barWidth,
+ scene_state->ui_precalculated_dimensions.progress_bar_width,
PROGRESS_BAR_HEIGHT);
-
- if(plugin_state->tokens_count > 1) {
+ if(totp_token_info_iterator_get_total_count(iterator_context) > 1) {
canvas_draw_icon(canvas, 0, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_left_8x9);
canvas_draw_icon(
- canvas, SCREEN_WIDTH - 9, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9);
+ canvas, SCREEN_WIDTH - 8, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9);
}
#ifdef TOTP_AUTOMATION_ICONS_ENABLED
@@ -390,7 +325,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
#ifdef TOTP_BADBT_TYPE_ENABLED
if(plugin_state->automation_method & AutomationMethodBadBt &&
plugin_state->bt_type_code_worker_context != NULL &&
- plugin_state->bt_type_code_worker_context->is_advertising) {
+ totp_bt_type_code_worker_is_advertising(plugin_state->bt_type_code_worker_context)) {
canvas_draw_icon(
canvas,
SCREEN_WIDTH_CENTER +
@@ -418,10 +353,12 @@ bool totp_scene_generate_token_handle_event(
if(event->input.key == InputKeyDown &&
plugin_state->automation_method & AutomationMethodBadUsb) {
scene_state = (SceneState*)plugin_state->current_scene_state;
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
totp_usb_type_code_worker_notify(
scene_state->usb_type_code_worker_context,
TotpUsbTypeCodeWorkerEventType,
- scene_state->current_token->automation_features);
+ totp_token_info_iterator_get_current_token(iterator_context)->automation_features);
notification_message(
plugin_state->notification_app,
get_notification_sequence_automation(plugin_state, scene_state));
@@ -432,10 +369,12 @@ bool totp_scene_generate_token_handle_event(
event->input.key == InputKeyUp &&
plugin_state->automation_method & AutomationMethodBadBt) {
scene_state = (SceneState*)plugin_state->current_scene_state;
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
totp_bt_type_code_worker_notify(
plugin_state->bt_type_code_worker_context,
TotpBtTypeCodeWorkerEventType,
- scene_state->current_token->automation_features);
+ totp_token_info_iterator_get_current_token(iterator_context)->automation_features);
notification_message(
plugin_state->notification_app,
get_notification_sequence_automation(plugin_state, scene_state));
@@ -448,37 +387,43 @@ bool totp_scene_generate_token_handle_event(
return true;
}
- scene_state = (SceneState*)plugin_state->current_scene_state;
switch(event->input.key) {
case InputKeyUp:
break;
case InputKeyDown:
break;
- case InputKeyRight:
- totp_roll_value_uint16_t(
- &scene_state->current_token_index,
+ case InputKeyRight: {
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ size_t current_token_index =
+ totp_token_info_iterator_get_current_token_index(iterator_context);
+ totp_roll_value_size_t(
+ ¤t_token_index,
1,
0,
- plugin_state->tokens_count - 1,
+ totp_token_info_iterator_get_total_count(iterator_context) - 1,
RollOverflowBehaviorRoll);
- update_totp_params(plugin_state);
+
+ update_totp_params(plugin_state, current_token_index);
break;
- case InputKeyLeft:
- totp_roll_value_uint16_t(
- &scene_state->current_token_index,
+ }
+ case InputKeyLeft: {
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ size_t current_token_index =
+ totp_token_info_iterator_get_current_token_index(iterator_context);
+ totp_roll_value_size_t(
+ ¤t_token_index,
-1,
0,
- plugin_state->tokens_count - 1,
+ totp_token_info_iterator_get_total_count(iterator_context) - 1,
RollOverflowBehaviorRoll);
- update_totp_params(plugin_state);
+
+ update_totp_params(plugin_state, current_token_index);
break;
+ }
case InputKeyOk:
- if(plugin_state->tokens_count == 0) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
- } else {
- TokenMenuSceneContext ctx = {.current_token_index = scene_state->current_token_index};
- totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, &ctx);
- }
+ totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu);
break;
case InputKeyBack:
break;
@@ -493,6 +438,8 @@ void totp_scene_generate_token_deactivate(PluginState* plugin_state) {
if(plugin_state->current_scene_state == NULL) return;
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
+ totp_generate_code_worker_stop(scene_state->generate_code_worker_context);
+
if(plugin_state->automation_method & AutomationMethodBadUsb) {
totp_usb_type_code_worker_stop(scene_state->usb_type_code_worker_context);
}
@@ -506,8 +453,8 @@ void totp_scene_generate_token_deactivate(PluginState* plugin_state) {
free(scene_state->notification_sequence_new_token);
}
- if(scene_state->notification_sequence_badusb != NULL) {
- free(scene_state->notification_sequence_badusb);
+ if(scene_state->notification_sequence_automation != NULL) {
+ free(scene_state->notification_sequence_automation);
}
furi_mutex_free(scene_state->last_code_update_sync);
diff --git a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.h b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.h
index e183f53d2..3f7bc0408 100644
--- a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.h
+++ b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.h
@@ -4,13 +4,7 @@
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
-typedef struct {
- uint16_t current_token_index;
-} GenerateTokenSceneContext;
-
-void totp_scene_generate_token_activate(
- PluginState* plugin_state,
- const GenerateTokenSceneContext* context);
+void totp_scene_generate_token_activate(PluginState* plugin_state);
void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state);
bool totp_scene_generate_token_handle_event(
const PluginEvent* const event,
diff --git a/applications/external/totp/ui/scenes/standby/standby.c b/applications/external/totp/ui/scenes/standby/standby.c
new file mode 100644
index 000000000..5cd6bae6a
--- /dev/null
+++ b/applications/external/totp/ui/scenes/standby/standby.c
@@ -0,0 +1,12 @@
+#include "standby.h"
+#include
+#include "../../constants.h"
+
+void totp_scene_standby_render(Canvas* const canvas) {
+ canvas_draw_icon(canvas, SCREEN_WIDTH - 56, SCREEN_HEIGHT - 48, &I_DolphinCommon_56x48);
+
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str_aligned(canvas, 5, 10, AlignLeft, AlignTop, "CLI command");
+
+ canvas_draw_str_aligned(canvas, 5, 24, AlignLeft, AlignTop, "is running now");
+}
\ No newline at end of file
diff --git a/applications/external/totp/ui/scenes/standby/standby.h b/applications/external/totp/ui/scenes/standby/standby.h
new file mode 100644
index 000000000..78e2b0915
--- /dev/null
+++ b/applications/external/totp/ui/scenes/standby/standby.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include
+
+void totp_scene_standby_render(Canvas* const canvas);
\ No newline at end of file
diff --git a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c
index 7b00f0a1b..a8c8de28a 100644
--- a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c
+++ b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c
@@ -6,12 +6,10 @@
#include "../../constants.h"
#include "../../scene_director.h"
#include "../../../services/config/config.h"
-#include
#include "../../../types/token_info.h"
#include "../generate_token/totp_scene_generate_token.h"
#include "../add_new_token/totp_scene_add_new_token.h"
#include "../app_settings/totp_app_settings.h"
-#include "../../../types/nullable.h"
#include
#define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3)
@@ -21,25 +19,19 @@ typedef enum { AddNewToken, DeleteToken, AppSettings } Control;
typedef struct {
Control selected_control;
- TotpNullable_uint16_t current_token_index;
} SceneState;
-void totp_scene_token_menu_activate(
- PluginState* plugin_state,
- const TokenMenuSceneContext* context) {
+void totp_scene_token_menu_activate(PluginState* plugin_state) {
SceneState* scene_state = malloc(sizeof(SceneState));
furi_check(scene_state != NULL);
plugin_state->current_scene_state = scene_state;
- if(context != NULL) {
- TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
- } else {
- TOTP_NULLABLE_NULL(scene_state->current_token_index);
- }
}
void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) {
const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
- if(scene_state->current_token_index.is_null) {
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
+ if(totp_token_info_iterator_get_total_count(iterator_context) == 0) {
ui_control_button_render(
canvas,
SCREEN_WIDTH_CENTER - 36,
@@ -95,22 +87,28 @@ bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginSt
}
switch(event->input.key) {
- case InputKeyUp:
+ case InputKeyUp: {
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
totp_roll_value_uint8_t(
&scene_state->selected_control, -1, AddNewToken, AppSettings, RollOverflowBehaviorRoll);
if(scene_state->selected_control == DeleteToken &&
- scene_state->current_token_index.is_null) {
+ totp_token_info_iterator_get_total_count(iterator_context) == 0) {
scene_state->selected_control--;
}
break;
- case InputKeyDown:
+ }
+ case InputKeyDown: {
+ const TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
totp_roll_value_uint8_t(
&scene_state->selected_control, 1, AddNewToken, AppSettings, RollOverflowBehaviorRoll);
if(scene_state->selected_control == DeleteToken &&
- scene_state->current_token_index.is_null) {
+ totp_token_info_iterator_get_total_count(iterator_context) == 0) {
scene_state->selected_control++;
}
break;
+ }
case InputKeyRight:
break;
case InputKeyLeft:
@@ -118,14 +116,7 @@ bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginSt
case InputKeyOk:
switch(scene_state->selected_control) {
case AddNewToken: {
- if(scene_state->current_token_index.is_null) {
- totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken, NULL);
- } else {
- TokenAddEditSceneContext add_new_token_scene_context = {
- .current_token_index = scene_state->current_token_index.value};
- totp_scene_director_activate_scene(
- plugin_state, TotpSceneAddNewToken, &add_new_token_scene_context);
- }
+ totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken);
break;
}
case DeleteToken: {
@@ -142,34 +133,21 @@ bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginSt
DialogMessageButton dialog_result =
dialog_message_show(plugin_state->dialogs_app, message);
dialog_message_free(message);
+ TokenInfoIteratorContext* iterator_context =
+ totp_config_get_token_iterator_context(plugin_state);
if(dialog_result == DialogMessageButtonRight &&
- !scene_state->current_token_index.is_null) {
- TokenInfo* tokenInfo = NULL;
- plugin_state->tokens_list = list_remove_at(
- plugin_state->tokens_list,
- scene_state->current_token_index.value,
- (void**)&tokenInfo);
- plugin_state->tokens_count--;
- furi_check(tokenInfo != NULL);
- token_info_free(tokenInfo);
-
- if(totp_full_save_config_file(plugin_state) != TotpConfigFileUpdateSuccess) {
+ totp_token_info_iterator_get_total_count(iterator_context) > 0) {
+ if(!totp_token_info_iterator_remove_current_token_info(iterator_context)) {
totp_dialogs_config_updating_error(plugin_state);
return false;
}
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+
+ totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
}
break;
}
case AppSettings: {
- if(!scene_state->current_token_index.is_null) {
- AppSettingsSceneContext app_settings_context = {
- .current_token_index = scene_state->current_token_index.value};
- totp_scene_director_activate_scene(
- plugin_state, TotpSceneAppSettings, &app_settings_context);
- } else {
- totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings, NULL);
- }
+ totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings);
break;
}
default:
@@ -177,14 +155,7 @@ bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginSt
}
break;
case InputKeyBack: {
- if(!scene_state->current_token_index.is_null) {
- GenerateTokenSceneContext generate_scene_context = {
- .current_token_index = scene_state->current_token_index.value};
- totp_scene_director_activate_scene(
- plugin_state, TotpSceneGenerateToken, &generate_scene_context);
- } else {
- totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
- }
+ totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
break;
}
default:
diff --git a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.h b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.h
index f9d4b4cbf..a715c9748 100644
--- a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.h
+++ b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.h
@@ -4,13 +4,7 @@
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
-typedef struct {
- uint16_t current_token_index;
-} TokenMenuSceneContext;
-
-void totp_scene_token_menu_activate(
- PluginState* plugin_state,
- const TokenMenuSceneContext* context);
+void totp_scene_token_menu_activate(PluginState* plugin_state);
void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state);
bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginState* plugin_state);
void totp_scene_token_menu_deactivate(PluginState* plugin_state);
diff --git a/applications/external/totp/ui/totp_scenes_enum.h b/applications/external/totp/ui/totp_scenes_enum.h
index 0c73af772..4624eddd9 100644
--- a/applications/external/totp/ui/totp_scenes_enum.h
+++ b/applications/external/totp/ui/totp_scenes_enum.h
@@ -34,5 +34,10 @@ enum Scenes {
/**
* @brief Scene where user can change application settings
*/
- TotpSceneAppSettings
+ TotpSceneAppSettings,
+
+ /**
+ * @brief Scene which informs user that CLI command is running
+ */
+ TotpSceneStandby
};
diff --git a/applications/external/totp/workers/bt_type_code/bt_type_code.c b/applications/external/totp/workers/bt_type_code/bt_type_code.c
index ec4c1619d..bf55dba6a 100644
--- a/applications/external/totp/workers/bt_type_code/bt_type_code.c
+++ b/applications/external/totp/workers/bt_type_code/bt_type_code.c
@@ -1,13 +1,40 @@
#include "bt_type_code.h"
#include
+#include
#include
+#include
+#include
+#include
+#include
+#include
#include
#include "../../types/common.h"
#include "../../types/token_info.h"
-#include "../common.h"
+#include "../type_code_common.h"
+#include "../../features_config.h"
+
+#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
+#define TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH
+#define TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE
+#endif
#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("authenticator/.bt_hid.keys")
+struct TotpBtTypeCodeWorkerContext {
+ char* code_buffer;
+ uint8_t code_buffer_size;
+ uint8_t flags;
+ FuriThread* thread;
+ FuriMutex* code_buffer_sync;
+ Bt* bt;
+ bool is_advertising;
+ bool is_connected;
+#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
+ char previous_bt_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN];
+ uint8_t previous_bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN];
+#endif
+};
+
static inline bool totp_type_code_worker_stop_requested() {
return furi_thread_flags_get() & TotpBtTypeCodeWorkerEventStop;
}
@@ -16,15 +43,15 @@ static inline bool totp_type_code_worker_stop_requested() {
static void totp_type_code_worker_bt_set_app_mac(uint8_t* mac) {
uint8_t max_i;
size_t uid_size = furi_hal_version_uid_size();
- if(uid_size < 6) {
+ if(uid_size < TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN) {
max_i = uid_size;
} else {
- max_i = 6;
+ max_i = TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN;
}
const uint8_t* uid = furi_hal_version_uid();
memcpy(mac, uid, max_i);
- for(uint8_t i = max_i; i < 6; i++) {
+ for(uint8_t i = max_i; i < TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN; i++) {
mac[i] = 0;
}
@@ -39,23 +66,21 @@ static void totp_type_code_worker_type_code(TotpBtTypeCodeWorkerContext* context
i++;
} while(!context->is_connected && i < 100 && !totp_type_code_worker_stop_requested());
- if(context->is_connected && furi_mutex_acquire(context->string_sync, 500) == FuriStatusOk) {
+ if(context->is_connected &&
+ furi_mutex_acquire(context->code_buffer_sync, 500) == FuriStatusOk) {
totp_type_code_worker_execute_automation(
&furi_hal_bt_hid_kb_press,
&furi_hal_bt_hid_kb_release,
- context->string,
- context->string_length,
+ context->code_buffer,
+ context->code_buffer_size,
context->flags);
- furi_mutex_release(context->string_sync);
+ furi_mutex_release(context->code_buffer_sync);
}
}
static int32_t totp_type_code_worker_callback(void* context) {
furi_check(context);
FuriMutex* context_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
- if(context_mutex == NULL) {
- return 251;
- }
TotpBtTypeCodeWorkerContext* bt_context = context;
@@ -92,13 +117,13 @@ static void connection_status_changed_callback(BtStatus status, void* context) {
void totp_bt_type_code_worker_start(
TotpBtTypeCodeWorkerContext* context,
- char* code_buf,
- uint8_t code_buf_length,
- FuriMutex* code_buf_update_sync) {
+ char* code_buffer,
+ uint8_t code_buffer_size,
+ FuriMutex* code_buffer_sync) {
furi_check(context != NULL);
- context->string = code_buf;
- context->string_length = code_buf_length;
- context->string_sync = code_buf_update_sync;
+ context->code_buffer = code_buffer;
+ context->code_buffer_size = code_buffer_size;
+ context->code_buffer_sync = code_buffer_sync;
context->thread = furi_thread_alloc();
furi_thread_set_name(context->thread, "TOTPBtHidWorker");
furi_thread_set_stack_size(context->thread, 1024);
@@ -137,7 +162,6 @@ TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init() {
bt_keys_storage_set_storage_path(context->bt, HID_BT_KEYS_STORAGE_PATH);
#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
- totp_type_code_worker_bt_set_app_mac(&context->bt_mac[0]);
memcpy(
&context->previous_bt_name[0],
furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard),
@@ -148,8 +172,10 @@ TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init() {
TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN);
char new_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN];
snprintf(new_name, sizeof(new_name), "%s TOTP Auth", furi_hal_version_get_name_ptr());
+ uint8_t new_bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN];
+ totp_type_code_worker_bt_set_app_mac(new_bt_mac);
furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, new_name);
- furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, context->bt_mac);
+ furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, new_bt_mac);
#endif
if(!bt_set_profile(context->bt, BtProfileHidKeyboard)) {
@@ -197,4 +223,8 @@ void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context) {
context->bt = NULL;
free(context);
+}
+
+bool totp_bt_type_code_worker_is_advertising(const TotpBtTypeCodeWorkerContext* context) {
+ return context->is_advertising;
}
\ No newline at end of file
diff --git a/applications/external/totp/workers/bt_type_code/bt_type_code.h b/applications/external/totp/workers/bt_type_code/bt_type_code.h
index 1c59ea3e9..85016592e 100644
--- a/applications/external/totp/workers/bt_type_code/bt_type_code.h
+++ b/applications/external/totp/workers/bt_type_code/bt_type_code.h
@@ -1,49 +1,79 @@
#pragma once
-#include
-#include
-#include
-#include
-#include "../../features_config.h"
-
-#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
-#define TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH
-#define TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE
-#endif
+#include
+#include
+#include
typedef uint8_t TotpBtTypeCodeWorkerEvent;
-typedef struct {
- char* string;
- uint8_t string_length;
- uint8_t flags;
- FuriThread* thread;
- FuriMutex* string_sync;
- Bt* bt;
- bool is_advertising;
- bool is_connected;
-#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
- uint8_t bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN];
- char previous_bt_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN];
- uint8_t previous_bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN];
-#endif
-} TotpBtTypeCodeWorkerContext;
+typedef struct TotpBtTypeCodeWorkerContext TotpBtTypeCodeWorkerContext;
+/**
+ * @brief Bluetooth token input automation worker events
+ */
enum TotpBtTypeCodeWorkerEvents {
- TotpBtTypeCodeWorkerEventReserved = 0b0000,
- TotpBtTypeCodeWorkerEventStop = 0b0100,
- TotpBtTypeCodeWorkerEventType = 0b1000
+
+ /**
+ * @brief Reserved, should not be used anywhere
+ */
+ TotpBtTypeCodeWorkerEventReserved = 0b00,
+
+ /**
+ * @brief Stop worker
+ */
+ TotpBtTypeCodeWorkerEventStop = 0b01,
+
+ /**
+ * @brief Trigger token input automation
+ */
+ TotpBtTypeCodeWorkerEventType = 0b10
};
+/**
+ * @brief Initializes bluetooth token input automation worker
+ * @return worker context
+ */
TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init();
+
+/**
+ * @brief Disposes bluetooth token input automation worker and releases all the allocated resources
+ * @param context worker context
+ */
void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context);
+
+/**
+ * @brief Starts bluetooth token input automation worker
+ * @param context worker context
+ * @param code_buffer code buffer to be used to automate
+ * @param code_buffer_size code buffer size
+ * @param code_buffer_sync code buffer synchronization primitive
+ */
void totp_bt_type_code_worker_start(
TotpBtTypeCodeWorkerContext* context,
- char* code_buf,
- uint8_t code_buf_length,
- FuriMutex* code_buf_update_sync);
+ char* code_buffer,
+ uint8_t code_buffer_size,
+ FuriMutex* code_buffer_sync);
+
+/**
+ * @brief Stops bluetooth token input automation worker
+ * @param context worker context
+ */
void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context);
+
+/**
+ * @brief Notifies bluetooth token input automation worker with a given event
+ * @param context worker context
+ * @param event event to notify worker with
+ * @param flags event flags
+ */
void totp_bt_type_code_worker_notify(
TotpBtTypeCodeWorkerContext* context,
TotpBtTypeCodeWorkerEvent event,
uint8_t flags);
+
+/**
+ * @brief Gets information whether Bluetooth is advertising now or not
+ * @param context worker context
+ * @return \c true if Bluetooth is advertising now; \c false otherwise
+ */
+bool totp_bt_type_code_worker_is_advertising(const TotpBtTypeCodeWorkerContext* context);
diff --git a/applications/external/totp/workers/common.h b/applications/external/totp/workers/common.h
deleted file mode 100644
index 5e3a2006e..000000000
--- a/applications/external/totp/workers/common.h
+++ /dev/null
@@ -1,12 +0,0 @@
-#pragma once
-#include
-#include "../types/token_info.h"
-
-typedef bool (*TOTP_AUTOMATION_KEY_HANDLER)(uint16_t key);
-
-void totp_type_code_worker_execute_automation(
- TOTP_AUTOMATION_KEY_HANDLER key_press_fn,
- TOTP_AUTOMATION_KEY_HANDLER key_release_fn,
- const char* string,
- uint8_t string_length,
- TokenAutomationFeature features);
\ No newline at end of file
diff --git a/applications/external/totp/workers/generate_totp_code/generate_totp_code.c b/applications/external/totp/workers/generate_totp_code/generate_totp_code.c
new file mode 100644
index 000000000..7e9356c45
--- /dev/null
+++ b/applications/external/totp/workers/generate_totp_code/generate_totp_code.c
@@ -0,0 +1,195 @@
+#include "generate_totp_code.h"
+#include
+#include "../../services/crypto/crypto.h"
+#include "../../services/totp/totp.h"
+#include "../../services/convert/convert.h"
+#include
+#include
+
+#define ONE_SEC_MS (1000)
+
+struct TotpGenerateCodeWorkerContext {
+ char* code_buffer;
+ FuriThread* thread;
+ FuriMutex* code_buffer_sync;
+ const TokenInfo* token_info;
+ float timezone_offset;
+ uint8_t* iv;
+ TOTP_NEW_CODE_GENERATED_HANDLER on_new_code_generated_handler;
+ void* on_new_code_generated_handler_context;
+ TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler;
+ void* on_code_lifetime_changed_handler_context;
+};
+
+static const char* STEAM_ALGO_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY";
+
+static void
+ int_token_to_str(uint64_t i_token_code, char* str, TokenDigitsCount len, TokenHashAlgo algo) {
+ str[len] = '\0';
+ if(i_token_code == OTP_ERROR) {
+ memset(&str[0], '-', len);
+ } else {
+ if(algo == STEAM) {
+ for(uint8_t i = 0; i < len; i++) {
+ str[i] = STEAM_ALGO_ALPHABET[i_token_code % 26];
+ i_token_code = i_token_code / 26;
+ }
+ } else {
+ for(int8_t i = len - 1; i >= 0; i--) {
+ str[i] = CONVERT_DIGIT_TO_CHAR(i_token_code % 10);
+ i_token_code = i_token_code / 10;
+ }
+ }
+ }
+}
+
+static TOTP_ALGO get_totp_algo_impl(TokenHashAlgo algo) {
+ switch(algo) {
+ case SHA1:
+ case STEAM:
+ return TOTP_ALGO_SHA1;
+ case SHA256:
+ return TOTP_ALGO_SHA256;
+ case SHA512:
+ return TOTP_ALGO_SHA512;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static void generate_totp_code(
+ TotpGenerateCodeWorkerContext* context,
+ const TokenInfo* token_info,
+ uint32_t current_ts) {
+ if(token_info->token != NULL && token_info->token_length > 0) {
+ size_t key_length;
+ uint8_t* key = totp_crypto_decrypt(
+ token_info->token, token_info->token_length, context->iv, &key_length);
+
+ int_token_to_str(
+ totp_at(
+ get_totp_algo_impl(token_info->algo),
+ key,
+ key_length,
+ current_ts,
+ context->timezone_offset,
+ token_info->duration),
+ context->code_buffer,
+ token_info->digits,
+ token_info->algo);
+ memset_s(key, key_length, 0, key_length);
+ free(key);
+ } else {
+ int_token_to_str(0, context->code_buffer, token_info->digits, token_info->algo);
+ }
+}
+
+static int32_t totp_generate_worker_callback(void* context) {
+ furi_check(context);
+
+ TotpGenerateCodeWorkerContext* t_context = context;
+
+ while(true) {
+ uint32_t flags = furi_thread_flags_wait(
+ TotpGenerateCodeWorkerEventStop | TotpGenerateCodeWorkerEventForceUpdate,
+ FuriFlagWaitAny,
+ ONE_SEC_MS);
+
+ if(flags ==
+ (uint32_t)
+ FuriFlagErrorTimeout) { // If timeout, consider as no error, as we expect this and can handle gracefully
+ flags = 0;
+ }
+
+ furi_check((flags & FuriFlagError) == 0); //-V562
+
+ if(flags & TotpGenerateCodeWorkerEventStop) break;
+
+ const TokenInfo* token_info = t_context->token_info;
+ if(token_info == NULL) {
+ continue;
+ }
+
+ uint32_t curr_ts = furi_hal_rtc_get_timestamp();
+
+ bool time_left = false;
+ if(flags & TotpGenerateCodeWorkerEventForceUpdate ||
+ (time_left = (curr_ts % token_info->duration) == 0)) {
+ if(furi_mutex_acquire(t_context->code_buffer_sync, FuriWaitForever) == FuriStatusOk) {
+ generate_totp_code(t_context, token_info, curr_ts);
+ curr_ts = furi_hal_rtc_get_timestamp();
+ furi_mutex_release(t_context->code_buffer_sync);
+ if(t_context->on_new_code_generated_handler != NULL) {
+ (*(t_context->on_new_code_generated_handler))(
+ time_left, t_context->on_new_code_generated_handler_context);
+ }
+ }
+ }
+
+ if(t_context->on_code_lifetime_changed_handler != NULL) {
+ (*(t_context->on_code_lifetime_changed_handler))(
+ (float)(token_info->duration - curr_ts % token_info->duration) /
+ (float)token_info->duration,
+ t_context->on_code_lifetime_changed_handler_context);
+ }
+ }
+
+ return 0;
+}
+
+TotpGenerateCodeWorkerContext* totp_generate_code_worker_start(
+ char* code_buffer,
+ const TokenInfo* token_info,
+ FuriMutex* code_buffer_sync,
+ float timezone_offset,
+ uint8_t* iv) {
+ TotpGenerateCodeWorkerContext* context = malloc(sizeof(TotpGenerateCodeWorkerContext));
+ furi_check(context != NULL);
+ context->code_buffer = code_buffer;
+ context->token_info = token_info;
+ context->code_buffer_sync = code_buffer_sync;
+ context->timezone_offset = timezone_offset;
+ context->iv = iv;
+ context->thread = furi_thread_alloc();
+ furi_thread_set_name(context->thread, "TOTPGenerateWorker");
+ furi_thread_set_stack_size(context->thread, 2048);
+ furi_thread_set_context(context->thread, context);
+ furi_thread_set_callback(context->thread, totp_generate_worker_callback);
+ furi_thread_start(context->thread);
+ return context;
+}
+
+void totp_generate_code_worker_stop(TotpGenerateCodeWorkerContext* context) {
+ furi_check(context != NULL);
+ furi_thread_flags_set(furi_thread_get_id(context->thread), TotpGenerateCodeWorkerEventStop);
+ furi_thread_join(context->thread);
+ furi_thread_free(context->thread);
+ free(context);
+}
+
+void totp_generate_code_worker_notify(
+ TotpGenerateCodeWorkerContext* context,
+ TotpGenerateCodeWorkerEvent event) {
+ furi_check(context != NULL);
+ furi_thread_flags_set(furi_thread_get_id(context->thread), event);
+}
+
+void totp_generate_code_worker_set_code_generated_handler(
+ TotpGenerateCodeWorkerContext* context,
+ TOTP_NEW_CODE_GENERATED_HANDLER on_new_code_generated_handler,
+ void* on_new_code_generated_handler_context) {
+ furi_check(context != NULL);
+ context->on_new_code_generated_handler = on_new_code_generated_handler;
+ context->on_new_code_generated_handler_context = on_new_code_generated_handler_context;
+}
+
+void totp_generate_code_worker_set_lifetime_changed_handler(
+ TotpGenerateCodeWorkerContext* context,
+ TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler,
+ void* on_code_lifetime_changed_handler_context) {
+ furi_check(context != NULL);
+ context->on_code_lifetime_changed_handler = on_code_lifetime_changed_handler;
+ context->on_code_lifetime_changed_handler_context = on_code_lifetime_changed_handler_context;
+}
\ No newline at end of file
diff --git a/applications/external/totp/workers/generate_totp_code/generate_totp_code.h b/applications/external/totp/workers/generate_totp_code/generate_totp_code.h
new file mode 100644
index 000000000..f351ffa68
--- /dev/null
+++ b/applications/external/totp/workers/generate_totp_code/generate_totp_code.h
@@ -0,0 +1,86 @@
+#pragma once
+
+#include
+#include
+#include "../../types/token_info.h"
+
+typedef uint8_t TotpGenerateCodeWorkerEvent;
+
+typedef void (*TOTP_NEW_CODE_GENERATED_HANDLER)(bool time_left, void* context);
+typedef void (*TOTP_CODE_LIFETIME_CHANGED_HANDLER)(float code_lifetime_percent, void* context);
+
+typedef struct TotpGenerateCodeWorkerContext TotpGenerateCodeWorkerContext;
+
+/**
+ * @brief Generate token worker events
+ */
+enum TotGenerateCodeWorkerEvents {
+
+ /**
+ * @brief Reserved, should not be used anywhere
+ */
+ TotpGenerateCodeWorkerEventReserved = 0b00,
+
+ /**
+ * @brief Stop worker
+ */
+ TotpGenerateCodeWorkerEventStop = 0b01,
+
+ /**
+ * @brief Trigger token input automation
+ */
+ TotpGenerateCodeWorkerEventForceUpdate = 0b10
+};
+
+/**
+ * @brief Starts generate code worker
+ * @param code_buffer code buffer to generate code to
+ * @param token_info token info to be used to generate code
+ * @param code_buffer_sync code buffer synchronization primitive
+ * @param timezone_offset timezone offset to be used to generate code
+ * @param iv initialization vector (IV) to be used to decrypt token secret
+ * @return worker context
+ */
+TotpGenerateCodeWorkerContext* totp_generate_code_worker_start(
+ char* code_buffer,
+ const TokenInfo* token_info,
+ FuriMutex* code_buffer_sync,
+ float timezone_offset,
+ uint8_t* iv);
+
+/**
+ * @brief Stops generate code worker
+ * @param context worker context
+ */
+void totp_generate_code_worker_stop(TotpGenerateCodeWorkerContext* context);
+
+/**
+ * @brief Notifies generate code worker with a given event
+ * @param context worker context
+ * @param event event to notify worker with
+ */
+void totp_generate_code_worker_notify(
+ TotpGenerateCodeWorkerContext* context,
+ TotpGenerateCodeWorkerEvent event);
+
+/**
+ * @brief Sets new handler for "on new code generated" event
+ * @param context worker context
+ * @param on_new_code_generated_handler handler
+ * @param on_new_code_generated_handler_context handler context
+ */
+void totp_generate_code_worker_set_code_generated_handler(
+ TotpGenerateCodeWorkerContext* context,
+ TOTP_NEW_CODE_GENERATED_HANDLER on_new_code_generated_handler,
+ void* on_new_code_generated_handler_context);
+
+/**
+ * @brief Sets new handler for "on code lifetime changed" event
+ * @param context worker context
+ * @param on_code_lifetime_changed_handler handler
+ * @param on_code_lifetime_changed_handler_context handler context
+ */
+void totp_generate_code_worker_set_lifetime_changed_handler(
+ TotpGenerateCodeWorkerContext* context,
+ TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler,
+ void* on_code_lifetime_changed_handler_context);
\ No newline at end of file
diff --git a/applications/external/totp/workers/common.c b/applications/external/totp/workers/type_code_common.c
similarity index 71%
rename from applications/external/totp/workers/common.c
rename to applications/external/totp/workers/type_code_common.c
index 8ad0c2b46..bf5818ab2 100644
--- a/applications/external/totp/workers/common.c
+++ b/applications/external/totp/workers/type_code_common.c
@@ -1,6 +1,6 @@
-#include "common.h"
-#include
-#include
+#include "type_code_common.h"
+#include
+#include
#include "../../services/convert/convert.h"
static const uint8_t hid_number_keys[] = {
@@ -14,7 +14,7 @@ static const uint8_t hid_number_keys[] = {
HID_KEYBOARD_Z};
static uint32_t get_keystroke_delay(TokenAutomationFeature features) {
- if(features & TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER) {
+ if(features & TokenAutomationFeatureTypeSlower) {
return 100;
}
@@ -22,7 +22,7 @@ static uint32_t get_keystroke_delay(TokenAutomationFeature features) {
}
static uint32_t get_keypress_delay(TokenAutomationFeature features) {
- if(features & TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER) {
+ if(features & TokenAutomationFeatureTypeSlower) {
return 60;
}
@@ -30,7 +30,7 @@ static uint32_t get_keypress_delay(TokenAutomationFeature features) {
}
static void totp_type_code_worker_press_key(
- uint8_t key,
+ uint16_t key,
TOTP_AUTOMATION_KEY_HANDLER key_press_fn,
TOTP_AUTOMATION_KEY_HANDLER key_release_fn,
TokenAutomationFeature features) {
@@ -42,39 +42,38 @@ static void totp_type_code_worker_press_key(
void totp_type_code_worker_execute_automation(
TOTP_AUTOMATION_KEY_HANDLER key_press_fn,
TOTP_AUTOMATION_KEY_HANDLER key_release_fn,
- const char* string,
- uint8_t string_length,
+ const char* code_buffer,
+ uint8_t code_buffer_size,
TokenAutomationFeature features) {
furi_delay_ms(500);
uint8_t i = 0;
- totp_type_code_worker_press_key(
- HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features);
- while(i < string_length && string[i] != 0) {
- uint8_t char_index = CONVERT_CHAR_TO_DIGIT(string[i]);
+ while(i < code_buffer_size && code_buffer[i] != 0) {
+ uint8_t char_index = CONVERT_CHAR_TO_DIGIT(code_buffer[i]);
if(char_index > 9) {
- char_index = string[i] - 0x41 + 10;
+ char_index = code_buffer[i] - 0x41 + 10;
}
if(char_index > 35) break;
- uint8_t hid_kb_key = hid_number_keys[char_index];
+ uint16_t hid_kb_key = hid_number_keys[char_index];
+ if(char_index > 9) {
+ hid_kb_key |= KEY_MOD_LEFT_SHIFT;
+ }
+
totp_type_code_worker_press_key(hid_kb_key, key_press_fn, key_release_fn, features);
furi_delay_ms(get_keystroke_delay(features));
i++;
}
- if(features & TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END) {
+ if(features & TokenAutomationFeatureEnterAtTheEnd) {
furi_delay_ms(get_keystroke_delay(features));
totp_type_code_worker_press_key(
HID_KEYBOARD_RETURN, key_press_fn, key_release_fn, features);
}
- if(features & TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END) {
+ if(features & TokenAutomationFeatureTabAtTheEnd) {
furi_delay_ms(get_keystroke_delay(features));
totp_type_code_worker_press_key(HID_KEYBOARD_TAB, key_press_fn, key_release_fn, features);
}
-
- totp_type_code_worker_press_key(
- HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features);
}
\ No newline at end of file
diff --git a/applications/external/totp/workers/type_code_common.h b/applications/external/totp/workers/type_code_common.h
new file mode 100644
index 000000000..db357329a
--- /dev/null
+++ b/applications/external/totp/workers/type_code_common.h
@@ -0,0 +1,20 @@
+#pragma once
+#include
+#include "../types/token_info.h"
+
+typedef bool (*TOTP_AUTOMATION_KEY_HANDLER)(uint16_t key);
+
+/**
+ * @brief Executes token input automation using given key press\release handlers
+ * @param key_press_fn key press handler
+ * @param key_release_fn key release handler
+ * @param code_buffer code buffer to be typed
+ * @param code_buffer_size code buffer size
+ * @param features automation features
+ */
+void totp_type_code_worker_execute_automation(
+ TOTP_AUTOMATION_KEY_HANDLER key_press_fn,
+ TOTP_AUTOMATION_KEY_HANDLER key_release_fn,
+ const char* code_buffer,
+ uint8_t code_buffer_size,
+ TokenAutomationFeature features);
\ No newline at end of file
diff --git a/applications/external/totp/workers/usb_type_code/usb_type_code.c b/applications/external/totp/workers/usb_type_code/usb_type_code.c
index 5f7ccddf8..a391bdf82 100644
--- a/applications/external/totp/workers/usb_type_code/usb_type_code.c
+++ b/applications/external/totp/workers/usb_type_code/usb_type_code.c
@@ -1,7 +1,21 @@
#include "usb_type_code.h"
+#include
+#include
+#include
+#include
+#include
#include "../../services/convert/convert.h"
#include "../../types/token_info.h"
-#include "../common.h"
+#include "../type_code_common.h"
+
+struct TotpUsbTypeCodeWorkerContext {
+ char* code_buffer;
+ uint8_t code_buffer_size;
+ uint8_t flags;
+ FuriThread* thread;
+ FuriMutex* code_buffer_sync;
+ FuriHalUsbInterface* usb_mode_prev;
+};
static void totp_type_code_worker_restore_usb_mode(TotpUsbTypeCodeWorkerContext* context) {
if(context->usb_mode_prev != NULL) {
@@ -25,14 +39,14 @@ static void totp_type_code_worker_type_code(TotpUsbTypeCodeWorkerContext* contex
} while(!furi_hal_hid_is_connected() && i < 100 && !totp_type_code_worker_stop_requested());
if(furi_hal_hid_is_connected() &&
- furi_mutex_acquire(context->string_sync, 500) == FuriStatusOk) {
+ furi_mutex_acquire(context->code_buffer_sync, 500) == FuriStatusOk) {
totp_type_code_worker_execute_automation(
&furi_hal_hid_kb_press,
&furi_hal_hid_kb_release,
- context->string,
- context->string_length,
+ context->code_buffer,
+ context->code_buffer_size,
context->flags);
- furi_mutex_release(context->string_sync);
+ furi_mutex_release(context->code_buffer_sync);
furi_delay_ms(100);
}
@@ -43,9 +57,6 @@ static void totp_type_code_worker_type_code(TotpUsbTypeCodeWorkerContext* contex
static int32_t totp_type_code_worker_callback(void* context) {
furi_check(context);
FuriMutex* context_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
- if(context_mutex == NULL) {
- return 251;
- }
while(true) {
uint32_t flags = furi_thread_flags_wait(
@@ -70,14 +81,14 @@ static int32_t totp_type_code_worker_callback(void* context) {
}
TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start(
- char* code_buf,
- uint8_t code_buf_length,
- FuriMutex* code_buf_update_sync) {
+ char* code_buffer,
+ uint8_t code_buffer_size,
+ FuriMutex* code_buffer_sync) {
TotpUsbTypeCodeWorkerContext* context = malloc(sizeof(TotpUsbTypeCodeWorkerContext));
furi_check(context != NULL);
- context->string = code_buf;
- context->string_length = code_buf_length;
- context->string_sync = code_buf_update_sync;
+ context->code_buffer = code_buffer;
+ context->code_buffer_size = code_buffer_size;
+ context->code_buffer_sync = code_buffer_sync;
context->thread = furi_thread_alloc();
context->usb_mode_prev = NULL;
furi_thread_set_name(context->thread, "TOTPUsbHidWorker");
diff --git a/applications/external/totp/workers/usb_type_code/usb_type_code.h b/applications/external/totp/workers/usb_type_code/usb_type_code.h
index d0ea600ce..0a700e7fe 100644
--- a/applications/external/totp/workers/usb_type_code/usb_type_code.h
+++ b/applications/external/totp/workers/usb_type_code/usb_type_code.h
@@ -1,32 +1,59 @@
#pragma once
-#include
-#include
-#include
+#include
+#include
+#include
typedef uint8_t TotpUsbTypeCodeWorkerEvent;
-typedef struct {
- char* string;
- uint8_t string_length;
- uint8_t flags;
- FuriThread* thread;
- FuriMutex* string_sync;
- FuriHalUsbInterface* usb_mode_prev;
-} TotpUsbTypeCodeWorkerContext;
+typedef struct TotpUsbTypeCodeWorkerContext TotpUsbTypeCodeWorkerContext;
+/**
+ * @brief USB token input automation worker events
+ */
enum TotpUsbTypeCodeWorkerEvents {
+
+ /**
+ * @brief Reserved, should not be used anywhere
+ */
TotpUsbTypeCodeWorkerEventReserved = 0b00,
+
+ /**
+ * @brief Stop worker
+ */
TotpUsbTypeCodeWorkerEventStop = 0b01,
+
+ /**
+ * @brief Trigger token input automation
+ */
TotpUsbTypeCodeWorkerEventType = 0b10
};
+/**
+ * @brief Starts USB token input automation worker
+ * @param code_buffer code buffer to be used to automate
+ * @param code_buffer_size code buffer size
+ * @param code_buffer_sync code buffer synchronization primitive
+ * @return worker context
+ */
TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start(
- char* code_buf,
- uint8_t code_buf_length,
- FuriMutex* code_buf_update_sync);
+ char* code_buffer,
+ uint8_t code_buffer_size,
+ FuriMutex* code_buffer_sync);
+
+/**
+ * @brief Stops USB token input automation worker
+ * @param context worker context
+ */
void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context);
+
+/**
+ * @brief Notifies USB token input automation worker with a given event
+ * @param context worker context
+ * @param event event to notify worker with
+ * @param flags event flags
+ */
void totp_usb_type_code_worker_notify(
TotpUsbTypeCodeWorkerContext* context,
TotpUsbTypeCodeWorkerEvent event,
- uint8_t flags);
\ No newline at end of file
+ uint8_t flags);
diff --git a/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c b/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c
index 38a5a20e4..6988c42de 100644
--- a/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c
+++ b/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c
@@ -114,13 +114,9 @@ void uart_terminal_scene_console_output_on_enter(void* context) {
// Send command with CR+LF or newline '\n'
if(app->is_command && app->selected_tx_string) {
if(app->TERMINAL_MODE == 1) {
- // char buffer[240];
- // snprintf(buffer, 240, "%s\r\n", (app->selected_tx_string));
- // uart_terminal_uart_tx((unsigned char *)buffer, strlen(buffer));
uart_terminal_uart_tx(
(uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
- uart_terminal_uart_tx((uint8_t*)("\r"), 1);
- uart_terminal_uart_tx((uint8_t*)("\n"), 1);
+ uart_terminal_uart_tx((uint8_t*)("\r\n"), 2);
} else {
uart_terminal_uart_tx(
(uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
@@ -149,9 +145,4 @@ void uart_terminal_scene_console_output_on_exit(void* context) {
// Unregister rx callback
uart_terminal_uart_set_handle_rx_data_cb(app->uart, NULL);
-
- // Automatically logut when exiting view
- //if(app->is_command) {
- // uart_terminal_uart_tx((uint8_t*)("exit\n"), strlen("exit\n"));
- //}
}
\ No newline at end of file
diff --git a/applications/external/usb_hid_autofire/CHANGELOG.md b/applications/external/usb_hid_autofire/CHANGELOG.md
deleted file mode 100644
index d0924edd3..000000000
--- a/applications/external/usb_hid_autofire/CHANGELOG.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Changelog
-
-## 0.4
-- Show active/inactive state in primary font (bold)
-
-## 0.3
-- Add a delay between key-presses (with left/right buttons)
-
-## 0.2
-- Update icon
-
-## 0.1
-- Initial release of the USB HID Autofire application
diff --git a/applications/external/usb_hid_autofire/LICENSE b/applications/external/usb_hid_autofire/LICENSE
deleted file mode 100644
index f288702d2..000000000
--- a/applications/external/usb_hid_autofire/LICENSE
+++ /dev/null
@@ -1,674 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- Copyright (C)
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
diff --git a/applications/external/usb_hid_autofire/application.fam b/applications/external/usb_hid_autofire/application.fam
deleted file mode 100644
index 9e7b9378c..000000000
--- a/applications/external/usb_hid_autofire/application.fam
+++ /dev/null
@@ -1,13 +0,0 @@
-App(
- appid="usb_hid_autofire",
- name="USB HID Autofire",
- apptype=FlipperAppType.EXTERNAL,
- entry_point="usb_hid_autofire_app",
- cdefines=["APP_USB_HID_AUTOFIRE"],
- requires=[
- "gui",
- ],
- stack_size=1 * 1024,
- fap_icon="usb_hid_autofire.png",
- fap_category="Misc",
-)
diff --git a/applications/external/usb_hid_autofire/tools.c b/applications/external/usb_hid_autofire/tools.c
deleted file mode 100644
index 566d00564..000000000
--- a/applications/external/usb_hid_autofire/tools.c
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// Tools for USB HID Autofire
-//
-
-void strrev(char* arr, int start, int end) {
- char temp;
-
- if(start >= end) return;
-
- temp = *(arr + start);
- *(arr + start) = *(arr + end);
- *(arr + end) = temp;
-
- start++;
- end--;
- strrev(arr, start, end);
-}
-
-char* itoa(int number, char* arr, int base) {
- int i = 0, r, negative = 0;
-
- if(number == 0) {
- arr[i] = '0';
- arr[i + 1] = '\0';
- return arr;
- }
-
- if(number < 0 && base == 10) {
- number *= -1;
- negative = 1;
- }
-
- while(number != 0) {
- r = number % base;
- arr[i] = (r > 9) ? (r - 10) + 'a' : r + '0';
- i++;
- number /= base;
- }
-
- if(negative) {
- arr[i] = '-';
- i++;
- }
-
- strrev(arr, 0, i - 1);
-
- arr[i] = '\0';
-
- return arr;
-}
diff --git a/applications/external/usb_hid_autofire/tools.h b/applications/external/usb_hid_autofire/tools.h
deleted file mode 100644
index 7e5226514..000000000
--- a/applications/external/usb_hid_autofire/tools.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef FLIPPERZERO_FIRMWARE_TOOLS_H
-#define FLIPPERZERO_FIRMWARE_TOOLS_H
-
-void strrev(char* arr, int start, int end);
-char* itoa(int number, char* arr, int base);
-
-#endif //FLIPPERZERO_FIRMWARE_TOOLS_H
diff --git a/applications/external/usb_hid_autofire/usb_hid_autofire.c b/applications/external/usb_hid_autofire/usb_hid_autofire.c
deleted file mode 100644
index cf8077ae7..000000000
--- a/applications/external/usb_hid_autofire/usb_hid_autofire.c
+++ /dev/null
@@ -1,127 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include "version.h"
-#include "tools.h"
-
-// Uncomment to be able to make a screenshot
-//#define USB_HID_AUTOFIRE_SCREENSHOT
-
-typedef enum {
- EventTypeInput,
-} EventType;
-
-typedef struct {
- union {
- InputEvent input;
- };
- EventType type;
-} UsbMouseEvent;
-
-bool btn_left_autofire = false;
-uint32_t autofire_delay = 10;
-
-static void usb_hid_autofire_render_callback(Canvas* canvas, void* ctx) {
- UNUSED(ctx);
- char autofire_delay_str[12];
- //std::string pi = "pi is " + std::to_string(3.1415926);
- itoa(autofire_delay, autofire_delay_str, 10);
- //sprintf(autofire_delay_str, "%lu", autofire_delay);
-
- canvas_clear(canvas);
-
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str(canvas, 0, 10, "USB HID Autofire");
- canvas_draw_str(canvas, 0, 34, btn_left_autofire ? "" : "");
-
- canvas_set_font(canvas, FontSecondary);
- canvas_draw_str(canvas, 90, 10, "v");
- canvas_draw_str(canvas, 96, 10, VERSION);
- canvas_draw_str(canvas, 0, 22, "Press [ok] for auto left clicking");
- canvas_draw_str(canvas, 0, 46, "delay [ms]:");
- canvas_draw_str(canvas, 50, 46, autofire_delay_str);
- canvas_draw_str(canvas, 0, 63, "Press [back] to exit");
-}
-
-static void usb_hid_autofire_input_callback(InputEvent* input_event, void* ctx) {
- FuriMessageQueue* event_queue = ctx;
-
- UsbMouseEvent event;
- event.type = EventTypeInput;
- event.input = *input_event;
- furi_message_queue_put(event_queue, &event, FuriWaitForever);
-}
-
-int32_t usb_hid_autofire_app(void* p) {
- UNUSED(p);
- FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(UsbMouseEvent));
- furi_check(event_queue);
- ViewPort* view_port = view_port_alloc();
-
- FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
-#ifndef USB_HID_AUTOFIRE_SCREENSHOT
- furi_hal_usb_unlock();
- furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true);
-#endif
-
- view_port_draw_callback_set(view_port, usb_hid_autofire_render_callback, NULL);
- view_port_input_callback_set(view_port, usb_hid_autofire_input_callback, event_queue);
-
- // Open GUI and register view_port
- Gui* gui = furi_record_open(RECORD_GUI);
- gui_add_view_port(gui, view_port, GuiLayerFullscreen);
-
- UsbMouseEvent event;
- while(1) {
- FuriStatus event_status = furi_message_queue_get(event_queue, &event, 50);
-
- if(event_status == FuriStatusOk) {
- if(event.type == EventTypeInput) {
- if(event.input.key == InputKeyBack) {
- break;
- }
-
- if(event.input.type != InputTypeRelease) {
- continue;
- }
-
- switch(event.input.key) {
- case InputKeyOk:
- btn_left_autofire = !btn_left_autofire;
- break;
- case InputKeyLeft:
- if(autofire_delay > 0) {
- autofire_delay -= 10;
- }
- break;
- case InputKeyRight:
- autofire_delay += 10;
- break;
- default:
- break;
- }
- }
- }
-
- if(btn_left_autofire) {
- furi_hal_hid_mouse_press(HID_MOUSE_BTN_LEFT);
- // TODO: Don't wait, but use the timer directly to just don't send the release event (see furi_hal_cortex_delay_us)
- furi_delay_us(autofire_delay * 500);
- furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT);
- furi_delay_us(autofire_delay * 500);
- }
-
- view_port_update(view_port);
- }
-
- furi_hal_usb_set_config(usb_mode_prev, NULL);
-
- // remove & free all stuff created by app
- gui_remove_view_port(gui, view_port);
- view_port_free(view_port);
- furi_message_queue_free(event_queue);
-
- return 0;
-}
diff --git a/applications/external/usb_hid_autofire/usb_hid_autofire.kra b/applications/external/usb_hid_autofire/usb_hid_autofire.kra
deleted file mode 100644
index 21d416548..000000000
Binary files a/applications/external/usb_hid_autofire/usb_hid_autofire.kra and /dev/null differ
diff --git a/applications/external/usb_hid_autofire/usb_hid_autofire.png b/applications/external/usb_hid_autofire/usb_hid_autofire.png
deleted file mode 100644
index 369bff022..000000000
Binary files a/applications/external/usb_hid_autofire/usb_hid_autofire.png and /dev/null differ
diff --git a/applications/external/usb_hid_autofire/usb_hid_autofire.svg b/applications/external/usb_hid_autofire/usb_hid_autofire.svg
deleted file mode 100644
index ed66f3cfd..000000000
--- a/applications/external/usb_hid_autofire/usb_hid_autofire.svg
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
diff --git a/applications/external/usb_hid_autofire/version.h b/applications/external/usb_hid_autofire/version.h
deleted file mode 100644
index ac1f5d0fa..000000000
--- a/applications/external/usb_hid_autofire/version.h
+++ /dev/null
@@ -1 +0,0 @@
-#define VERSION "0.4"
diff --git a/applications/external/wav_player/wav_parser.c b/applications/external/wav_player/wav_parser.c
index 1f534bacb..8c1f22b19 100644
--- a/applications/external/wav_player/wav_parser.c
+++ b/applications/external/wav_player/wav_parser.c
@@ -11,7 +11,7 @@ const char* format_text(FormatTag tag) {
default:
return "Unknown";
}
-};
+}
struct WavParser {
WavHeaderChunk header;
diff --git a/applications/external/weather_station/protocols/protocol_items.c b/applications/external/weather_station/protocols/protocol_items.c
index 2c9d751c7..cd4bae76d 100644
--- a/applications/external/weather_station/protocols/protocol_items.c
+++ b/applications/external/weather_station/protocols/protocol_items.c
@@ -16,6 +16,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = {
&ws_protocol_auriol_th,
&ws_protocol_oregon_v1,
&ws_protocol_tx_8300,
+ &ws_protocol_wendox_w6726,
};
const SubGhzProtocolRegistry weather_station_protocol_registry = {
diff --git a/applications/external/weather_station/protocols/protocol_items.h b/applications/external/weather_station/protocols/protocol_items.h
index f9e443abc..0398c11f2 100644
--- a/applications/external/weather_station/protocols/protocol_items.h
+++ b/applications/external/weather_station/protocols/protocol_items.h
@@ -16,5 +16,6 @@
#include "auriol_hg0601a.h"
#include "oregon_v1.h"
#include "tx_8300.h"
+#include "wendox_w6726.h"
extern const SubGhzProtocolRegistry weather_station_protocol_registry;
diff --git a/applications/external/weather_station/protocols/wendox_w6726.c b/applications/external/weather_station/protocols/wendox_w6726.c
new file mode 100644
index 000000000..265c77f70
--- /dev/null
+++ b/applications/external/weather_station/protocols/wendox_w6726.c
@@ -0,0 +1,299 @@
+#include "wendox_w6726.h"
+
+#define TAG "WSProtocolWendoxW6726"
+
+/*
+ * Wendox W6726
+ *
+ * Temperature -50°Ρ to +70°Ρ
+ * _ _ _ __ _
+ * _| |___| |___| |___ ... | |_| |__...._______________
+ * preamble data guard time
+ *
+ * 3 reps every 3 minutes
+ * in the first message 11 bytes of the preamble in the rest by 7
+ *
+ * bit 0: 1955-hi, 5865-lo
+ * bit 1: 5865-hi, 1955-lo
+ * guard time: 12*1955+(lo last bit)
+ * data: 29 bit
+ *
+ * IIIII | ZTTTTTTTTT | uuuuuuuBuu | CCCC
+ *
+ * I: identification;
+ * Z: temperature sign;
+ * T: temperature sign dependent +12 Ρ°;
+ * B: battery low; flag to indicate low battery voltage;
+ * C: CRC4 (polynomial = 0x9, start_data = 0xD);
+ * u: unknown;
+ */
+
+static const SubGhzBlockConst ws_protocol_wendox_w6726_const = {
+ .te_short = 1955,
+ .te_long = 5865,
+ .te_delta = 300,
+ .min_count_bit_for_found = 29,
+};
+
+struct WSProtocolDecoderWendoxW6726 {
+ SubGhzProtocolDecoderBase base;
+
+ SubGhzBlockDecoder decoder;
+ WSBlockGeneric generic;
+
+ uint16_t header_count;
+};
+
+struct WSProtocolEncoderWendoxW6726 {
+ SubGhzProtocolEncoderBase base;
+
+ SubGhzProtocolBlockEncoder encoder;
+ WSBlockGeneric generic;
+};
+
+typedef enum {
+ WendoxW6726DecoderStepReset = 0,
+ WendoxW6726DecoderStepCheckPreambule,
+ WendoxW6726DecoderStepSaveDuration,
+ WendoxW6726DecoderStepCheckDuration,
+} WendoxW6726DecoderStep;
+
+const SubGhzProtocolDecoder ws_protocol_wendox_w6726_decoder = {
+ .alloc = ws_protocol_decoder_wendox_w6726_alloc,
+ .free = ws_protocol_decoder_wendox_w6726_free,
+
+ .feed = ws_protocol_decoder_wendox_w6726_feed,
+ .reset = ws_protocol_decoder_wendox_w6726_reset,
+
+ .get_hash_data = ws_protocol_decoder_wendox_w6726_get_hash_data,
+ .serialize = ws_protocol_decoder_wendox_w6726_serialize,
+ .deserialize = ws_protocol_decoder_wendox_w6726_deserialize,
+ .get_string = ws_protocol_decoder_wendox_w6726_get_string,
+};
+
+const SubGhzProtocolEncoder ws_protocol_wendox_w6726_encoder = {
+ .alloc = NULL,
+ .free = NULL,
+
+ .deserialize = NULL,
+ .stop = NULL,
+ .yield = NULL,
+};
+
+const SubGhzProtocol ws_protocol_wendox_w6726 = {
+ .name = WS_PROTOCOL_WENDOX_W6726_NAME,
+ .type = SubGhzProtocolWeatherStation,
+ .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
+ SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
+
+ .decoder = &ws_protocol_wendox_w6726_decoder,
+ .encoder = &ws_protocol_wendox_w6726_encoder,
+};
+
+void* ws_protocol_decoder_wendox_w6726_alloc(SubGhzEnvironment* environment) {
+ UNUSED(environment);
+ WSProtocolDecoderWendoxW6726* instance = malloc(sizeof(WSProtocolDecoderWendoxW6726));
+ instance->base.protocol = &ws_protocol_wendox_w6726;
+ instance->generic.protocol_name = instance->base.protocol->name;
+ return instance;
+}
+
+void ws_protocol_decoder_wendox_w6726_free(void* context) {
+ furi_assert(context);
+ WSProtocolDecoderWendoxW6726* instance = context;
+ free(instance);
+}
+
+void ws_protocol_decoder_wendox_w6726_reset(void* context) {
+ furi_assert(context);
+ WSProtocolDecoderWendoxW6726* instance = context;
+ instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+}
+
+static bool ws_protocol_wendox_w6726_check(WSProtocolDecoderWendoxW6726* instance) {
+ if(!instance->decoder.decode_data) return false;
+ uint8_t msg[] = {
+ instance->decoder.decode_data >> 28,
+ instance->decoder.decode_data >> 20,
+ instance->decoder.decode_data >> 12,
+ instance->decoder.decode_data >> 4};
+
+ uint8_t crc = subghz_protocol_blocks_crc4(msg, 4, 0x9, 0xD);
+ return (crc == (instance->decoder.decode_data & 0x0F));
+}
+
+/**
+ * Analysis of received data
+ * @param instance Pointer to a WSBlockGeneric* instance
+ */
+static void ws_protocol_wendox_w6726_remote_controller(WSBlockGeneric* instance) {
+ instance->id = (instance->data >> 24) & 0xFF;
+ instance->battery_low = (instance->data >> 6) & 1;
+ instance->channel = WS_NO_CHANNEL;
+
+ if(((instance->data >> 23) & 1)) {
+ instance->temp = (float)(((instance->data >> 14) & 0x1FF) + 12) / 10.0f;
+ } else {
+ instance->temp = (float)((~(instance->data >> 14) & 0x1FF) + 1 - 12) / -10.0f;
+ }
+
+ if(instance->temp < -50.0f) {
+ instance->temp = -50.0f;
+ } else if(instance->temp > 70.0f) {
+ instance->temp = 70.0f;
+ }
+
+ instance->btn = WS_NO_BTN;
+ instance->humidity = WS_NO_HUMIDITY;
+}
+
+void ws_protocol_decoder_wendox_w6726_feed(void* context, bool level, uint32_t duration) {
+ furi_assert(context);
+ WSProtocolDecoderWendoxW6726* instance = context;
+
+ switch(instance->decoder.parser_step) {
+ case WendoxW6726DecoderStepReset:
+ if((level) && (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) <
+ ws_protocol_wendox_w6726_const.te_delta)) {
+ instance->decoder.parser_step = WendoxW6726DecoderStepCheckPreambule;
+ instance->decoder.te_last = duration;
+ instance->header_count = 0;
+ }
+ break;
+
+ case WendoxW6726DecoderStepCheckPreambule:
+ if(level) {
+ instance->decoder.te_last = duration;
+ } else {
+ if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) <
+ ws_protocol_wendox_w6726_const.te_delta * 1) &&
+ (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_long) <
+ ws_protocol_wendox_w6726_const.te_delta * 2)) {
+ instance->header_count++;
+ } else if((instance->header_count > 4) && (instance->header_count < 12)) {
+ if((DURATION_DIFF(
+ instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) <
+ ws_protocol_wendox_w6726_const.te_delta * 2) &&
+ (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) <
+ ws_protocol_wendox_w6726_const.te_delta)) {
+ instance->decoder.decode_data = 0;
+ instance->decoder.decode_count_bit = 0;
+ subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+ instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration;
+ } else {
+ instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+ }
+
+ } else {
+ instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+ }
+ }
+ break;
+
+ case WendoxW6726DecoderStepSaveDuration:
+ if(level) {
+ instance->decoder.te_last = duration;
+ instance->decoder.parser_step = WendoxW6726DecoderStepCheckDuration;
+ } else {
+ instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+ }
+ break;
+
+ case WendoxW6726DecoderStepCheckDuration:
+ if(!level) {
+ if(duration >
+ ws_protocol_wendox_w6726_const.te_short + ws_protocol_wendox_w6726_const.te_long) {
+ if(DURATION_DIFF(
+ instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) <
+ ws_protocol_wendox_w6726_const.te_delta) {
+ subghz_protocol_blocks_add_bit(&instance->decoder, 0);
+ instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration;
+ } else if(
+ DURATION_DIFF(
+ instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) <
+ ws_protocol_wendox_w6726_const.te_delta * 2) {
+ subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+ instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration;
+ } else {
+ instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+ }
+ if((instance->decoder.decode_count_bit ==
+ ws_protocol_wendox_w6726_const.min_count_bit_for_found) &&
+ ws_protocol_wendox_w6726_check(instance)) {
+ instance->generic.data = instance->decoder.decode_data;
+ instance->generic.data_count_bit = instance->decoder.decode_count_bit;
+ ws_protocol_wendox_w6726_remote_controller(&instance->generic);
+ if(instance->base.callback)
+ instance->base.callback(&instance->base, instance->base.context);
+ }
+
+ instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+ } else if(
+ (DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) <
+ ws_protocol_wendox_w6726_const.te_delta) &&
+ (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_long) <
+ ws_protocol_wendox_w6726_const.te_delta * 3)) {
+ subghz_protocol_blocks_add_bit(&instance->decoder, 0);
+ instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration;
+ } else if(
+ (DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) <
+ ws_protocol_wendox_w6726_const.te_delta * 2) &&
+ (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) <
+ ws_protocol_wendox_w6726_const.te_delta)) {
+ subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+ instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration;
+ } else {
+ instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+ }
+ } else {
+ instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+ }
+ break;
+ }
+}
+
+uint8_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context) {
+ furi_assert(context);
+ WSProtocolDecoderWendoxW6726* instance = context;
+ return subghz_protocol_blocks_get_hash_data(
+ &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
+}
+
+SubGhzProtocolStatus ws_protocol_decoder_wendox_w6726_serialize(
+ void* context,
+ FlipperFormat* flipper_format,
+ SubGhzRadioPreset* preset) {
+ furi_assert(context);
+ WSProtocolDecoderWendoxW6726* instance = context;
+ return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
+}
+
+SubGhzProtocolStatus
+ ws_protocol_decoder_wendox_w6726_deserialize(void* context, FlipperFormat* flipper_format) {
+ furi_assert(context);
+ WSProtocolDecoderWendoxW6726* instance = context;
+ return ws_block_generic_deserialize_check_count_bit(
+ &instance->generic,
+ flipper_format,
+ ws_protocol_wendox_w6726_const.min_count_bit_for_found);
+}
+
+void ws_protocol_decoder_wendox_w6726_get_string(void* context, FuriString* output) {
+ furi_assert(context);
+ WSProtocolDecoderWendoxW6726* instance = context;
+ furi_string_printf(
+ output,
+ "%s %dbit\r\n"
+ "Key:0x%lX%08lX\r\n"
+ "Sn:0x%lX Ch:%d Bat:%d\r\n"
+ "Temp:%3.1f C Hum:%d%%",
+ instance->generic.protocol_name,
+ instance->generic.data_count_bit,
+ (uint32_t)(instance->generic.data >> 32),
+ (uint32_t)(instance->generic.data),
+ instance->generic.id,
+ instance->generic.channel,
+ instance->generic.battery_low,
+ (double)instance->generic.temp,
+ instance->generic.humidity);
+}
diff --git a/applications/external/weather_station/protocols/wendox_w6726.h b/applications/external/weather_station/protocols/wendox_w6726.h
new file mode 100644
index 000000000..236777a1c
--- /dev/null
+++ b/applications/external/weather_station/protocols/wendox_w6726.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include "ws_generic.h"
+#include
+
+#define WS_PROTOCOL_WENDOX_W6726_NAME "Wendox W6726"
+
+typedef struct WSProtocolDecoderWendoxW6726 WSProtocolDecoderWendoxW6726;
+typedef struct WSProtocolEncoderWendoxW6726 WSProtocolEncoderWendoxW6726;
+
+extern const SubGhzProtocolDecoder ws_protocol_wendox_w6726_decoder;
+extern const SubGhzProtocolEncoder ws_protocol_wendox_w6726_encoder;
+extern const SubGhzProtocol ws_protocol_wendox_w6726;
+
+/**
+ * Allocate WSProtocolDecoderWendoxW6726.
+ * @param environment Pointer to a SubGhzEnvironment instance
+ * @return WSProtocolDecoderWendoxW6726* pointer to a WSProtocolDecoderWendoxW6726 instance
+ */
+void* ws_protocol_decoder_wendox_w6726_alloc(SubGhzEnvironment* environment);
+
+/**
+ * Free WSProtocolDecoderWendoxW6726.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ */
+void ws_protocol_decoder_wendox_w6726_free(void* context);
+
+/**
+ * Reset decoder WSProtocolDecoderWendoxW6726.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ */
+void ws_protocol_decoder_wendox_w6726_reset(void* context);
+
+/**
+ * Parse a raw sequence of levels and durations received from the air.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ * @param level Signal level true-high false-low
+ * @param duration Duration of this level in, us
+ */
+void ws_protocol_decoder_wendox_w6726_feed(void* context, bool level, uint32_t duration);
+
+/**
+ * Getting the hash sum of the last randomly received parcel.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ * @return hash Hash sum
+ */
+uint8_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context);
+
+/**
+ * Serialize data WSProtocolDecoderWendoxW6726.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ * @param flipper_format Pointer to a FlipperFormat instance
+ * @param preset The modulation on which the signal was received, SubGhzRadioPreset
+ * @return status
+ */
+SubGhzProtocolStatus ws_protocol_decoder_wendox_w6726_serialize(
+ void* context,
+ FlipperFormat* flipper_format,
+ SubGhzRadioPreset* preset);
+
+/**
+ * Deserialize data WSProtocolDecoderWendoxW6726.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ * @param flipper_format Pointer to a FlipperFormat instance
+ * @return status
+ */
+SubGhzProtocolStatus
+ ws_protocol_decoder_wendox_w6726_deserialize(void* context, FlipperFormat* flipper_format);
+
+/**
+ * Getting a textual representation of the received data.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ * @param output Resulting text
+ */
+void ws_protocol_decoder_wendox_w6726_get_string(void* context, FuriString* output);
diff --git a/applications/external/weather_station/scenes/weather_station_receiver.c b/applications/external/weather_station/scenes/weather_station_receiver.c
index e76810430..79d01f13d 100644
--- a/applications/external/weather_station/scenes/weather_station_receiver.c
+++ b/applications/external/weather_station/scenes/weather_station_receiver.c
@@ -133,7 +133,7 @@ void weather_station_scene_receiver_on_enter(void* context) {
if(app->txrx->txrx_state == WSTxRxStateRx) {
ws_rx_end(app);
- };
+ }
if((app->txrx->txrx_state == WSTxRxStateIDLE) || (app->txrx->txrx_state == WSTxRxStateSleep)) {
ws_begin(
app,
@@ -157,7 +157,7 @@ bool weather_station_scene_receiver_on_event(void* context, SceneManagerEvent ev
if(app->txrx->txrx_state == WSTxRxStateRx) {
ws_rx_end(app);
ws_sleep(app);
- };
+ }
app->txrx->hopper_state = WSHopperStateOFF;
app->txrx->idx_menu_chosen = 0;
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app);
diff --git a/applications/external/weather_station/weather_station_app.c b/applications/external/weather_station/weather_station_app.c
index a3135a6b0..8bea4961d 100644
--- a/applications/external/weather_station/weather_station_app.c
+++ b/applications/external/weather_station/weather_station_app.c
@@ -110,7 +110,8 @@ WeatherStationApp* weather_station_app_alloc() {
// Auto switch to internal radio if external radio is not available
furi_delay_ms(15);
if(!furi_hal_subghz_check_radio()) {
- furi_hal_subghz_set_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_select_radio_type(SubGhzRadioInternal);
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
}
furi_hal_power_suppress_charge_enter();
@@ -128,6 +129,8 @@ void weather_station_app_free(WeatherStationApp* app) {
// Disable power for External CC1101 if it was enabled and module is connected
furi_hal_subghz_disable_ext_power();
+ // Reinit SPI handles for internal radio / nfc
+ furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
// Submenu
view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewSubmenu);
diff --git a/applications/external/weather_station/weather_station_app_i.c b/applications/external/weather_station/weather_station_app_i.c
index 7236b6625..2e83a6e30 100644
--- a/applications/external/weather_station/weather_station_app_i.c
+++ b/applications/external/weather_station/weather_station_app_i.c
@@ -146,7 +146,7 @@ void ws_hopper_update(WeatherStationApp* app) {
if(app->txrx->txrx_state == WSTxRxStateRx) {
ws_rx_end(app);
- };
+ }
if(app->txrx->txrx_state == WSTxRxStateIDLE) {
subghz_receiver_reset(app->txrx->receiver);
app->txrx->preset->frequency =
diff --git a/applications/external/wifi_marauder_companion/application.fam b/applications/external/wifi_marauder_companion/application.fam
index 5fe303b00..746819b99 100644
--- a/applications/external/wifi_marauder_companion/application.fam
+++ b/applications/external/wifi_marauder_companion/application.fam
@@ -8,4 +8,5 @@ App(
order=90,
fap_icon="wifi_10px.png",
fap_category="WiFi",
+ fap_icon_assets="assets",
)
diff --git a/applications/external/wifi_marauder_companion/assets/DolphinCommon_56x48.png b/applications/external/wifi_marauder_companion/assets/DolphinCommon_56x48.png
new file mode 100644
index 000000000..089aaed83
Binary files /dev/null and b/applications/external/wifi_marauder_companion/assets/DolphinCommon_56x48.png differ
diff --git a/applications/external/wifi_marauder_companion/assets/KeyBackspaceSelected_16x9.png b/applications/external/wifi_marauder_companion/assets/KeyBackspaceSelected_16x9.png
new file mode 100644
index 000000000..7cc0759a8
Binary files /dev/null and b/applications/external/wifi_marauder_companion/assets/KeyBackspaceSelected_16x9.png differ
diff --git a/applications/external/wifi_marauder_companion/assets/KeyBackspace_16x9.png b/applications/external/wifi_marauder_companion/assets/KeyBackspace_16x9.png
new file mode 100644
index 000000000..9946232d9
Binary files /dev/null and b/applications/external/wifi_marauder_companion/assets/KeyBackspace_16x9.png differ
diff --git a/applications/external/wifi_marauder_companion/assets/KeySaveSelected_24x11.png b/applications/external/wifi_marauder_companion/assets/KeySaveSelected_24x11.png
new file mode 100644
index 000000000..eeb3569d3
Binary files /dev/null and b/applications/external/wifi_marauder_companion/assets/KeySaveSelected_24x11.png differ
diff --git a/applications/external/wifi_marauder_companion/assets/KeySave_24x11.png b/applications/external/wifi_marauder_companion/assets/KeySave_24x11.png
new file mode 100644
index 000000000..e7dba987a
Binary files /dev/null and b/applications/external/wifi_marauder_companion/assets/KeySave_24x11.png differ
diff --git a/applications/external/wifi_marauder_companion/assets/Text_10x10.png b/applications/external/wifi_marauder_companion/assets/Text_10x10.png
new file mode 100644
index 000000000..8e8a6183d
Binary files /dev/null and b/applications/external/wifi_marauder_companion/assets/Text_10x10.png differ
diff --git a/applications/external/wifi_marauder_companion/assets/WarningDolphin_45x42.png b/applications/external/wifi_marauder_companion/assets/WarningDolphin_45x42.png
new file mode 100644
index 000000000..d766ffbb4
Binary files /dev/null and b/applications/external/wifi_marauder_companion/assets/WarningDolphin_45x42.png differ
diff --git a/applications/external/wifi_marauder_companion/file/sequential_file.c b/applications/external/wifi_marauder_companion/file/sequential_file.c
new file mode 100644
index 000000000..d780deb12
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/file/sequential_file.c
@@ -0,0 +1,46 @@
+#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;
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/file/sequential_file.h b/applications/external/wifi_marauder_companion/file/sequential_file.h
new file mode 100644
index 000000000..4fd4794c2
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/file/sequential_file.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include
+
+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);
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_config.h b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_config.h
index 715897d17..ae976c6bf 100644
--- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_config.h
+++ b/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)
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c
index 0729500eb..05d94fe80 100644
--- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c
@@ -1,5 +1,34 @@
#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 +63,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,13 +93,14 @@ 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
+ // 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
@@ -73,25 +109,53 @@ void wifi_marauder_scene_console_output_on_enter(void* context) {
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);
+ }
}
}
@@ -113,14 +177,18 @@ bool wifi_marauder_scene_console_output_on_event(void* context, SceneManagerEven
void wifi_marauder_scene_console_output_on_exit(void* context) {
WifiMarauderApp* app = context;
+ // 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);
- // Automatically stop the scan when exiting view
- if(app->is_command) {
- wifi_marauder_uart_tx((uint8_t*)("stopscan\n"), strlen("stopscan\n"));
- }
+ 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)) {
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_log_viewer.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_log_viewer.c
index f4e84ccc8..6edb4a49d 100644
--- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_log_viewer.c
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_log_viewer.c
@@ -148,8 +148,10 @@ bool wifi_marauder_scene_log_viewer_on_event(void* context, SceneManagerEvent ev
// Browse
FuriString* predefined_filepath = furi_string_alloc_set_str(MARAUDER_APP_FOLDER_LOGS);
FuriString* selected_filepath = furi_string_alloc();
+ DialogsFileBrowserOptions browser_options;
+ dialog_file_browser_set_basic_options(&browser_options, ".log", &I_Text_10x10);
if(dialog_file_browser_show(
- app->dialogs, selected_filepath, predefined_filepath, NULL)) {
+ app->dialogs, selected_filepath, predefined_filepath, &browser_options)) {
strncpy(
app->log_file_path,
furi_string_get_cstr(selected_filepath),
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_confirm_delete.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_confirm_delete.c
new file mode 100644
index 000000000..8e436fe2b
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_confirm_delete.c
@@ -0,0 +1,83 @@
+#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);
+}
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_edit.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_edit.c
new file mode 100644
index 000000000..697daba4f
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_edit.c
@@ -0,0 +1,125 @@
+#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);
+}
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_edit_list.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_edit_list.c
new file mode 100644
index 000000000..7a3284caf
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_edit_list.c
@@ -0,0 +1,188 @@
+#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);
+}
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_options.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_options.c
new file mode 100644
index 000000000..35b374f61
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_options.c
@@ -0,0 +1,111 @@
+#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);
+}
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_select.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_select.c
new file mode 100644
index 000000000..bc0746858
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_select.c
@@ -0,0 +1,90 @@
+#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);
+}
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_settings.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_settings.c
new file mode 100644
index 000000000..b4903af05
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_settings.c
@@ -0,0 +1,87 @@
+#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);
+}
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_stage_add.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_stage_add.c
new file mode 100644
index 000000000..33f1a2f03
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_stage_add.c
@@ -0,0 +1,297 @@
+#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);
+}
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_stage_edit.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_stage_edit.c
new file mode 100644
index 000000000..b8581e3e7
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_script_stage_edit.c
@@ -0,0 +1,203 @@
+#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);
+}
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c
index 2b2ee3a8a..c77543fd0 100644
--- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c
+++ b/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,20 @@ 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 +250,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) {
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_text_input.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_text_input.c
index b721e868d..e6091a410 100644
--- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_text_input.c
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_text_input.c
@@ -44,24 +44,24 @@ void wifi_marauder_scene_text_input_on_enter(void* context) {
}
// Setup view
- TextInput* text_input = app->text_input;
+ WIFI_TextInput* text_input = app->text_input;
// Add help message to header
if(app->special_case_input_step == 1) {
- text_input_set_header_text(text_input, "Enter source MAC");
+ wifi_text_input_set_header_text(text_input, "Enter source MAC");
} else if(0 == strncmp("ssid -a -g", app->selected_tx_string, strlen("ssid -a -g"))) {
- text_input_set_header_text(text_input, "Enter # SSIDs to generate");
+ wifi_text_input_set_header_text(text_input, "Enter # SSIDs to generate");
} else if(0 == strncmp("ssid -a -n", app->selected_tx_string, strlen("ssid -a -n"))) {
- text_input_set_header_text(text_input, "Enter SSID name to add");
+ wifi_text_input_set_header_text(text_input, "Enter SSID name to add");
} else if(0 == strncmp("ssid -r", app->selected_tx_string, strlen("ssid -r"))) {
- text_input_set_header_text(text_input, "Remove target from SSID list");
+ wifi_text_input_set_header_text(text_input, "Remove target from SSID list");
} else if(0 == strncmp("select -a", app->selected_tx_string, strlen("select -a"))) {
- text_input_set_header_text(text_input, "Add target from AP list");
+ wifi_text_input_set_header_text(text_input, "Add target from AP list");
} else if(0 == strncmp("select -s", app->selected_tx_string, strlen("select -s"))) {
- text_input_set_header_text(text_input, "Add target from SSID list");
+ wifi_text_input_set_header_text(text_input, "Add target from SSID list");
} else {
- text_input_set_header_text(text_input, "Add command arguments");
+ wifi_text_input_set_header_text(text_input, "Add command arguments");
}
- text_input_set_result_callback(
+ wifi_text_input_set_result_callback(
text_input,
wifi_marauder_scene_text_input_callback,
app,
@@ -84,7 +84,7 @@ bool wifi_marauder_scene_text_input_on_event(void* context, SceneManagerEvent ev
consumed = true;
} else if(event.event == WifiMarauderEventSaveSourceMac) {
if(12 != strlen(app->text_input_store)) {
- text_input_set_header_text(app->text_input, "MAC must be 12 hex chars!");
+ wifi_text_input_set_header_text(app->text_input, "MAC must be 12 hex chars!");
} else {
snprintf(
app->special_case_input_src_addr,
@@ -106,12 +106,12 @@ bool wifi_marauder_scene_text_input_on_event(void* context, SceneManagerEvent ev
// Advance scene to input destination MAC, clear text input
app->special_case_input_step = 2;
bzero(app->text_input_store, WIFI_MARAUDER_TEXT_INPUT_STORE_SIZE);
- text_input_set_header_text(app->text_input, "Enter destination MAC");
+ wifi_text_input_set_header_text(app->text_input, "Enter destination MAC");
}
consumed = true;
} else if(event.event == WifiMarauderEventSaveDestinationMac) {
if(12 != strlen(app->text_input_store)) {
- text_input_set_header_text(app->text_input, "MAC must be 12 hex chars!");
+ wifi_text_input_set_header_text(app->text_input, "MAC must be 12 hex chars!");
} else {
snprintf(
app->special_case_input_dst_addr,
@@ -150,5 +150,5 @@ bool wifi_marauder_scene_text_input_on_event(void* context, SceneManagerEvent ev
void wifi_marauder_scene_text_input_on_exit(void* context) {
WifiMarauderApp* app = context;
- text_input_reset(app->text_input);
+ wifi_text_input_reset(app->text_input);
}
diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_user_input.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_user_input.c
new file mode 100644
index 000000000..3d5697caf
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_user_input.c
@@ -0,0 +1,155 @@
+#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:
+ wifi_text_input_set_header_text(app->text_input, "Enter value:");
+ wifi_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:
+ wifi_text_input_set_header_text(app->text_input, "Enter a valid number:");
+ wifi_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:
+ wifi_text_input_set_header_text(app->text_input, "Enter file name:");
+ wifi_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;
+ }
+
+ wifi_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));
+ wifi_text_input_reset(app->text_input);
+}
diff --git a/applications/external/wifi_marauder_companion/script/cJSON.c b/applications/external/wifi_marauder_companion/script/cJSON.c
new file mode 100644
index 000000000..06341fe38
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/cJSON.c
@@ -0,0 +1,2743 @@
+/*
+ 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
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef ENABLE_LOCALES
+#include
+#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);
+}
diff --git a/applications/external/wifi_marauder_companion/script/cJSON.h b/applications/external/wifi_marauder_companion/script/cJSON.h
new file mode 100644
index 000000000..14ec83d9d
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/cJSON.h
@@ -0,0 +1,321 @@
+/*
+ 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
+
+/* 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
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu.c
new file mode 100644
index 000000000..6fe853eb6
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu.c
@@ -0,0 +1,32 @@
+#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);
+}
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu.h b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu.h
new file mode 100644
index 000000000..f5186526c
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include
+#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);
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_beaconap.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_beaconap.c
new file mode 100644
index 000000000..35a74ee3d
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_beaconap.c
@@ -0,0 +1,27 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_beaconlist.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_beaconlist.c
new file mode 100644
index 000000000..6f320db3e
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_beaconlist.c
@@ -0,0 +1,59 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_config.h b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_config.h
new file mode 100644
index 000000000..1fd2a314b
--- /dev/null
+++ b/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)
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_deauth.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_deauth.c
new file mode 100644
index 000000000..b15b6f461
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_deauth.c
@@ -0,0 +1,27 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_delay.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_delay.c
new file mode 100644
index 000000000..ffd74f720
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_delay.c
@@ -0,0 +1,27 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_exec.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_exec.c
new file mode 100644
index 000000000..62afdc2f3
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_exec.c
@@ -0,0 +1,30 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_probe.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_probe.c
new file mode 100644
index 000000000..53fa26f47
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_probe.c
@@ -0,0 +1,27 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_scan.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_scan.c
new file mode 100644
index 000000000..3aab740bb
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_scan.c
@@ -0,0 +1,93 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_select.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_select.c
new file mode 100644
index 000000000..a6121db95
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_select.c
@@ -0,0 +1,95 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffbeacon.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffbeacon.c
new file mode 100644
index 000000000..11e7b3297
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffbeacon.c
@@ -0,0 +1,27 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffdeauth.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffdeauth.c
new file mode 100644
index 000000000..935a55936
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffdeauth.c
@@ -0,0 +1,27 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffesp.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffesp.c
new file mode 100644
index 000000000..e90d6b06c
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffesp.c
@@ -0,0 +1,27 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffpmkid.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffpmkid.c
new file mode 100644
index 000000000..d4f1f8f36
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffpmkid.c
@@ -0,0 +1,91 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffpwn.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffpwn.c
new file mode 100644
index 000000000..d0859cd8b
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffpwn.c
@@ -0,0 +1,27 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffraw.c b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffraw.c
new file mode 100644
index 000000000..39641f1ee
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/menu/wifi_marauder_script_stage_menu_sniffraw.c
@@ -0,0 +1,27 @@
+#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};
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/wifi_marauder_script.c b/applications/external/wifi_marauder_companion/script/wifi_marauder_script.c
new file mode 100644
index 000000000..64dfacef5
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/wifi_marauder_script.c
@@ -0,0 +1,947 @@
+#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);
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/wifi_marauder_script.h b/applications/external/wifi_marauder_companion/script/wifi_marauder_script.h
new file mode 100644
index 000000000..e11ee267f
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/wifi_marauder_script.h
@@ -0,0 +1,257 @@
+/*
+ * ----------------------------------------------------------------------------------------------------
+ * 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
+#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);
diff --git a/applications/external/wifi_marauder_companion/script/wifi_marauder_script_executor.c b/applications/external/wifi_marauder_companion/script/wifi_marauder_script_executor.c
new file mode 100644
index 000000000..d7799c300
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/wifi_marauder_script_executor.c
@@ -0,0 +1,307 @@
+#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;
+ }
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/wifi_marauder_script_executor.h b/applications/external/wifi_marauder_companion/script/wifi_marauder_script_executor.h
new file mode 100644
index 000000000..654712849
--- /dev/null
+++ b/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);
diff --git a/applications/external/wifi_marauder_companion/script/wifi_marauder_script_worker.c b/applications/external/wifi_marauder_companion/script/wifi_marauder_script_worker.c
new file mode 100644
index 000000000..45c5b56ba
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/wifi_marauder_script_worker.c
@@ -0,0 +1,74 @@
+#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);
+ }
+}
\ No newline at end of file
diff --git a/applications/external/wifi_marauder_companion/script/wifi_marauder_script_worker.h b/applications/external/wifi_marauder_companion/script/wifi_marauder_script_worker.h
new file mode 100644
index 000000000..76ff070d2
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/script/wifi_marauder_script_worker.h
@@ -0,0 +1,43 @@
+#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);
diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app.c b/applications/external/wifi_marauder_companion/wifi_marauder_app.c
index 42e94a8b8..821987a8a 100644
--- a/applications/external/wifi_marauder_companion/wifi_marauder_app.c
+++ b/applications/external/wifi_marauder_companion/wifi_marauder_app.c
@@ -66,9 +66,11 @@ WifiMarauderApp* wifi_marauder_app_alloc() {
app->text_box_store = furi_string_alloc();
furi_string_reserve(app->text_box_store, WIFI_MARAUDER_TEXT_BOX_STORE_SIZE);
- app->text_input = text_input_alloc();
+ app->text_input = wifi_text_input_alloc();
view_dispatcher_add_view(
- app->view_dispatcher, WifiMarauderAppViewTextInput, text_input_get_view(app->text_input));
+ app->view_dispatcher,
+ WifiMarauderAppViewTextInput,
+ wifi_text_input_get_view(app->text_input));
app->widget = widget_alloc();
view_dispatcher_add_view(
@@ -81,6 +83,11 @@ 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;
@@ -100,6 +107,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) {
@@ -134,10 +145,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);
+ wifi_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);
diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h b/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h
index 39b48fae3..2a16522bb 100644
--- a/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h
+++ b/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
#include
#include
#include
-#include
+#include
#include
#include
+#include "wifi_marauder_text_input.h"
+#include
#include
+#include
#include
-#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;
@@ -42,7 +57,7 @@ struct WifiMarauderApp {
FuriString* text_box_store;
size_t text_box_store_strlen;
TextBox* text_box;
- TextInput* text_input;
+ WIFI_TextInput* text_input;
Storage* storage;
File* capture_file;
File* log_file;
@@ -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;
diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h b/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h
index 79f96b107..5acdfa38e 100644
--- a/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h
+++ b/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h
@@ -7,5 +7,6 @@ typedef enum {
WifiMarauderEventSaveSourceMac,
WifiMarauderEventSaveDestinationMac,
WifiMarauderEventStartSettingsInit,
- WifiMarauderEventStartLogViewer
+ WifiMarauderEventStartLogViewer,
+ WifiMarauderEventStartScriptSelect
} WifiMarauderCustomEvent;
diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_text_input.c b/applications/external/wifi_marauder_companion/wifi_marauder_text_input.c
new file mode 100644
index 000000000..e17e5aaee
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/wifi_marauder_text_input.c
@@ -0,0 +1,616 @@
+#include "wifi_marauder_text_input.h"
+#include
+#include "ESP32_WiFi_Marauder_icons.h"
+#include "wifi_marauder_app_i.h"
+#include
+
+struct WIFI_TextInput {
+ View* view;
+ FuriTimer* timer;
+};
+
+typedef struct {
+ const char text;
+ const uint8_t x;
+ const uint8_t y;
+} WIFI_TextInputKey;
+
+typedef struct {
+ const char* header;
+ char* text_buffer;
+ size_t text_buffer_size;
+ bool clear_default_text;
+
+ WIFI_TextInputCallback callback;
+ void* callback_context;
+
+ uint8_t selected_row;
+ uint8_t selected_column;
+
+ WIFI_TextInputValidatorCallback validator_callback;
+ void* validator_callback_context;
+ FuriString* validator_text;
+ bool valadator_message_visible;
+} WIFI_TextInputModel;
+
+static const uint8_t keyboard_origin_x = 1;
+static const uint8_t keyboard_origin_y = 29;
+static const uint8_t keyboard_row_count = 4;
+
+#define ENTER_KEY '\r'
+#define BACKSPACE_KEY '\b'
+
+static const WIFI_TextInputKey keyboard_keys_row_1[] = {
+ {'{', 1, 0},
+ {'(', 9, 0},
+ {'[', 17, 0},
+ {'|', 25, 0},
+ {'@', 33, 0},
+ {'&', 41, 0},
+ {'#', 49, 0},
+ {';', 57, 0},
+ {'^', 65, 0},
+ {'*', 73, 0},
+ {'`', 81, 0},
+ {'"', 89, 0},
+ {'~', 97, 0},
+ {'\'', 105, 0},
+ {'.', 113, 0},
+ {'/', 120, 0},
+};
+
+static const WIFI_TextInputKey keyboard_keys_row_2[] = {
+ {'q', 1, 10},
+ {'w', 9, 10},
+ {'e', 17, 10},
+ {'r', 25, 10},
+ {'t', 33, 10},
+ {'y', 41, 10},
+ {'u', 49, 10},
+ {'i', 57, 10},
+ {'o', 65, 10},
+ {'p', 73, 10},
+ {'0', 81, 10},
+ {'1', 89, 10},
+ {'2', 97, 10},
+ {'3', 105, 10},
+ {'=', 113, 10},
+ {'-', 120, 10},
+};
+
+static const WIFI_TextInputKey keyboard_keys_row_3[] = {
+ {'a', 1, 21},
+ {'s', 9, 21},
+ {'d', 18, 21},
+ {'f', 25, 21},
+ {'g', 33, 21},
+ {'h', 41, 21},
+ {'j', 49, 21},
+ {'k', 57, 21},
+ {'l', 65, 21},
+ {BACKSPACE_KEY, 72, 13},
+ {'4', 89, 21},
+ {'5', 97, 21},
+ {'6', 105, 21},
+ {'$', 113, 21},
+ {'%', 120, 21},
+
+};
+
+static const WIFI_TextInputKey keyboard_keys_row_4[] = {
+ {'z', 1, 33},
+ {'x', 9, 33},
+ {'c', 18, 33},
+ {'v', 25, 33},
+ {'b', 33, 33},
+ {'n', 41, 33},
+ {'m', 49, 33},
+ {'_', 57, 33},
+ {ENTER_KEY, 64, 24},
+ {'7', 89, 33},
+ {'8', 97, 33},
+ {'9', 105, 33},
+ {'!', 113, 33},
+ {'+', 120, 33},
+};
+
+static uint8_t get_row_size(uint8_t row_index) {
+ uint8_t row_size = 0;
+
+ switch(row_index + 1) {
+ case 1:
+ row_size = sizeof(keyboard_keys_row_1) / sizeof(WIFI_TextInputKey);
+ break;
+ case 2:
+ row_size = sizeof(keyboard_keys_row_2) / sizeof(WIFI_TextInputKey);
+ break;
+ case 3:
+ row_size = sizeof(keyboard_keys_row_3) / sizeof(WIFI_TextInputKey);
+ break;
+ case 4:
+ row_size = sizeof(keyboard_keys_row_4) / sizeof(WIFI_TextInputKey);
+ break;
+ }
+
+ return row_size;
+}
+
+static const WIFI_TextInputKey* get_row(uint8_t row_index) {
+ const WIFI_TextInputKey* row = NULL;
+
+ switch(row_index + 1) {
+ case 1:
+ row = keyboard_keys_row_1;
+ break;
+ case 2:
+ row = keyboard_keys_row_2;
+ break;
+ case 3:
+ row = keyboard_keys_row_3;
+ break;
+ case 4:
+ row = keyboard_keys_row_4;
+ break;
+ }
+
+ return row;
+}
+
+static char get_selected_char(WIFI_TextInputModel* model) {
+ return get_row(model->selected_row)[model->selected_column].text;
+}
+
+static bool char_is_lowercase(char letter) {
+ return (letter >= 0x61 && letter <= 0x7A);
+}
+
+static char char_to_uppercase(const char letter) {
+ switch(letter) {
+ case '_':
+ return 0x20;
+ case '(':
+ return 0x29;
+ case '{':
+ return 0x7d;
+ case '[':
+ return 0x5d;
+ case '/':
+ return 0x5c;
+ case ';':
+ return 0x3a;
+ case '.':
+ return 0x2c;
+ case '!':
+ return 0x3f;
+ case '<':
+ return 0x3e;
+ }
+ if(char_is_lowercase(letter)) {
+ return (letter - 0x20);
+ } else {
+ return letter;
+ }
+}
+
+static void wifi_text_input_backspace_cb(WIFI_TextInputModel* model) {
+ uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer);
+ if(text_length > 0) {
+ model->text_buffer[text_length - 1] = 0;
+ }
+}
+
+static void wifi_text_input_view_draw_callback(Canvas* canvas, void* _model) {
+ WIFI_TextInputModel* model = _model;
+ //uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
+ uint8_t needed_string_width = canvas_width(canvas) - 8;
+ uint8_t start_pos = 4;
+
+ const char* text = model->text_buffer;
+
+ canvas_clear(canvas);
+ canvas_set_color(canvas, ColorBlack);
+
+ canvas_draw_str(canvas, 2, 7, model->header);
+ elements_slightly_rounded_frame(canvas, 1, 8, 126, 12);
+
+ if(canvas_string_width(canvas, text) > needed_string_width) {
+ canvas_draw_str(canvas, start_pos, 17, "...");
+ start_pos += 6;
+ needed_string_width -= 8;
+ }
+
+ while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) {
+ text++;
+ }
+
+ if(model->clear_default_text) {
+ elements_slightly_rounded_box(
+ canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10);
+ canvas_set_color(canvas, ColorWhite);
+ } else {
+ canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 18, "|");
+ canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 18, "|");
+ }
+ canvas_draw_str(canvas, start_pos, 17, text);
+
+ canvas_set_font(canvas, FontKeyboard);
+
+ for(uint8_t row = 0; row <= keyboard_row_count; row++) {
+ const uint8_t column_count = get_row_size(row);
+ const WIFI_TextInputKey* keys = get_row(row);
+
+ for(size_t column = 0; column < column_count; column++) {
+ if(keys[column].text == ENTER_KEY) {
+ canvas_set_color(canvas, ColorBlack);
+ if(model->selected_row == row && model->selected_column == column) {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeySaveSelected_24x11);
+ } else {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeySave_24x11);
+ }
+ } else if(keys[column].text == BACKSPACE_KEY) {
+ canvas_set_color(canvas, ColorBlack);
+ if(model->selected_row == row && model->selected_column == column) {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeyBackspaceSelected_16x9);
+ } else {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeyBackspace_16x9);
+ }
+ } else {
+ if(model->selected_row == row && model->selected_column == column) {
+ canvas_set_color(canvas, ColorBlack);
+ canvas_draw_box(
+ canvas,
+ keyboard_origin_x + keys[column].x - 1,
+ keyboard_origin_y + keys[column].y - 8,
+ 7,
+ 10);
+ canvas_set_color(canvas, ColorWhite);
+ } else {
+ canvas_set_color(canvas, ColorBlack);
+ }
+
+ canvas_draw_glyph(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ keys[column].text);
+ }
+ }
+ }
+ if(model->valadator_message_visible) {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_set_color(canvas, ColorWhite);
+ canvas_draw_box(canvas, 8, 10, 110, 48);
+ canvas_set_color(canvas, ColorBlack);
+ canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42);
+ canvas_draw_rframe(canvas, 8, 8, 112, 50, 3);
+ canvas_draw_rframe(canvas, 9, 9, 110, 48, 2);
+ elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text));
+ canvas_set_font(canvas, FontKeyboard);
+ }
+}
+
+static void
+ wifi_text_input_handle_up(WIFI_TextInput* wifi_text_input, WIFI_TextInputModel* model) {
+ UNUSED(wifi_text_input);
+ if(model->selected_row > 0) {
+ model->selected_row--;
+ if(model->selected_column > get_row_size(model->selected_row) - 6) {
+ model->selected_column = model->selected_column + 1;
+ }
+ }
+}
+
+static void
+ wifi_text_input_handle_down(WIFI_TextInput* wifi_text_input, WIFI_TextInputModel* model) {
+ UNUSED(wifi_text_input);
+ if(model->selected_row < keyboard_row_count - 1) {
+ model->selected_row++;
+ if(model->selected_column > get_row_size(model->selected_row) - 4) {
+ model->selected_column = model->selected_column - 1;
+ }
+ }
+}
+
+static void
+ wifi_text_input_handle_left(WIFI_TextInput* wifi_text_input, WIFI_TextInputModel* model) {
+ UNUSED(wifi_text_input);
+ if(model->selected_column > 0) {
+ model->selected_column--;
+ } else {
+ model->selected_column = get_row_size(model->selected_row) - 1;
+ }
+}
+
+static void
+ wifi_text_input_handle_right(WIFI_TextInput* wifi_text_input, WIFI_TextInputModel* model) {
+ UNUSED(wifi_text_input);
+ if(model->selected_column < get_row_size(model->selected_row) - 1) {
+ model->selected_column++;
+ } else {
+ model->selected_column = 0;
+ }
+}
+
+static void wifi_text_input_handle_ok(
+ WIFI_TextInput* wifi_text_input,
+ WIFI_TextInputModel* model,
+ bool shift) {
+ char selected = get_selected_char(model);
+ uint8_t text_length = strlen(model->text_buffer);
+
+ if(shift) {
+ selected = char_to_uppercase(selected);
+ }
+
+ if(selected == ENTER_KEY) {
+ if(model->validator_callback &&
+ (!model->validator_callback(
+ model->text_buffer, model->validator_text, model->validator_callback_context))) {
+ model->valadator_message_visible = true;
+ furi_timer_start(wifi_text_input->timer, furi_kernel_get_tick_frequency() * 4);
+ } else if(model->callback != 0 && text_length > 0) {
+ model->callback(model->callback_context);
+ }
+ } else if(selected == BACKSPACE_KEY) {
+ wifi_text_input_backspace_cb(model);
+ } else {
+ if(model->clear_default_text) {
+ text_length = 0;
+ }
+ if(text_length < (model->text_buffer_size - 1)) {
+ model->text_buffer[text_length] = selected;
+ model->text_buffer[text_length + 1] = 0;
+ }
+ }
+ model->clear_default_text = false;
+}
+
+static bool wifi_text_input_view_input_callback(InputEvent* event, void* context) {
+ WIFI_TextInput* wifi_text_input = context;
+ furi_assert(wifi_text_input);
+
+ bool consumed = false;
+
+ // Acquire model
+ WIFI_TextInputModel* model = view_get_model(wifi_text_input->view);
+
+ if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) &&
+ model->valadator_message_visible) {
+ model->valadator_message_visible = false;
+ consumed = true;
+ } else if(event->type == InputTypeShort) {
+ consumed = true;
+ switch(event->key) {
+ case InputKeyUp:
+ wifi_text_input_handle_up(wifi_text_input, model);
+ break;
+ case InputKeyDown:
+ wifi_text_input_handle_down(wifi_text_input, model);
+ break;
+ case InputKeyLeft:
+ wifi_text_input_handle_left(wifi_text_input, model);
+ break;
+ case InputKeyRight:
+ wifi_text_input_handle_right(wifi_text_input, model);
+ break;
+ case InputKeyOk:
+ wifi_text_input_handle_ok(wifi_text_input, model, false);
+ break;
+ default:
+ consumed = false;
+ break;
+ }
+ } else if(event->type == InputTypeLong) {
+ consumed = true;
+ switch(event->key) {
+ case InputKeyUp:
+ wifi_text_input_handle_up(wifi_text_input, model);
+ break;
+ case InputKeyDown:
+ wifi_text_input_handle_down(wifi_text_input, model);
+ break;
+ case InputKeyLeft:
+ wifi_text_input_handle_left(wifi_text_input, model);
+ break;
+ case InputKeyRight:
+ wifi_text_input_handle_right(wifi_text_input, model);
+ break;
+ case InputKeyOk:
+ wifi_text_input_handle_ok(wifi_text_input, model, true);
+ break;
+ case InputKeyBack:
+ wifi_text_input_backspace_cb(model);
+ break;
+ default:
+ consumed = false;
+ break;
+ }
+ } else if(event->type == InputTypeRepeat) {
+ consumed = true;
+ switch(event->key) {
+ case InputKeyUp:
+ wifi_text_input_handle_up(wifi_text_input, model);
+ break;
+ case InputKeyDown:
+ wifi_text_input_handle_down(wifi_text_input, model);
+ break;
+ case InputKeyLeft:
+ wifi_text_input_handle_left(wifi_text_input, model);
+ break;
+ case InputKeyRight:
+ wifi_text_input_handle_right(wifi_text_input, model);
+ break;
+ case InputKeyBack:
+ wifi_text_input_backspace_cb(model);
+ break;
+ default:
+ consumed = false;
+ break;
+ }
+ }
+
+ // Commit model
+ view_commit_model(wifi_text_input->view, consumed);
+
+ return consumed;
+}
+
+void wifi_text_input_timer_callback(void* context) {
+ furi_assert(context);
+ WIFI_TextInput* wifi_text_input = context;
+
+ with_view_model(
+ wifi_text_input->view,
+ WIFI_TextInputModel * model,
+ { model->valadator_message_visible = false; },
+ true);
+}
+
+WIFI_TextInput* wifi_text_input_alloc() {
+ WIFI_TextInput* wifi_text_input = malloc(sizeof(WIFI_TextInput));
+ wifi_text_input->view = view_alloc();
+ view_set_context(wifi_text_input->view, wifi_text_input);
+ view_allocate_model(wifi_text_input->view, ViewModelTypeLocking, sizeof(WIFI_TextInputModel));
+ view_set_draw_callback(wifi_text_input->view, wifi_text_input_view_draw_callback);
+ view_set_input_callback(wifi_text_input->view, wifi_text_input_view_input_callback);
+
+ wifi_text_input->timer =
+ furi_timer_alloc(wifi_text_input_timer_callback, FuriTimerTypeOnce, wifi_text_input);
+
+ with_view_model(
+ wifi_text_input->view,
+ WIFI_TextInputModel * model,
+ { model->validator_text = furi_string_alloc(); },
+ false);
+
+ wifi_text_input_reset(wifi_text_input);
+
+ return wifi_text_input;
+}
+
+void wifi_text_input_free(WIFI_TextInput* wifi_text_input) {
+ furi_assert(wifi_text_input);
+ with_view_model(
+ wifi_text_input->view,
+ WIFI_TextInputModel * model,
+ { furi_string_free(model->validator_text); },
+ false);
+
+ // Send stop command
+ furi_timer_stop(wifi_text_input->timer);
+ // Release allocated memory
+ furi_timer_free(wifi_text_input->timer);
+
+ view_free(wifi_text_input->view);
+
+ free(wifi_text_input);
+}
+
+void wifi_text_input_reset(WIFI_TextInput* wifi_text_input) {
+ furi_assert(wifi_text_input);
+ with_view_model(
+ wifi_text_input->view,
+ WIFI_TextInputModel * model,
+ {
+ model->text_buffer_size = 0;
+ model->header = "";
+ model->selected_row = 0;
+ model->selected_column = 0;
+ model->clear_default_text = false;
+ model->text_buffer = NULL;
+ model->text_buffer_size = 0;
+ model->callback = NULL;
+ model->callback_context = NULL;
+ model->validator_callback = NULL;
+ model->validator_callback_context = NULL;
+ furi_string_reset(model->validator_text);
+ model->valadator_message_visible = false;
+ },
+ true);
+}
+
+View* wifi_text_input_get_view(WIFI_TextInput* wifi_text_input) {
+ furi_assert(wifi_text_input);
+ return wifi_text_input->view;
+}
+
+void wifi_text_input_set_result_callback(
+ WIFI_TextInput* wifi_text_input,
+ WIFI_TextInputCallback callback,
+ void* callback_context,
+ char* text_buffer,
+ size_t text_buffer_size,
+ bool clear_default_text) {
+ with_view_model(
+ wifi_text_input->view,
+ WIFI_TextInputModel * model,
+ {
+ model->callback = callback;
+ model->callback_context = callback_context;
+ model->text_buffer = text_buffer;
+ model->text_buffer_size = text_buffer_size;
+ model->clear_default_text = clear_default_text;
+ if(text_buffer && text_buffer[0] != '\0') {
+ // Set focus on Save
+ model->selected_row = 2;
+ model->selected_column = 8;
+ }
+ },
+ true);
+}
+
+void wifi_text_input_set_validator(
+ WIFI_TextInput* wifi_text_input,
+ WIFI_TextInputValidatorCallback callback,
+ void* callback_context) {
+ with_view_model(
+ wifi_text_input->view,
+ WIFI_TextInputModel * model,
+ {
+ model->validator_callback = callback;
+ model->validator_callback_context = callback_context;
+ },
+ true);
+}
+
+WIFI_TextInputValidatorCallback
+ wifi_text_input_get_validator_callback(WIFI_TextInput* wifi_text_input) {
+ WIFI_TextInputValidatorCallback validator_callback = NULL;
+ with_view_model(
+ wifi_text_input->view,
+ WIFI_TextInputModel * model,
+ { validator_callback = model->validator_callback; },
+ false);
+ return validator_callback;
+}
+
+void* wifi_text_input_get_validator_callback_context(WIFI_TextInput* wifi_text_input) {
+ void* validator_callback_context = NULL;
+ with_view_model(
+ wifi_text_input->view,
+ WIFI_TextInputModel * model,
+ { validator_callback_context = model->validator_callback_context; },
+ false);
+ return validator_callback_context;
+}
+
+void wifi_text_input_set_header_text(WIFI_TextInput* wifi_text_input, const char* text) {
+ with_view_model(
+ wifi_text_input->view, WIFI_TextInputModel * model, { model->header = text; }, true);
+}
diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_text_input.h b/applications/external/wifi_marauder_companion/wifi_marauder_text_input.h
new file mode 100644
index 000000000..b6b1f7bdf
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/wifi_marauder_text_input.h
@@ -0,0 +1,82 @@
+#pragma once
+
+#include
+#include "wifi_marauder_validators.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Text input anonymous structure */
+typedef struct WIFI_TextInput WIFI_TextInput;
+typedef void (*WIFI_TextInputCallback)(void* context);
+typedef bool (*WIFI_TextInputValidatorCallback)(const char* text, FuriString* error, void* context);
+
+/** Allocate and initialize text input
+ *
+ * This text input is used to enter string
+ *
+ * @return WIFI_TextInput instance
+ */
+WIFI_TextInput* wifi_text_input_alloc();
+
+/** Deinitialize and free text input
+ *
+ * @param wifi_text_input WIFI_TextInput instance
+ */
+void wifi_text_input_free(WIFI_TextInput* wifi_text_input);
+
+/** Clean text input view Note: this function does not free memory
+ *
+ * @param wifi_text_input Text input instance
+ */
+void wifi_text_input_reset(WIFI_TextInput* wifi_text_input);
+
+/** Get text input view
+ *
+ * @param wifi_text_input WIFI_TextInput instance
+ *
+ * @return View instance that can be used for embedding
+ */
+View* wifi_text_input_get_view(WIFI_TextInput* wifi_text_input);
+
+/** Set text input result callback
+ *
+ * @param wifi_text_input WIFI_TextInput instance
+ * @param callback callback fn
+ * @param callback_context callback context
+ * @param text_buffer pointer to YOUR text buffer, that we going
+ * to modify
+ * @param text_buffer_size YOUR text buffer size in bytes. Max string
+ * length will be text_buffer_size-1.
+ * @param clear_default_text clear text from text_buffer on first OK
+ * event
+ */
+void wifi_text_input_set_result_callback(
+ WIFI_TextInput* wifi_text_input,
+ WIFI_TextInputCallback callback,
+ void* callback_context,
+ char* text_buffer,
+ size_t text_buffer_size,
+ bool clear_default_text);
+
+void wifi_text_input_set_validator(
+ WIFI_TextInput* wifi_text_input,
+ WIFI_TextInputValidatorCallback callback,
+ void* callback_context);
+
+WIFI_TextInputValidatorCallback
+ wifi_text_input_get_validator_callback(WIFI_TextInput* wifi_text_input);
+
+void* wifi_text_input_get_validator_callback_context(WIFI_TextInput* wifi_text_input);
+
+/** Set text input header text
+ *
+ * @param wifi_text_input WIFI_TextInput instance
+ * @param text text to be shown
+ */
+void wifi_text_input_set_header_text(WIFI_TextInput* wifi_text_input, const char* text);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_validators.c b/applications/external/wifi_marauder_companion/wifi_marauder_validators.c
new file mode 100644
index 000000000..5bec88269
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/wifi_marauder_validators.c
@@ -0,0 +1,57 @@
+#include
+#include "wifi_marauder_validators.h"
+#include
+
+struct ValidatorIsFile {
+ char* app_path_folder;
+ const char* app_extension;
+ char* current_name;
+};
+
+bool validator_is_file_callback(const char* text, FuriString* error, void* context) {
+ furi_assert(context);
+ ValidatorIsFile* instance = context;
+
+ if(instance->current_name != NULL) {
+ if(strcmp(instance->current_name, text) == 0) {
+ return true;
+ }
+ }
+
+ bool ret = true;
+ FuriString* path = furi_string_alloc_printf(
+ "%s/%s%s", instance->app_path_folder, text, instance->app_extension);
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ if(storage_common_stat(storage, furi_string_get_cstr(path), NULL) == FSE_OK) {
+ ret = false;
+ furi_string_printf(error, "This name\nexists!\nChoose\nanother one.");
+ } else {
+ ret = true;
+ }
+ furi_string_free(path);
+ furi_record_close(RECORD_STORAGE);
+
+ return ret;
+}
+
+ValidatorIsFile* validator_is_file_alloc_init(
+ const char* app_path_folder,
+ const char* app_extension,
+ const char* current_name) {
+ ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile));
+
+ instance->app_path_folder = strdup(app_path_folder);
+ instance->app_extension = app_extension;
+ if(current_name != NULL) {
+ instance->current_name = strdup(current_name);
+ }
+
+ return instance;
+}
+
+void validator_is_file_free(ValidatorIsFile* instance) {
+ furi_assert(instance);
+ free(instance->app_path_folder);
+ free(instance->current_name);
+ free(instance);
+}
diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_validators.h b/applications/external/wifi_marauder_companion/wifi_marauder_validators.h
new file mode 100644
index 000000000..d9200b6db
--- /dev/null
+++ b/applications/external/wifi_marauder_companion/wifi_marauder_validators.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+typedef struct ValidatorIsFile ValidatorIsFile;
+
+ValidatorIsFile* validator_is_file_alloc_init(
+ const char* app_path_folder,
+ const char* app_extension,
+ const char* current_name);
+
+void validator_is_file_free(ValidatorIsFile* instance);
+
+bool validator_is_file_callback(const char* text, FuriString* error, void* context);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/applications/external/wii_ec_anal/LICENSE b/applications/external/wiiec/LICENSE
similarity index 100%
rename from applications/external/wii_ec_anal/LICENSE
rename to applications/external/wiiec/LICENSE
diff --git a/applications/external/wii_ec_anal/WiiEC.png b/applications/external/wiiec/WiiEC.png
similarity index 100%
rename from applications/external/wii_ec_anal/WiiEC.png
rename to applications/external/wiiec/WiiEC.png
diff --git a/applications/external/wii_ec_anal/_image_tool/LICENSE b/applications/external/wiiec/_image_tool/LICENSE
similarity index 100%
rename from applications/external/wii_ec_anal/_image_tool/LICENSE
rename to applications/external/wiiec/_image_tool/LICENSE
diff --git a/applications/external/wii_ec_anal/_image_tool/README b/applications/external/wiiec/_image_tool/README
similarity index 100%
rename from applications/external/wii_ec_anal/_image_tool/README
rename to applications/external/wiiec/_image_tool/README
diff --git a/applications/external/wii_ec_anal/_image_tool/_convert.c b/applications/external/wiiec/_image_tool/_convert.c
similarity index 100%
rename from applications/external/wii_ec_anal/_image_tool/_convert.c
rename to applications/external/wiiec/_image_tool/_convert.c
diff --git a/applications/external/wii_ec_anal/_image_tool/_convert.sh b/applications/external/wiiec/_image_tool/_convert.sh
similarity index 100%
rename from applications/external/wii_ec_anal/_image_tool/_convert.sh
rename to applications/external/wiiec/_image_tool/_convert.sh
diff --git a/applications/external/wii_ec_anal/_image_tool/_convert_images.c b/applications/external/wiiec/_image_tool/_convert_images.c
similarity index 100%
rename from applications/external/wii_ec_anal/_image_tool/_convert_images.c
rename to applications/external/wiiec/_image_tool/_convert_images.c
diff --git a/applications/external/wii_ec_anal/_image_tool/_convert_images.h b/applications/external/wiiec/_image_tool/_convert_images.h
similarity index 100%
rename from applications/external/wii_ec_anal/_image_tool/_convert_images.h
rename to applications/external/wiiec/_image_tool/_convert_images.h
diff --git a/applications/external/wii_ec_anal/_image_tool/_convert_test.c b/applications/external/wiiec/_image_tool/_convert_test.c
similarity index 100%
rename from applications/external/wii_ec_anal/_image_tool/_convert_test.c
rename to applications/external/wiiec/_image_tool/_convert_test.c
diff --git a/applications/external/wii_ec_anal/application.fam b/applications/external/wiiec/application.fam
similarity index 100%
rename from applications/external/wii_ec_anal/application.fam
rename to applications/external/wiiec/application.fam
diff --git a/applications/external/wii_ec_anal/bc_logging.h b/applications/external/wiiec/bc_logging.h
similarity index 100%
rename from applications/external/wii_ec_anal/bc_logging.h
rename to applications/external/wiiec/bc_logging.h
diff --git a/applications/external/wii_ec_anal/err.h b/applications/external/wiiec/err.h
similarity index 100%
rename from applications/external/wii_ec_anal/err.h
rename to applications/external/wiiec/err.h
diff --git a/applications/external/wii_ec_anal/gfx/images.c b/applications/external/wiiec/gfx/images.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/images.c
rename to applications/external/wiiec/gfx/images.c
diff --git a/applications/external/wii_ec_anal/gfx/images.h b/applications/external/wiiec/gfx/images.h
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/images.h
rename to applications/external/wiiec/gfx/images.h
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_0.c b/applications/external/wiiec/gfx/img_3x5_0.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_0.c
rename to applications/external/wiiec/gfx/img_3x5_0.c
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_1.c b/applications/external/wiiec/gfx/img_3x5_1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_1.c
rename to applications/external/wiiec/gfx/img_3x5_1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_2.c b/applications/external/wiiec/gfx/img_3x5_2.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_2.c
rename to applications/external/wiiec/gfx/img_3x5_2.c
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_3.c b/applications/external/wiiec/gfx/img_3x5_3.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_3.c
rename to applications/external/wiiec/gfx/img_3x5_3.c
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_4.c b/applications/external/wiiec/gfx/img_3x5_4.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_4.c
rename to applications/external/wiiec/gfx/img_3x5_4.c
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_5.c b/applications/external/wiiec/gfx/img_3x5_5.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_5.c
rename to applications/external/wiiec/gfx/img_3x5_5.c
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_6.c b/applications/external/wiiec/gfx/img_3x5_6.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_6.c
rename to applications/external/wiiec/gfx/img_3x5_6.c
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_7.c b/applications/external/wiiec/gfx/img_3x5_7.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_7.c
rename to applications/external/wiiec/gfx/img_3x5_7.c
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_8.c b/applications/external/wiiec/gfx/img_3x5_8.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_8.c
rename to applications/external/wiiec/gfx/img_3x5_8.c
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_9.c b/applications/external/wiiec/gfx/img_3x5_9.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_9.c
rename to applications/external/wiiec/gfx/img_3x5_9.c
diff --git a/applications/external/wii_ec_anal/gfx/img_3x5_v.c b/applications/external/wiiec/gfx/img_3x5_v.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_3x5_v.c
rename to applications/external/wiiec/gfx/img_3x5_v.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_0.c b/applications/external/wiiec/gfx/img_5x7_0.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_0.c
rename to applications/external/wiiec/gfx/img_5x7_0.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_1.c b/applications/external/wiiec/gfx/img_5x7_1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_1.c
rename to applications/external/wiiec/gfx/img_5x7_1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_2.c b/applications/external/wiiec/gfx/img_5x7_2.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_2.c
rename to applications/external/wiiec/gfx/img_5x7_2.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_3.c b/applications/external/wiiec/gfx/img_5x7_3.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_3.c
rename to applications/external/wiiec/gfx/img_5x7_3.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_4.c b/applications/external/wiiec/gfx/img_5x7_4.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_4.c
rename to applications/external/wiiec/gfx/img_5x7_4.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_5.c b/applications/external/wiiec/gfx/img_5x7_5.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_5.c
rename to applications/external/wiiec/gfx/img_5x7_5.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_6.c b/applications/external/wiiec/gfx/img_5x7_6.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_6.c
rename to applications/external/wiiec/gfx/img_5x7_6.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_7.c b/applications/external/wiiec/gfx/img_5x7_7.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_7.c
rename to applications/external/wiiec/gfx/img_5x7_7.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_8.c b/applications/external/wiiec/gfx/img_5x7_8.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_8.c
rename to applications/external/wiiec/gfx/img_5x7_8.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_9.c b/applications/external/wiiec/gfx/img_5x7_9.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_9.c
rename to applications/external/wiiec/gfx/img_5x7_9.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_A.c b/applications/external/wiiec/gfx/img_5x7_A.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_A.c
rename to applications/external/wiiec/gfx/img_5x7_A.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_B.c b/applications/external/wiiec/gfx/img_5x7_B.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_B.c
rename to applications/external/wiiec/gfx/img_5x7_B.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_C.c b/applications/external/wiiec/gfx/img_5x7_C.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_C.c
rename to applications/external/wiiec/gfx/img_5x7_C.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_D.c b/applications/external/wiiec/gfx/img_5x7_D.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_D.c
rename to applications/external/wiiec/gfx/img_5x7_D.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_E.c b/applications/external/wiiec/gfx/img_5x7_E.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_E.c
rename to applications/external/wiiec/gfx/img_5x7_E.c
diff --git a/applications/external/wii_ec_anal/gfx/img_5x7_F.c b/applications/external/wiiec/gfx/img_5x7_F.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_5x7_F.c
rename to applications/external/wiiec/gfx/img_5x7_F.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_0.c b/applications/external/wiiec/gfx/img_6x8_0.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_0.c
rename to applications/external/wiiec/gfx/img_6x8_0.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_1.c b/applications/external/wiiec/gfx/img_6x8_1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_1.c
rename to applications/external/wiiec/gfx/img_6x8_1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_2.c b/applications/external/wiiec/gfx/img_6x8_2.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_2.c
rename to applications/external/wiiec/gfx/img_6x8_2.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_3.c b/applications/external/wiiec/gfx/img_6x8_3.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_3.c
rename to applications/external/wiiec/gfx/img_6x8_3.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_4.c b/applications/external/wiiec/gfx/img_6x8_4.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_4.c
rename to applications/external/wiiec/gfx/img_6x8_4.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_5.c b/applications/external/wiiec/gfx/img_6x8_5.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_5.c
rename to applications/external/wiiec/gfx/img_6x8_5.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_6.c b/applications/external/wiiec/gfx/img_6x8_6.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_6.c
rename to applications/external/wiiec/gfx/img_6x8_6.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_7.c b/applications/external/wiiec/gfx/img_6x8_7.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_7.c
rename to applications/external/wiiec/gfx/img_6x8_7.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_8.c b/applications/external/wiiec/gfx/img_6x8_8.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_8.c
rename to applications/external/wiiec/gfx/img_6x8_8.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_9.c b/applications/external/wiiec/gfx/img_6x8_9.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_9.c
rename to applications/external/wiiec/gfx/img_6x8_9.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_A.c b/applications/external/wiiec/gfx/img_6x8_A.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_A.c
rename to applications/external/wiiec/gfx/img_6x8_A.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_B.c b/applications/external/wiiec/gfx/img_6x8_B.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_B.c
rename to applications/external/wiiec/gfx/img_6x8_B.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_C.c b/applications/external/wiiec/gfx/img_6x8_C.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_C.c
rename to applications/external/wiiec/gfx/img_6x8_C.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_D.c b/applications/external/wiiec/gfx/img_6x8_D.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_D.c
rename to applications/external/wiiec/gfx/img_6x8_D.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_E.c b/applications/external/wiiec/gfx/img_6x8_E.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_E.c
rename to applications/external/wiiec/gfx/img_6x8_E.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_F.c b/applications/external/wiiec/gfx/img_6x8_F.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_F.c
rename to applications/external/wiiec/gfx/img_6x8_F.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_G.c b/applications/external/wiiec/gfx/img_6x8_G.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_G.c
rename to applications/external/wiiec/gfx/img_6x8_G.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_X.c b/applications/external/wiiec/gfx/img_6x8_X.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_X.c
rename to applications/external/wiiec/gfx/img_6x8_X.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_Y.c b/applications/external/wiiec/gfx/img_6x8_Y.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_Y.c
rename to applications/external/wiiec/gfx/img_6x8_Y.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_Z.c b/applications/external/wiiec/gfx/img_6x8_Z.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_Z.c
rename to applications/external/wiiec/gfx/img_6x8_Z.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_d_.c b/applications/external/wiiec/gfx/img_6x8_d_.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_d_.c
rename to applications/external/wiiec/gfx/img_6x8_d_.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_n_.c b/applications/external/wiiec/gfx/img_6x8_n_.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_n_.c
rename to applications/external/wiiec/gfx/img_6x8_n_.c
diff --git a/applications/external/wii_ec_anal/gfx/img_6x8_v_.c b/applications/external/wiiec/gfx/img_6x8_v_.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_6x8_v_.c
rename to applications/external/wiiec/gfx/img_6x8_v_.c
diff --git a/applications/external/wii_ec_anal/gfx/img_RIP.c b/applications/external/wiiec/gfx/img_RIP.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_RIP.c
rename to applications/external/wiiec/gfx/img_RIP.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_Cable.c b/applications/external/wiiec/gfx/img_cc_Cable.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_Cable.c
rename to applications/external/wiiec/gfx/img_cc_Cable.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_Joy.c b/applications/external/wiiec/gfx/img_cc_Joy.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_Joy.c
rename to applications/external/wiiec/gfx/img_cc_Joy.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_Main.c b/applications/external/wiiec/gfx/img_cc_Main.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_Main.c
rename to applications/external/wiiec/gfx/img_cc_Main.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_btn_A1.c b/applications/external/wiiec/gfx/img_cc_btn_A1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_btn_A1.c
rename to applications/external/wiiec/gfx/img_cc_btn_A1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_btn_B1.c b/applications/external/wiiec/gfx/img_cc_btn_B1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_btn_B1.c
rename to applications/external/wiiec/gfx/img_cc_btn_B1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_btn_X1.c b/applications/external/wiiec/gfx/img_cc_btn_X1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_btn_X1.c
rename to applications/external/wiiec/gfx/img_cc_btn_X1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_btn_Y1.c b/applications/external/wiiec/gfx/img_cc_btn_Y1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_btn_Y1.c
rename to applications/external/wiiec/gfx/img_cc_btn_Y1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_pad_LR1.c b/applications/external/wiiec/gfx/img_cc_pad_LR1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_pad_LR1.c
rename to applications/external/wiiec/gfx/img_cc_pad_LR1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_pad_UD1.c b/applications/external/wiiec/gfx/img_cc_pad_UD1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_pad_UD1.c
rename to applications/external/wiiec/gfx/img_cc_pad_UD1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_trg_L1.c b/applications/external/wiiec/gfx/img_cc_trg_L1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_trg_L1.c
rename to applications/external/wiiec/gfx/img_cc_trg_L1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_trg_L2.c b/applications/external/wiiec/gfx/img_cc_trg_L2.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_trg_L2.c
rename to applications/external/wiiec/gfx/img_cc_trg_L2.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_trg_L3.c b/applications/external/wiiec/gfx/img_cc_trg_L3.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_trg_L3.c
rename to applications/external/wiiec/gfx/img_cc_trg_L3.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_trg_L4.c b/applications/external/wiiec/gfx/img_cc_trg_L4.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_trg_L4.c
rename to applications/external/wiiec/gfx/img_cc_trg_L4.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_trg_R1.c b/applications/external/wiiec/gfx/img_cc_trg_R1.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_trg_R1.c
rename to applications/external/wiiec/gfx/img_cc_trg_R1.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_trg_R2.c b/applications/external/wiiec/gfx/img_cc_trg_R2.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_trg_R2.c
rename to applications/external/wiiec/gfx/img_cc_trg_R2.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_trg_R3.c b/applications/external/wiiec/gfx/img_cc_trg_R3.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_trg_R3.c
rename to applications/external/wiiec/gfx/img_cc_trg_R3.c
diff --git a/applications/external/wii_ec_anal/gfx/img_cc_trg_R4.c b/applications/external/wiiec/gfx/img_cc_trg_R4.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_cc_trg_R4.c
rename to applications/external/wiiec/gfx/img_cc_trg_R4.c
diff --git a/applications/external/wii_ec_anal/gfx/img_csLogo_FULL.c b/applications/external/wiiec/gfx/img_csLogo_FULL.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_csLogo_FULL.c
rename to applications/external/wiiec/gfx/img_csLogo_FULL.c
diff --git a/applications/external/wii_ec_anal/gfx/img_csLogo_Small.c b/applications/external/wiiec/gfx/img_csLogo_Small.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_csLogo_Small.c
rename to applications/external/wiiec/gfx/img_csLogo_Small.c
diff --git a/applications/external/wii_ec_anal/gfx/img_ecp_SCL.c b/applications/external/wiiec/gfx/img_ecp_SCL.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_ecp_SCL.c
rename to applications/external/wiiec/gfx/img_ecp_SCL.c
diff --git a/applications/external/wii_ec_anal/gfx/img_ecp_SDA.c b/applications/external/wiiec/gfx/img_ecp_SDA.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_ecp_SDA.c
rename to applications/external/wiiec/gfx/img_ecp_SDA.c
diff --git a/applications/external/wii_ec_anal/gfx/img_ecp_port.c b/applications/external/wiiec/gfx/img_ecp_port.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_ecp_port.c
rename to applications/external/wiiec/gfx/img_ecp_port.c
diff --git a/applications/external/wii_ec_anal/gfx/img_key_Back.c b/applications/external/wiiec/gfx/img_key_Back.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_key_Back.c
rename to applications/external/wiiec/gfx/img_key_Back.c
diff --git a/applications/external/wii_ec_anal/gfx/img_key_D.c b/applications/external/wiiec/gfx/img_key_D.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_key_D.c
rename to applications/external/wiiec/gfx/img_key_D.c
diff --git a/applications/external/wii_ec_anal/gfx/img_key_L.c b/applications/external/wiiec/gfx/img_key_L.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_key_L.c
rename to applications/external/wiiec/gfx/img_key_L.c
diff --git a/applications/external/wii_ec_anal/gfx/img_key_OK.c b/applications/external/wiiec/gfx/img_key_OK.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_key_OK.c
rename to applications/external/wiiec/gfx/img_key_OK.c
diff --git a/applications/external/wii_ec_anal/gfx/img_key_OKi.c b/applications/external/wiiec/gfx/img_key_OKi.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_key_OKi.c
rename to applications/external/wiiec/gfx/img_key_OKi.c
diff --git a/applications/external/wii_ec_anal/gfx/img_key_R.c b/applications/external/wiiec/gfx/img_key_R.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_key_R.c
rename to applications/external/wiiec/gfx/img_key_R.c
diff --git a/applications/external/wii_ec_anal/gfx/img_key_U.c b/applications/external/wiiec/gfx/img_key_U.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_key_U.c
rename to applications/external/wiiec/gfx/img_key_U.c
diff --git a/applications/external/wii_ec_anal/gfx/img_key_Ui.c b/applications/external/wiiec/gfx/img_key_Ui.c
similarity index 100%
rename from applications/external/wii_ec_anal/gfx/img_key_Ui.c
rename to applications/external/wiiec/gfx/img_key_Ui.c
diff --git a/applications/external/wii_ec_anal/i2c_workaround.h b/applications/external/wiiec/i2c_workaround.h
similarity index 100%
rename from applications/external/wii_ec_anal/i2c_workaround.h
rename to applications/external/wiiec/i2c_workaround.h
diff --git a/applications/external/wii_ec_anal/info.sh b/applications/external/wiiec/info.sh
similarity index 100%
rename from applications/external/wii_ec_anal/info.sh
rename to applications/external/wiiec/info.sh
diff --git a/applications/external/wii_ec_anal/wii_anal.c b/applications/external/wiiec/wii_anal.c
similarity index 95%
rename from applications/external/wii_ec_anal/wii_anal.c
rename to applications/external/wiiec/wii_anal.c
index 34b4f318b..5882dd60b 100644
--- a/applications/external/wii_ec_anal/wii_anal.c
+++ b/applications/external/wiiec/wii_anal.c
@@ -80,6 +80,8 @@ static void showVer(Canvas* const canvas) {
show(canvas, 4, 59, VER_MAJ, SHOW_SET_BLK);
canvas_draw_frame(canvas, 8, 62, 2, 2);
show(canvas, 11, 59, VER_MIN, SHOW_SET_BLK);
+ canvas_draw_frame(canvas, 15, 62, 2, 2);
+ show(canvas, 18, 59, VER_SUB, SHOW_SET_BLK);
}
//+============================================================================
@@ -94,9 +96,10 @@ static void cbDraw(Canvas* const canvas, void* ctx) {
furi_assert(canvas);
furi_assert(ctx);
- // Try to acquire the mutex for the plugin state variables, timeout = 25mS
state_t* state = ctx;
- furi_mutex_acquire(state->mutex, FuriWaitForever);
+
+ // Try to acquire the mutex for the plugin state variables, timeout = 25mS
+ if(furi_mutex_acquire(state->mutex, 25) != FuriStatusOk) return;
switch(state->scene) {
//---------------------------------------------------------------------
@@ -344,8 +347,7 @@ int32_t wii_ec_anal(void) {
goto bail;
}
// 5. Create a mutex for (reading/writing) the plugin state variables
- state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
- if(!state->mutex) {
+ if(!(state->mutex = furi_mutex_alloc(FuriMutexTypeNormal))) {
ERROR(wii_errs[(error = ERR_NO_MUTEX)]);
goto bail;
}
@@ -434,7 +436,10 @@ int32_t wii_ec_anal(void) {
// Read successful
// *** Try to lock the plugin state variables ***
- furi_mutex_acquire(state->mutex, FuriWaitForever);
+ if(furi_mutex_acquire(state->mutex, FuriWaitForever) != FuriStatusOk) {
+ ERROR(wii_errs[(error = ERR_MUTEX_BLOCK)]);
+ goto bail;
+ }
// *** Handle events ***
switch(msg.id) {
@@ -472,7 +477,10 @@ int32_t wii_ec_anal(void) {
if(redraw) view_port_update(vpp);
// *** Try to release the plugin state variables ***
- furi_mutex_release(state->mutex);
+ if(furi_mutex_release(state->mutex) != FuriStatusOk) {
+ ERROR(wii_errs[(error = ERR_MUTEX_RELEASE)]);
+ goto bail;
+ }
} while(state->run);
// ===== Game Over =====
@@ -480,13 +488,13 @@ int32_t wii_ec_anal(void) {
bail:
// 10. Release system notification queue
- if(state->notify) {
+ if(state && state->notify) {
furi_record_close(RECORD_NOTIFICATION);
state->notify = NULL;
}
// 9. Stop the timer
- if(state->timer) {
+ if(state && state->timer) {
(void)furi_timer_stop(state->timer);
furi_timer_free(state->timer);
state->timer = NULL;
@@ -507,7 +515,10 @@ bail:
}
// 5. Free the mutex
- furi_mutex_free(state->mutex);
+ if(state && state->mutex) {
+ furi_mutex_free(state->mutex);
+ state->mutex = NULL;
+ }
// 4. Free up state pointer(s)
// none
diff --git a/applications/external/wii_ec_anal/wii_anal.h b/applications/external/wiiec/wii_anal.h
similarity index 98%
rename from applications/external/wii_ec_anal/wii_anal.h
rename to applications/external/wiiec/wii_anal.h
index d8997b030..3be398a54 100644
--- a/applications/external/wii_ec_anal/wii_anal.h
+++ b/applications/external/wiiec/wii_anal.h
@@ -56,7 +56,8 @@ typedef struct eventMsg {
// Access to this memory is controlled by mutex
//
typedef struct state {
- FuriMutex* mutex;
+ FuriMutex* mutex; // mutex for using this struct
+
bool run; // true : plugin is running
bool timerEn; // controller scanning enabled
diff --git a/applications/external/wii_ec_anal/wii_anal_ec.c b/applications/external/wiiec/wii_anal_ec.c
similarity index 100%
rename from applications/external/wii_ec_anal/wii_anal_ec.c
rename to applications/external/wiiec/wii_anal_ec.c
diff --git a/applications/external/wii_ec_anal/wii_anal_ec.h b/applications/external/wiiec/wii_anal_ec.h
similarity index 100%
rename from applications/external/wii_ec_anal/wii_anal_ec.h
rename to applications/external/wiiec/wii_anal_ec.h
diff --git a/applications/external/wii_ec_anal/wii_anal_keys.c b/applications/external/wiiec/wii_anal_keys.c
similarity index 100%
rename from applications/external/wii_ec_anal/wii_anal_keys.c
rename to applications/external/wiiec/wii_anal_keys.c
diff --git a/applications/external/wii_ec_anal/wii_anal_keys.h b/applications/external/wiiec/wii_anal_keys.h
similarity index 100%
rename from applications/external/wii_ec_anal/wii_anal_keys.h
rename to applications/external/wiiec/wii_anal_keys.h
diff --git a/applications/external/wii_ec_anal/wii_anal_lcd.c b/applications/external/wiiec/wii_anal_lcd.c
similarity index 100%
rename from applications/external/wii_ec_anal/wii_anal_lcd.c
rename to applications/external/wiiec/wii_anal_lcd.c
diff --git a/applications/external/wii_ec_anal/wii_anal_lcd.h b/applications/external/wiiec/wii_anal_lcd.h
similarity index 100%
rename from applications/external/wii_ec_anal/wii_anal_lcd.h
rename to applications/external/wiiec/wii_anal_lcd.h
diff --git a/applications/external/wii_ec_anal/wii_anal_ver.h b/applications/external/wiiec/wii_anal_ver.h
similarity index 85%
rename from applications/external/wii_ec_anal/wii_anal_ver.h
rename to applications/external/wiiec/wii_anal_ver.h
index 3f2c8c0e6..df2659d57 100644
--- a/applications/external/wii_ec_anal/wii_anal_ver.h
+++ b/applications/external/wiiec/wii_anal_ver.h
@@ -5,5 +5,6 @@
#define VER_MAJ &img_3x5_1
#define VER_MIN &img_3x5_0
+#define VER_SUB &img_3x5_1
#endif //WII_ANAL_VER_H_
diff --git a/applications/external/wii_ec_anal/wii_ec.c b/applications/external/wiiec/wii_ec.c
similarity index 100%
rename from applications/external/wii_ec_anal/wii_ec.c
rename to applications/external/wiiec/wii_ec.c
diff --git a/applications/external/wii_ec_anal/wii_ec.h b/applications/external/wiiec/wii_ec.h
similarity index 100%
rename from applications/external/wii_ec_anal/wii_ec.h
rename to applications/external/wiiec/wii_ec.h
diff --git a/applications/external/wii_ec_anal/wii_ec_classic.c b/applications/external/wiiec/wii_ec_classic.c
similarity index 100%
rename from applications/external/wii_ec_anal/wii_ec_classic.c
rename to applications/external/wiiec/wii_ec_classic.c
diff --git a/applications/external/wii_ec_anal/wii_ec_classic.h b/applications/external/wiiec/wii_ec_classic.h
similarity index 100%
rename from applications/external/wii_ec_anal/wii_ec_classic.h
rename to applications/external/wiiec/wii_ec_classic.h
diff --git a/applications/external/wii_ec_anal/wii_ec_macros.h b/applications/external/wiiec/wii_ec_macros.h
similarity index 100%
rename from applications/external/wii_ec_anal/wii_ec_macros.h
rename to applications/external/wiiec/wii_ec_macros.h
diff --git a/applications/external/wii_ec_anal/wii_ec_nunchuck.c b/applications/external/wiiec/wii_ec_nunchuck.c
similarity index 100%
rename from applications/external/wii_ec_anal/wii_ec_nunchuck.c
rename to applications/external/wiiec/wii_ec_nunchuck.c
diff --git a/applications/external/wii_ec_anal/wii_ec_nunchuck.h b/applications/external/wiiec/wii_ec_nunchuck.h
similarity index 100%
rename from applications/external/wii_ec_anal/wii_ec_nunchuck.h
rename to applications/external/wiiec/wii_ec_nunchuck.h
diff --git a/applications/external/wii_ec_anal/wii_ec_udraw.c b/applications/external/wiiec/wii_ec_udraw.c
similarity index 100%
rename from applications/external/wii_ec_anal/wii_ec_udraw.c
rename to applications/external/wiiec/wii_ec_udraw.c
diff --git a/applications/external/wii_ec_anal/wii_ec_udraw.h b/applications/external/wiiec/wii_ec_udraw.h
similarity index 100%
rename from applications/external/wii_ec_anal/wii_ec_udraw.h
rename to applications/external/wiiec/wii_ec_udraw.h
diff --git a/applications/external/wii_ec_anal/wii_i2c.c b/applications/external/wiiec/wii_i2c.c
similarity index 100%
rename from applications/external/wii_ec_anal/wii_i2c.c
rename to applications/external/wiiec/wii_i2c.c
diff --git a/applications/external/wii_ec_anal/wii_i2c.h b/applications/external/wiiec/wii_i2c.h
similarity index 100%
rename from applications/external/wii_ec_anal/wii_i2c.h
rename to applications/external/wiiec/wii_i2c.h
diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h
index 36da022da..a96fa9679 100644
--- a/applications/main/archive/helpers/archive_browser.h
+++ b/applications/main/archive/helpers/archive_browser.h
@@ -62,7 +62,7 @@ static inline const char* archive_get_default_path(ArchiveTabEnum tab) {
}
inline bool archive_is_known_app(ArchiveFileTypeEnum type) {
- return (type != ArchiveFileTypeFolder && type != ArchiveFileTypeUnknown);
+ return (type != ArchiveFileTypeUnknown);
}
bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx);
diff --git a/applications/main/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c
index 7304be6b4..9d4b963f5 100644
--- a/applications/main/archive/helpers/archive_favorites.c
+++ b/applications/main/archive/helpers/archive_favorites.c
@@ -164,7 +164,7 @@ bool archive_favorites_read(void* context) {
need_refresh = true;
}
} else {
- if(storage_file_exists(storage, furi_string_get_cstr(buffer))) {
+ if(storage_common_exists(storage, furi_string_get_cstr(buffer))) {
storage_common_stat(storage, furi_string_get_cstr(buffer), &file_info);
archive_add_file_item(
browser, file_info_is_dir(&file_info), furi_string_get_cstr(buffer));
diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h
index e4bed3a63..7680bbd57 100644
--- a/applications/main/archive/helpers/archive_files.h
+++ b/applications/main/archive/helpers/archive_files.h
@@ -5,7 +5,7 @@
#include
#include
#include "toolbox/path.h"
-#include "xtreme/settings.h"
+#include
#define FAP_MANIFEST_MAX_ICON_SIZE 32
diff --git a/applications/main/archive/helpers/favorite_timeout.c b/applications/main/archive/helpers/favorite_timeout.c
new file mode 100644
index 000000000..7643ebfc8
--- /dev/null
+++ b/applications/main/archive/helpers/favorite_timeout.c
@@ -0,0 +1,32 @@
+#include "favorite_timeout.h"
+#include
+
+bool process_favorite_launch(char** args) {
+ if(*args && strlen(*args) > 4 && strncmp(*args, "fav/", 4) == 0) {
+ *args += 3;
+ return true;
+ }
+ return false;
+}
+
+void favorite_timeout_callback(void* _ctx) {
+ FavoriteTImeoutCtx* ctx = _ctx;
+ while(scene_manager_previous_scene(ctx->scene_manager))
+ ;
+ view_dispatcher_stop(ctx->view_dispatcher);
+}
+
+void favorite_timeout_run(ViewDispatcher* view_dispatcher, SceneManager* scene_manager) {
+ int32_t timeout = XTREME_SETTINGS()->favorite_timeout;
+ if(timeout == 0) {
+ view_dispatcher_run(view_dispatcher);
+ return;
+ }
+
+ FavoriteTImeoutCtx ctx = {.view_dispatcher = view_dispatcher, .scene_manager = scene_manager};
+ FuriTimer* timer = furi_timer_alloc(favorite_timeout_callback, FuriTimerTypeOnce, &ctx);
+ furi_timer_start(timer, timeout * furi_kernel_get_tick_frequency());
+ view_dispatcher_run(view_dispatcher);
+ furi_timer_stop(timer);
+ furi_timer_free(timer);
+}
diff --git a/applications/main/archive/helpers/favorite_timeout.h b/applications/main/archive/helpers/favorite_timeout.h
new file mode 100644
index 000000000..aded05708
--- /dev/null
+++ b/applications/main/archive/helpers/favorite_timeout.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include
+#include
+#include
+
+bool process_favorite_launch(char** p);
+
+typedef struct {
+ ViewDispatcher* view_dispatcher;
+ SceneManager* scene_manager;
+} FavoriteTImeoutCtx;
+
+void favorite_timeout_callback(void* _ctx);
+
+void favorite_timeout_run(ViewDispatcher* view_dispatcher, SceneManager* scene_manager);
diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c
index 8d606cf09..f0884ab73 100644
--- a/applications/main/archive/scenes/archive_scene_browser.c
+++ b/applications/main/archive/scenes/archive_scene_browser.c
@@ -21,6 +21,7 @@ static const char* flipper_app_name[] = {
[ArchiveFileTypeU2f] = "U2F",
[ArchiveFileTypeApplication] = "Apps",
[ArchiveFileTypeUpdateManifest] = "UpdaterApp",
+ [ArchiveFileTypeFolder] = "Archive",
};
static void archive_loader_callback(const void* message, void* context) {
@@ -35,7 +36,8 @@ static void archive_loader_callback(const void* message, void* context) {
}
}
-static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selected) {
+static void
+ archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selected, bool favorites) {
UNUSED(browser);
Loader* loader = furi_record_open(RECORD_LOADER);
@@ -47,8 +49,14 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec
}
status = loader_start(loader, flipper_app_name[selected->type], param);
} else {
- status = loader_start(
- loader, flipper_app_name[selected->type], furi_string_get_cstr(selected->path));
+ const char* str = furi_string_get_cstr(selected->path);
+ if(favorites) {
+ char arg[strlen(str) + 4];
+ snprintf(arg, sizeof(arg), "fav%s", str);
+ status = loader_start(loader, flipper_app_name[selected->type], arg);
+ } else {
+ status = loader_start(loader, flipper_app_name[selected->type], str);
+ }
}
if(status != LoaderStatusOk) {
@@ -107,8 +115,12 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) {
consumed = true;
break;
case ArchiveBrowserEventFileMenuRun:
- if(archive_is_known_app(selected->type)) {
- archive_run_in_app(browser, selected);
+ if(selected->type == ArchiveFileTypeFolder) {
+ archive_switch_tab(browser, TAB_LEFT);
+ archive_show_file_menu(browser, false);
+ archive_enter_dir(browser, selected->path);
+ } else if(archive_is_known_app(selected->type)) {
+ archive_run_in_app(browser, selected, favorites);
archive_show_file_menu(browser, false);
}
consumed = true;
diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c
index 994290a3c..cc2ed6263 100644
--- a/applications/main/archive/views/archive_browser_view.c
+++ b/applications/main/archive/views/archive_browser_view.c
@@ -66,8 +66,35 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) {
furi_string_set(item_pin, "Unpin");
}
- if(selected->type == ArchiveFileTypeFolder) {
+ if(model->tab_idx == ArchiveTabFavorites) {
+ //FURI_LOG_D(TAG, "ArchiveTabFavorites");
+
+ furi_string_set(item_rename, "Move");
+
+ archive_menu_add_item(
+ menu_array_push_raw(model->context_menu),
+ item_run,
+ ArchiveBrowserEventFileMenuRun);
+ archive_menu_add_item(
+ menu_array_push_raw(model->context_menu),
+ item_pin,
+ ArchiveBrowserEventFileMenuPin);
+ if(selected->type <= ArchiveFileTypeBadKb) {
+ archive_menu_add_item(
+ menu_array_push_raw(model->context_menu),
+ item_show,
+ ArchiveBrowserEventFileMenuShow);
+ }
+ archive_menu_add_item(
+ menu_array_push_raw(model->context_menu),
+ item_rename,
+ ArchiveBrowserEventFileMenuRename);
+ } else if(selected->type == ArchiveFileTypeFolder) {
//FURI_LOG_D(TAG, "Directory type");
+ archive_menu_add_item(
+ menu_array_push_raw(model->context_menu),
+ item_pin,
+ ArchiveBrowserEventFileMenuPin);
archive_menu_add_item(
menu_array_push_raw(model->context_menu),
item_rename,
@@ -97,29 +124,6 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) {
menu_array_push_raw(model->context_menu),
item_delete,
ArchiveBrowserEventFileMenuDelete);
- } else if(model->tab_idx == ArchiveTabFavorites) {
- //FURI_LOG_D(TAG, "ArchiveTabFavorites");
-
- furi_string_set(item_rename, "Move");
-
- archive_menu_add_item(
- menu_array_push_raw(model->context_menu),
- item_run,
- ArchiveBrowserEventFileMenuRun);
- archive_menu_add_item(
- menu_array_push_raw(model->context_menu),
- item_pin,
- ArchiveBrowserEventFileMenuPin);
- if(selected->type <= ArchiveFileTypeBadKb) {
- archive_menu_add_item(
- menu_array_push_raw(model->context_menu),
- item_show,
- ArchiveBrowserEventFileMenuShow);
- }
- archive_menu_add_item(
- menu_array_push_raw(model->context_menu),
- item_rename,
- ArchiveBrowserEventFileMenuRename);
} else if(selected->is_app) {
//FURI_LOG_D(TAG, "3 types");
archive_menu_add_item(
@@ -320,7 +324,7 @@ static void archive_render_status_bar(Canvas* canvas, ArchiveBrowserViewModel* m
const char* tab_name = ArchiveTabNames[model->tab_idx];
- canvas_draw_icon(canvas, 0, 0, &I_Background_128x11);
+ canvas_draw_icon(canvas, 0, 0, XTREME_ASSETS()->I_Background_128x11);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 0, 0, 50, 13);
diff --git a/applications/main/bad_kb/bad_kb_app.c b/applications/main/bad_kb/bad_kb_app.c
index d1215d628..e8b29b837 100644
--- a/applications/main/bad_kb/bad_kb_app.c
+++ b/applications/main/bad_kb/bad_kb_app.c
@@ -4,8 +4,9 @@
#include
#include
#include
-#include
+#include
#include
+#include
#include
#include
@@ -100,19 +101,19 @@ static void bad_kb_save_settings(BadKbApp* app) {
void bad_kb_reload_worker(BadKbApp* app) {
bad_kb_script_close(app->bad_kb_script);
- app->bad_kb_script = bad_kb_script_open(app->file_path, app->is_bt ? app->bt : NULL);
+ app->bad_kb_script = bad_kb_script_open(app->file_path, app->is_bt ? app->bt : NULL, app);
bad_kb_script_set_keyboard_layout(app->bad_kb_script, app->keyboard_layout);
}
-void bad_kb_config_switch_mode(BadKbApp* app) {
- scene_manager_previous_scene(app->scene_manager);
- if(app->is_bt) {
- furi_hal_bt_start_advertising();
- } else {
- furi_hal_bt_stop_advertising();
- }
- scene_manager_next_scene(app->scene_manager, BadKbSceneConfig);
+int32_t bad_kb_config_switch_mode(BadKbApp* app) {
+ if(!app->is_bt) furi_hal_bt_stop_advertising();
+ XTREME_SETTINGS()->bad_bt = app->is_bt;
+ XTREME_SETTINGS_SAVE();
bad_kb_reload_worker(app);
+ if(app->is_bt) furi_hal_bt_start_advertising();
+ scene_manager_next_scene(app->scene_manager, BadKbSceneConfig);
+ scene_manager_previous_scene(app->scene_manager);
+ return 0;
}
void bad_kb_config_switch_remember_mode(BadKbApp* app) {
@@ -204,6 +205,7 @@ BadKbApp* bad_kb_app_alloc(char* arg) {
app->file_path = furi_string_alloc();
app->keyboard_layout = furi_string_alloc();
+ process_favorite_launch(&arg);
if(arg && strlen(arg)) {
furi_string_set(app->file_path, arg);
}
@@ -269,7 +271,8 @@ BadKbApp* bad_kb_app_alloc(char* arg) {
"BadKbConnInit", 1024, (FuriThreadCallback)bad_kb_connection_init, app);
furi_thread_start(app->conn_init_thread);
if(!furi_string_empty(app->file_path)) {
- app->bad_kb_script = bad_kb_script_open(app->file_path, app->is_bt ? app->bt : NULL);
+ app->bad_kb_script =
+ bad_kb_script_open(app->file_path, app->is_bt ? app->bt : NULL, app);
bad_kb_script_set_keyboard_layout(app->bad_kb_script, app->keyboard_layout);
scene_manager_next_scene(app->scene_manager, BadKbSceneWork);
} else {
@@ -335,8 +338,8 @@ void bad_kb_app_free(BadKbApp* app) {
free(app);
}
-int32_t bad_kb_app(void* p) {
- BadKbApp* bad_kb_app = bad_kb_app_alloc((char*)p);
+int32_t bad_kb_app(char* p) {
+ BadKbApp* bad_kb_app = bad_kb_app_alloc(p);
view_dispatcher_run(bad_kb_app->view_dispatcher);
diff --git a/applications/main/bad_kb/bad_kb_app.h b/applications/main/bad_kb/bad_kb_app.h
index 2da40a21f..923ba7780 100644
--- a/applications/main/bad_kb/bad_kb_app.h
+++ b/applications/main/bad_kb/bad_kb_app.h
@@ -6,67 +6,21 @@
#include
#include
-#include
#include
#include
#include
-#include
-#include
-#include
-#include
-#include "views/bad_kb_view.h"
#define BAD_KB_APP_BASE_FOLDER EXT_PATH("badkb")
#define BAD_KB_APP_PATH_LAYOUT_FOLDER BAD_KB_APP_BASE_FOLDER "/assets/layouts"
#define BAD_KB_APP_SCRIPT_EXTENSION ".txt"
#define BAD_KB_APP_LAYOUT_EXTENSION ".kl"
-#define BAD_KB_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH
-#define BAD_KB_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE
-
-typedef enum {
- BadKbAppErrorNoFiles,
- BadKbAppErrorCloseRpc,
-} BadKbAppError;
-
typedef enum BadKbCustomEvent {
BadKbAppCustomEventTextEditResult,
BadKbAppCustomEventByteInputDone,
BadKbCustomEventErrorBack
} BadKbCustomEvent;
-typedef struct {
- char bt_name[BAD_KB_ADV_NAME_MAX_LEN + 1];
- uint8_t bt_mac[BAD_KB_MAC_ADDRESS_LEN];
- FuriHalUsbInterface* usb_mode;
- GapPairing bt_mode;
-} BadKbConfig;
-
-typedef struct {
- Gui* gui;
- ViewDispatcher* view_dispatcher;
- SceneManager* scene_manager;
- NotificationApp* notifications;
- DialogsApp* dialogs;
- Widget* widget;
- VariableItemList* var_item_list;
- TextInput* text_input;
- ByteInput* byte_input;
-
- BadKbAppError error;
- FuriString* file_path;
- FuriString* keyboard_layout;
- BadKb* bad_kb_view;
- BadKbScript* bad_kb_script;
-
- Bt* bt;
- bool is_bt;
- bool bt_remember;
- BadKbConfig config;
- BadKbConfig prev_config;
- FuriThread* conn_init_thread;
-} BadKbApp;
-
typedef enum {
BadKbAppViewError,
BadKbAppViewWork,
@@ -75,8 +29,6 @@ typedef enum {
BadKbAppViewConfigName
} BadKbAppView;
-void bad_kb_config_switch_mode(BadKbApp* app);
-
void bad_kb_config_switch_remember_mode(BadKbApp* app);
int32_t bad_kb_connection_init(BadKbApp* app);
diff --git a/applications/main/bad_kb/helpers/ducky_script.c b/applications/main/bad_kb/helpers/ducky_script.c
index 6b212a68d..71c307cdf 100644
--- a/applications/main/bad_kb/helpers/ducky_script.c
+++ b/applications/main/bad_kb/helpers/ducky_script.c
@@ -10,6 +10,7 @@
#include "ducky_script.h"
#include "ducky_script_i.h"
#include
+#include
#define TAG "BadKB"
#define WORKER_TAG TAG "Worker"
@@ -67,6 +68,7 @@ typedef enum {
} WorkerEvtFlags;
static const char ducky_cmd_id[] = {"ID"};
+static const char ducky_cmd_bt_id[] = {"BT_ID"};
static const uint8_t numpad_keys[10] = {
HID_KEYPAD_0,
@@ -327,6 +329,26 @@ static bool ducky_set_usb_id(BadKbScript* bad_kb, const char* line) {
return false;
}
+static bool ducky_set_bt_id(BadKbScript* bad_kb, const char* line) {
+ size_t line_len = strlen(line);
+ size_t mac_len = BAD_KB_MAC_ADDRESS_LEN * 3;
+ if(line_len < mac_len + 1) return false; // MAC + at least 1 char for name
+
+ uint8_t mac[BAD_KB_MAC_ADDRESS_LEN];
+ for(size_t i = 0; i < BAD_KB_MAC_ADDRESS_LEN; i++) {
+ char a = line[i * 3];
+ char b = line[i * 3 + 1];
+ if((a < 'A' && a > 'F') || (a < '0' && a > '9') || (b < 'A' && b > 'F') ||
+ (b < '0' && b > '9') || !hex_char_to_uint8(a, b, &mac[i])) {
+ return false;
+ }
+ }
+
+ furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, line + mac_len);
+ bt_set_profile_mac_address(bad_kb->bt, mac);
+ return true;
+}
+
static bool ducky_script_preload(BadKbScript* bad_kb, File* script_file) {
uint8_t ret = 0;
uint32_t line_len = 0;
@@ -354,18 +376,48 @@ static bool ducky_script_preload(BadKbScript* bad_kb, File* script_file) {
}
} while(ret > 0);
- if(!bad_kb->bt) {
- const char* line_tmp = furi_string_get_cstr(bad_kb->line);
- bool id_set = false; // Looking for ID command at first line
- if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
- id_set = ducky_set_usb_id(bad_kb, &line_tmp[strlen(ducky_cmd_id) + 1]);
+ const char* line_tmp = furi_string_get_cstr(bad_kb->line);
+ if(bad_kb->app->switch_mode_thread) {
+ furi_thread_join(bad_kb->app->switch_mode_thread);
+ furi_thread_free(bad_kb->app->switch_mode_thread);
+ bad_kb->app->switch_mode_thread = NULL;
+ }
+ // Looking for ID or BT_ID command at first line
+ bool reset_bt_id = !!bad_kb->bt;
+ if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
+ if(bad_kb->bt) {
+ bad_kb->app->is_bt = false;
+ bad_kb->app->switch_mode_thread = furi_thread_alloc_ex(
+ "BadKbSwitchMode",
+ 1024,
+ (FuriThreadCallback)bad_kb_config_switch_mode,
+ bad_kb->app);
+ furi_thread_start(bad_kb->app->switch_mode_thread);
+ return false;
}
-
- if(id_set) {
+ if(ducky_set_usb_id(bad_kb, &line_tmp[strlen(ducky_cmd_id) + 1])) {
furi_check(furi_hal_usb_set_config(&usb_hid, &bad_kb->hid_cfg));
} else {
furi_check(furi_hal_usb_set_config(&usb_hid, NULL));
}
+ } else if(strncmp(line_tmp, ducky_cmd_bt_id, strlen(ducky_cmd_bt_id)) == 0) {
+ if(!bad_kb->bt) {
+ bad_kb->app->is_bt = true;
+ bad_kb->app->switch_mode_thread = furi_thread_alloc_ex(
+ "BadKbSwitchMode",
+ 1024,
+ (FuriThreadCallback)bad_kb_config_switch_mode,
+ bad_kb->app);
+ furi_thread_start(bad_kb->app->switch_mode_thread);
+ return false;
+ }
+ if(!bad_kb->app->bt_remember) {
+ reset_bt_id = !ducky_set_bt_id(bad_kb, &line_tmp[strlen(ducky_cmd_bt_id) + 1]);
+ }
+ }
+ if(reset_bt_id) {
+ furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, bad_kb->app->config.bt_name);
+ bt_set_profile_mac_address(bad_kb->bt, bad_kb->app->config.bt_mac);
}
storage_file_seek(script_file, 0, true);
@@ -766,10 +818,11 @@ static void bad_kb_script_set_default_keyboard_layout(BadKbScript* bad_kb) {
memcpy(bad_kb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_kb->layout)));
}
-BadKbScript* bad_kb_script_open(FuriString* file_path, Bt* bt) {
+BadKbScript* bad_kb_script_open(FuriString* file_path, Bt* bt, BadKbApp* app) {
furi_assert(file_path);
BadKbScript* bad_kb = malloc(sizeof(BadKbScript));
+ bad_kb->app = app;
bad_kb->file_path = furi_string_alloc();
furi_string_set(bad_kb->file_path, file_path);
bad_kb->keyboard_layout = furi_string_alloc();
diff --git a/applications/main/bad_kb/helpers/ducky_script.h b/applications/main/bad_kb/helpers/ducky_script.h
index 6f5e03e0b..6a0386cf1 100644
--- a/applications/main/bad_kb/helpers/ducky_script.h
+++ b/applications/main/bad_kb/helpers/ducky_script.h
@@ -8,6 +8,13 @@ extern "C" {
#include
#include
+#include
+#include
+#include
+#include