Merge remote-tracking branch 'origin/dev' into release-candidate

This commit is contained in:
Aleksandr Kutuzov 2023-03-10 18:26:49 +09:00
commit dec00feb7b
343 changed files with 8111 additions and 5082 deletions

View File

@ -44,6 +44,16 @@ jobs:
echo random_hash=$(openssl rand -base64 40 | shasum -a 256 | awk '{print $1}') >> $GITHUB_OUTPUT
echo "event_type=$TYPE" >> $GITHUB_OUTPUT
- name: 'Check API versions'
run: |
set -e
N_API_HEADER_SIGNATURES=`ls -1 firmware/targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l`
if [ $N_API_HEADER_SIGNATURES != 1 ] ; then
echo API versions aren\'t matching for available targets. Please update!
head -n2 firmware/targets/f*/api_symbols.csv
exit 1
fi
- name: 'Make artifacts directory'
run: |
rm -rf artifacts

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
*~
*.swp
*.swo
*.gdb_history

View File

@ -44,3 +44,6 @@
# Functions that always return the same error code
//-V:picopass_device_decrypt:1048
# Examples
//V_EXCLUDE_PATH applications/examples/

View File

@ -11,6 +11,7 @@ typedef struct {
uint16_t left;
uint16_t right;
uint16_t ok;
FuriMutex* mutex;
} KeypadTestState;
static void keypad_test_reset_state(KeypadTestState* state) {
@ -22,7 +23,8 @@ static void keypad_test_reset_state(KeypadTestState* state) {
}
static void keypad_test_render_callback(Canvas* canvas, void* ctx) {
KeypadTestState* state = (KeypadTestState*)acquire_mutex((ValueMutex*)ctx, 25);
KeypadTestState* state = ctx;
furi_mutex_acquire(state->mutex, FuriWaitForever);
canvas_clear(canvas);
char strings[5][20];
@ -51,7 +53,7 @@ static void keypad_test_render_callback(Canvas* canvas, void* ctx) {
canvas_draw_str(canvas, 10, 63, "[back] - reset, hold to exit");
release_mutex((ValueMutex*)ctx, state);
furi_mutex_release(state->mutex);
}
static void keypad_test_input_callback(InputEvent* input_event, void* ctx) {
@ -64,17 +66,17 @@ int32_t keypad_test_app(void* p) {
FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent));
furi_check(event_queue);
KeypadTestState _state = {{false, false, false, false, false}, 0, 0, 0, 0, 0};
KeypadTestState state = {{false, false, false, false, false}, 0, 0, 0, 0, 0, NULL};
state.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, &_state, sizeof(KeypadTestState))) {
if(!state.mutex) {
FURI_LOG_E(TAG, "cannot create mutex");
return 0;
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, keypad_test_render_callback, &state_mutex);
view_port_draw_callback_set(view_port, keypad_test_render_callback, &state);
view_port_input_callback_set(view_port, keypad_test_input_callback, event_queue);
// Open GUI and register view_port
@ -83,7 +85,7 @@ int32_t keypad_test_app(void* p) {
InputEvent event;
while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) {
KeypadTestState* state = (KeypadTestState*)acquire_mutex_block(&state_mutex);
furi_mutex_acquire(state.mutex, FuriWaitForever);
FURI_LOG_I(
TAG,
"key: %s type: %s",
@ -92,54 +94,54 @@ int32_t keypad_test_app(void* p) {
if(event.key == InputKeyRight) {
if(event.type == InputTypePress) {
state->press[0] = true;
state.press[0] = true;
} else if(event.type == InputTypeRelease) {
state->press[0] = false;
state.press[0] = false;
} else if(event.type == InputTypeShort) {
++state->right;
++state.right;
}
} else if(event.key == InputKeyLeft) {
if(event.type == InputTypePress) {
state->press[1] = true;
state.press[1] = true;
} else if(event.type == InputTypeRelease) {
state->press[1] = false;
state.press[1] = false;
} else if(event.type == InputTypeShort) {
++state->left;
++state.left;
}
} else if(event.key == InputKeyUp) {
if(event.type == InputTypePress) {
state->press[2] = true;
state.press[2] = true;
} else if(event.type == InputTypeRelease) {
state->press[2] = false;
state.press[2] = false;
} else if(event.type == InputTypeShort) {
++state->up;
++state.up;
}
} else if(event.key == InputKeyDown) {
if(event.type == InputTypePress) {
state->press[3] = true;
state.press[3] = true;
} else if(event.type == InputTypeRelease) {
state->press[3] = false;
state.press[3] = false;
} else if(event.type == InputTypeShort) {
++state->down;
++state.down;
}
} else if(event.key == InputKeyOk) {
if(event.type == InputTypePress) {
state->press[4] = true;
state.press[4] = true;
} else if(event.type == InputTypeRelease) {
state->press[4] = false;
state.press[4] = false;
} else if(event.type == InputTypeShort) {
++state->ok;
++state.ok;
}
} else if(event.key == InputKeyBack) {
if(event.type == InputTypeLong) {
release_mutex(&state_mutex, state);
furi_mutex_release(state.mutex);
break;
} else if(event.type == InputTypeShort) {
keypad_test_reset_state(state);
keypad_test_reset_state(&state);
}
}
release_mutex(&state_mutex, state);
furi_mutex_release(state.mutex);
view_port_update(view_port);
}
@ -147,7 +149,7 @@ int32_t keypad_test_app(void* p) {
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
furi_mutex_free(state.mutex);
furi_record_close(RECORD_GUI);

View File

@ -53,15 +53,17 @@ static void (*text_box_test_render[])(Canvas* canvas) = {
typedef struct {
uint32_t idx;
FuriMutex* mutex;
} TextBoxTestState;
static void text_box_test_render_callback(Canvas* canvas, void* ctx) {
TextBoxTestState* state = acquire_mutex((ValueMutex*)ctx, 25);
TextBoxTestState* state = ctx;
furi_mutex_acquire(state->mutex, FuriWaitForever);
canvas_clear(canvas);
text_box_test_render[state->idx](canvas);
release_mutex((ValueMutex*)ctx, state);
furi_mutex_release(state->mutex);
}
static void text_box_test_input_callback(InputEvent* input_event, void* ctx) {
@ -74,17 +76,17 @@ int32_t text_box_test_app(void* p) {
FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent));
furi_check(event_queue);
TextBoxTestState _state = {.idx = 0};
TextBoxTestState state = {.idx = 0, .mutex = NULL};
state.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, &_state, sizeof(TextBoxTestState))) {
if(!state.mutex) {
FURI_LOG_E(TAG, "Cannot create mutex");
return 0;
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, text_box_test_render_callback, &state_mutex);
view_port_draw_callback_set(view_port, text_box_test_render_callback, &state);
view_port_input_callback_set(view_port, text_box_test_input_callback, event_queue);
// Open GUI and register view_port
@ -94,24 +96,24 @@ int32_t text_box_test_app(void* p) {
uint32_t test_renders_num = COUNT_OF(text_box_test_render);
InputEvent event;
while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) {
TextBoxTestState* state = acquire_mutex_block(&state_mutex);
furi_mutex_acquire(state.mutex, FuriWaitForever);
if(event.type == InputTypeShort) {
if(event.key == InputKeyRight) {
if(state->idx < test_renders_num - 1) {
state->idx++;
if(state.idx < test_renders_num - 1) {
state.idx++;
}
} else if(event.key == InputKeyLeft) {
if(state->idx > 0) {
state->idx--;
if(state.idx > 0) {
state.idx--;
}
} else if(event.key == InputKeyBack) {
release_mutex(&state_mutex, state);
furi_mutex_release(state.mutex);
break;
}
}
release_mutex(&state_mutex, state);
furi_mutex_release(state.mutex);
view_port_update(view_port);
}
@ -119,7 +121,7 @@ int32_t text_box_test_app(void* p) {
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
furi_mutex_free(state.mutex);
furi_record_close(RECORD_GUI);

View File

@ -5,7 +5,6 @@
// v2 tests
void test_furi_create_open();
void test_furi_valuemutex();
void test_furi_concurrent_access();
void test_furi_pubsub();
@ -30,10 +29,6 @@ MU_TEST(mu_test_furi_create_open) {
test_furi_create_open();
}
MU_TEST(mu_test_furi_valuemutex) {
test_furi_valuemutex();
}
MU_TEST(mu_test_furi_pubsub) {
test_furi_pubsub();
}
@ -51,7 +46,6 @@ MU_TEST_SUITE(test_suite) {
// v2 tests
MU_RUN_TEST(mu_test_furi_create_open);
MU_RUN_TEST(mu_test_furi_valuemutex);
MU_RUN_TEST(mu_test_furi_pubsub);
MU_RUN_TEST(mu_test_furi_memmgr);
}

View File

@ -1,41 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <furi.h>
#include "../minunit.h"
void test_furi_valuemutex() {
const int init_value = 0xdeadbeef;
const int changed_value = 0x12345678;
int value = init_value;
bool result;
ValueMutex valuemutex;
// init mutex case
result = init_mutex(&valuemutex, &value, sizeof(value));
mu_assert(result, "init mutex failed");
// acquire mutex case
int* value_pointer = acquire_mutex(&valuemutex, 100);
mu_assert_pointers_eq(value_pointer, &value);
// second acquire mutex case
int* value_pointer_second = acquire_mutex(&valuemutex, 100);
mu_assert_pointers_eq(value_pointer_second, NULL);
// change value case
*value_pointer = changed_value;
mu_assert_int_eq(value, changed_value);
// release mutex case
result = release_mutex(&valuemutex, &value);
mu_assert(result, "release mutex failed");
// TODO
//acquire mutex blocking case
//write mutex blocking case
//read mutex blocking case
mu_check(delete_mutex(&valuemutex));
}

View File

@ -191,7 +191,7 @@ static void clean_directory(Storage* fs_api, const char* clean_dir) {
size_t size = strlen(clean_dir) + strlen(name) + 1 + 1;
char* fullname = malloc(size);
snprintf(fullname, size, "%s/%s", clean_dir, name);
if(fileinfo.flags & FSF_DIRECTORY) {
if(file_info_is_dir(&fileinfo)) {
clean_directory(fs_api, fullname);
}
FS_Error error = storage_common_remove(fs_api, fullname);
@ -608,9 +608,8 @@ static void test_rpc_storage_list_create_expected_list(
}
if(path_contains_only_ascii(name)) {
list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ?
PB_Storage_File_FileType_DIR :
PB_Storage_File_FileType_FILE;
list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR :
PB_Storage_File_FileType_FILE;
list->file[i].size = fileinfo.size;
list->file[i].data = NULL;
/* memory free inside rpc_encode_and_send() -> pb_release() */
@ -873,7 +872,7 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) {
if(error == FSE_OK) {
response->which_content = PB_Main_storage_stat_response_tag;
response->content.storage_stat_response.has_file = true;
response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ?
response->content.storage_stat_response.file.type = file_info_is_dir(&fileinfo) ?
PB_Storage_File_FileType_DIR :
PB_Storage_File_FileType_FILE;
response->content.storage_stat_response.file.size = fileinfo.size;

View File

@ -179,7 +179,7 @@ MU_TEST_1(test_dirwalk_full, Storage* storage) {
while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo)));
}
dir_walk_free(dir_walk);
@ -204,7 +204,7 @@ MU_TEST_1(test_dirwalk_no_recursive, Storage* storage) {
while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo)));
}
dir_walk_free(dir_walk);
@ -219,7 +219,7 @@ static bool test_dirwalk_filter_no_folder_ext(const char* name, FileInfo* filein
UNUSED(ctx);
// only files
if(!(fileinfo->flags & FSF_DIRECTORY)) {
if(!file_info_is_dir(fileinfo)) {
// with ".test" in name
if(strstr(name, ".test") != NULL) {
return true;
@ -243,7 +243,7 @@ MU_TEST_1(test_dirwalk_filter, Storage* storage) {
while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo)));
}
dir_walk_free(dir_walk);

View File

@ -2,9 +2,40 @@
#include <furi.h>
#include <storage/storage.h>
// DO NOT USE THIS IN PRODUCTION CODE
// This is a hack to access internal storage functions and definitions
#include <storage/storage_i.h>
#define UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/" path)
#define STORAGE_LOCKED_FILE EXT_PATH("locked_file.test")
#define STORAGE_LOCKED_DIR STORAGE_INT_PATH_PREFIX
#define STORAGE_TEST_DIR UNIT_TESTS_PATH("test_dir")
static bool storage_file_create(Storage* storage, const char* path, const char* data) {
File* file = storage_file_alloc(storage);
bool result = false;
do {
if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_NEW)) {
break;
}
if(storage_file_write(file, data, strlen(data)) != strlen(data)) {
break;
}
if(!storage_file_close(file)) {
break;
}
result = true;
} while(0);
storage_file_free(file);
return result;
}
static void storage_file_open_lock_setup() {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
@ -115,7 +146,7 @@ static int32_t storage_dir_locker(void* ctx) {
File* file = storage_file_alloc(storage);
furi_check(storage_dir_open(file, STORAGE_LOCKED_DIR));
furi_semaphore_release(semaphore);
furi_delay_ms(1000);
furi_delay_ms(100);
furi_check(storage_dir_close(file));
furi_record_close(RECORD_STORAGE);
@ -152,9 +183,21 @@ MU_TEST(storage_dir_open_lock) {
mu_assert(result, "cannot open locked dir");
}
MU_TEST(storage_dir_exists_test) {
Storage* storage = furi_record_open(RECORD_STORAGE);
mu_check(!storage_dir_exists(storage, STORAGE_TEST_DIR));
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, STORAGE_TEST_DIR));
mu_check(storage_dir_exists(storage, STORAGE_TEST_DIR));
mu_assert_int_eq(FSE_OK, storage_common_remove(storage, STORAGE_TEST_DIR));
furi_record_close(RECORD_STORAGE);
}
MU_TEST_SUITE(storage_dir) {
MU_RUN_TEST(storage_dir_open_close);
MU_RUN_TEST(storage_dir_open_lock);
MU_RUN_TEST(storage_dir_exists_test);
}
static const char* const storage_copy_test_paths[] = {
@ -303,9 +346,256 @@ MU_TEST_SUITE(storage_rename) {
furi_record_close(RECORD_STORAGE);
}
#define APPSDATA_APP_PATH(path) APPS_DATA_PATH "/" path
static const char* storage_test_apps[] = {
"-_twilight_-",
"-_rainbow_-",
"-_pinkie_-",
"-_apple_-",
"-_flutter_-",
"-_rare_-",
};
static size_t storage_test_apps_count = COUNT_OF(storage_test_apps);
static int32_t storage_test_app(void* arg) {
UNUSED(arg);
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_remove(storage, "/data/test");
int32_t ret = storage_file_create(storage, "/data/test", "test");
furi_record_close(RECORD_STORAGE);
return ret;
}
MU_TEST(test_storage_data_path_apps) {
for(size_t i = 0; i < storage_test_apps_count; i++) {
FuriThread* thread =
furi_thread_alloc_ex(storage_test_apps[i], 1024, storage_test_app, NULL);
furi_thread_set_appid(thread, storage_test_apps[i]);
furi_thread_start(thread);
furi_thread_join(thread);
mu_assert_int_eq(true, furi_thread_get_return_code(thread));
// Check if app data dir and file exists
Storage* storage = furi_record_open(RECORD_STORAGE);
FuriString* expected = furi_string_alloc();
furi_string_printf(expected, APPSDATA_APP_PATH("%s"), storage_test_apps[i]);
mu_check(storage_dir_exists(storage, furi_string_get_cstr(expected)));
furi_string_cat(expected, "/test");
mu_check(storage_file_exists(storage, furi_string_get_cstr(expected)));
furi_string_printf(expected, APPSDATA_APP_PATH("%s"), storage_test_apps[i]);
storage_simply_remove_recursive(storage, furi_string_get_cstr(expected));
furi_record_close(RECORD_STORAGE);
furi_string_free(expected);
furi_thread_free(thread);
}
}
MU_TEST(test_storage_data_path) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
mu_check(storage_dir_open(file, "/data"));
mu_check(storage_dir_close(file));
storage_file_free(file);
// check that appsdata folder exists
mu_check(storage_dir_exists(storage, APPS_DATA_PATH));
// check that cli folder exists
mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli")));
storage_simply_remove(storage, APPSDATA_APP_PATH("cli"));
furi_record_close(RECORD_STORAGE);
}
MU_TEST(test_storage_common_migrate) {
Storage* storage = furi_record_open(RECORD_STORAGE);
// Setup test folders
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
// Test migration from non existing
mu_assert_int_eq(
FSE_OK,
storage_common_migrate(
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
// Test migration from existing folder to non existing
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file1"), "test1"));
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file2.ext"), "test2"));
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file3.ext.ext"), "test3"));
mu_assert_int_eq(
FSE_OK,
storage_common_migrate(
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file1")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file2.ext")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext.ext")));
mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
// Test migration from existing folder to existing folder
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file1"), "test1"));
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file2.ext"), "test2"));
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file3.ext.ext"), "test3"));
mu_assert_int_eq(
FSE_OK,
storage_common_migrate(
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file1")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file2.ext")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext.ext")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file11")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file21.ext")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext1.ext")));
mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
// Test migration from empty folder to existing file
// Expected result: FSE_OK, folder removed, file untouched
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new"), "test1"));
mu_assert_int_eq(
FSE_OK,
storage_common_migrate(
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new")));
mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
// Test migration from empty folder to existing folder
// Expected result: FSE_OK, old folder removed, new folder untouched
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_new")));
mu_assert_int_eq(
FSE_OK,
storage_common_migrate(
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
// Test migration from existing file to non existing, no extension
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1"));
mu_assert_int_eq(
FSE_OK,
storage_common_migrate(
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new")));
mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old")));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
// Test migration from existing file to non existing, with extension
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old.file"), "test1"));
mu_assert_int_eq(
FSE_OK,
storage_common_migrate(
storage, UNIT_TESTS_PATH("migrate_old.file"), UNIT_TESTS_PATH("migrate_new.file")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new.file")));
mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old.file")));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old.file"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new.file"));
// Test migration from existing file to existing file, no extension
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1"));
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new"), "test2"));
mu_assert_int_eq(
FSE_OK,
storage_common_migrate(
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new")));
mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1")));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1"));
// Test migration from existing file to existing file, with extension
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old.file"), "test1"));
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new.file"), "test2"));
mu_assert_int_eq(
FSE_OK,
storage_common_migrate(
storage, UNIT_TESTS_PATH("migrate_old.file"), UNIT_TESTS_PATH("migrate_new.file")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new.file")));
mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old.file")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1.file")));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old.file"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new.file"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1.file"));
// Test migration from existing file to existing folder
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1"));
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_new")));
mu_assert_int_eq(
FSE_OK,
storage_common_migrate(
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old")));
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1")));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1"));
furi_record_close(RECORD_STORAGE);
}
MU_TEST_SUITE(test_data_path) {
MU_RUN_TEST(test_storage_data_path);
MU_RUN_TEST(test_storage_data_path_apps);
}
MU_TEST_SUITE(test_storage_common) {
MU_RUN_TEST(test_storage_common_migrate);
}
int run_minunit_test_storage() {
MU_RUN_SUITE(storage_file);
MU_RUN_SUITE(storage_dir);
MU_RUN_SUITE(storage_rename);
MU_RUN_SUITE(test_data_path);
MU_RUN_SUITE(test_storage_common);
return MU_EXIT_CODE;
}

View File

@ -0,0 +1,58 @@
# Apps Assets folder Example
This example shows how to use the Apps Assets folder to store data that is not part of the application itself, but is required for its operation, and that data is provided with the application.
## What is the Apps Assets Folder?
The **Apps Assets** folder is a folder where external applications unpack their assets.
The path to the current application folder is related to the `appid` of the app. The `appid` is used to identify the app in the app store and is stored in the `application.fam` file.
The Apps Assets folder is located only on the external storage, the SD card.
For example, if the `appid` of the app is `snake_game`, the path to the Apps Assets folder will be `/ext/apps_assets/snake_game`. But using raw paths is not recommended, because the path to the Apps Assets folder can change in the future. Use the `/assets` alias instead.
## How to get the path to the Apps Assets folder?
You can use `/assets` alias to get the path to the current application data folder. For example, if you want to open a file `database.txt` in the Apps Assets folder, you can use the next path: `/data/database.txt`. But this way is not recommended, because even the `/assets` alias can change in the future.
We recommend to use the `APP_ASSETS_PATH` macro to get the path to the Apps Assets folder. For example, if you want to open a file `database.txt` in the Apps Assets folder, you can use the next path: `APP_ASSETS_PATH("database.txt")`.
## What is the difference between the Apps Assets folder and the Apps Data folder?
The Apps Assets folder is used to store the data <u>provided</u> with the application. For example, if you want to create a game, you can store game levels (contant data) in the Apps Assets folder.
The Apps Data folder is used to store data <u>generated</u> by the application. For example, if you want to create a game, you can save the progress of the game (user-generated data) in the Apps Data folder.
## How to provide the data with the app?
To provide data with an application, you need to create a folder inside your application folder (eg "files") and place the data in it. After that, you need to add `fap_file_assets="files"` to your application.fam file.
For example, if you want to provide game levels with the application, you need to create a "levels" folder inside the "files" folder and put the game levels in it. After that, you need to add `fap_file_assets="files"` to your application.fam file. The final application folder structure will look like this:
```
snake_game
├── application.fam
├── snake_game.c
└── files
└── levels
├── level1.txt
├── level2.txt
└── level3.txt
```
When app is launched, the `files` folder will be unpacked to the Apps Assets folder. The final structure of the Apps Assets folder will look like this:
```
/assets
├── .assets.signature
└── levels
├── level1.txt
├── level2.txt
└── level3.txt
```
## When will the data be unpacked?
The data is unpacked when the application starts, if the application is launched for the first time, or if the data within the application is updated.
When an application is compiled, the contents of the "files" folder are hashed and stored within the application itself. When the application starts, this hash is compared to the hash stored in the `.assets.signature` file. If the hashes differ or the `.assets.signature` file does not exist, the application folder is deleted and the new data is unpacked.

View File

@ -0,0 +1,10 @@
App(
appid="example_apps_assets",
name="Example: Apps Assets",
apptype=FlipperAppType.EXTERNAL,
entry_point="example_apps_assets_main",
requires=["gui"],
stack_size=4 * 1024,
fap_category="Examples",
fap_file_assets="files",
)

View File

@ -0,0 +1,48 @@
#include <furi.h>
#include <storage/storage.h>
#include <toolbox/stream/stream.h>
#include <toolbox/stream/file_stream.h>
// Define log tag
#define TAG "example_apps_assets"
static void example_apps_data_print_file_content(Storage* storage, const char* path) {
Stream* stream = file_stream_alloc(storage);
FuriString* line = furi_string_alloc();
FURI_LOG_I(TAG, "----------------------------------------");
FURI_LOG_I(TAG, "File \"%s\" content:", path);
if(file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
while(stream_read_line(stream, line)) {
furi_string_replace_all(line, "\r", "");
furi_string_replace_all(line, "\n", "");
FURI_LOG_I(TAG, "%s", furi_string_get_cstr(line));
}
} else {
FURI_LOG_E(TAG, "Failed to open file");
}
FURI_LOG_I(TAG, "----------------------------------------");
furi_string_free(line);
file_stream_close(stream);
stream_free(stream);
}
// Application entry point
int32_t example_apps_assets_main(void* p) {
// Mark argument as unused
UNUSED(p);
// Open storage
Storage* storage = furi_record_open(RECORD_STORAGE);
example_apps_data_print_file_content(storage, APP_ASSETS_PATH("test_asset.txt"));
example_apps_data_print_file_content(storage, APP_ASSETS_PATH("poems/a jelly-fish.txt"));
example_apps_data_print_file_content(storage, APP_ASSETS_PATH("poems/theme in yellow.txt"));
example_apps_data_print_file_content(storage, APP_ASSETS_PATH("poems/my shadow.txt"));
// Close storage
furi_record_close(RECORD_STORAGE);
return 0;
}

View File

@ -0,0 +1,24 @@
A Jelly-Fish by Marianne Moore
Visible, invisible,
A fluctuating charm,
An amber-colored amethyst
Inhabits it; your arm
Approaches, and
It opens and
It closes;
You have meant
To catch it,
And it shrivels;
You abandon
Your intent—
It opens, and it
Closes and you
Reach for it—
The blue
Surrounding it
Grows cloudy, and
It floats away
From you.
source: "https://poets.org/anthology/poems-your-poetry-project-public-domain"

View File

@ -0,0 +1,23 @@
My Shadow by Robert Louis Stevenson
I have a little shadow that goes in and out with me,
And what can be the use of him is more than I can see.
He is very, very like me from the heels up to the head;
And I see him jump before me, when I jump into my bed.
The funniest thing about him is the way he likes to grow—
Not at all like proper children, which is always very slow;
For he sometimes shoots up taller like an India-rubber ball,
And he sometimes gets so little that theres none of him at all.
He hasnt got a notion of how children ought to play,
And can only make a fool of me in every sort of way.
He stays so close beside me, hes a coward you can see;
Id think shame to stick to nursie as that shadow sticks to me!
One morning, very early, before the sun was up,
I rose and found the shining dew on every buttercup;
But my lazy little shadow, like an arrant sleepy-head,
Had stayed at home behind me and was fast asleep in bed.
source: "https://poets.org/anthology/poems-your-poetry-project-public-domain"

View File

@ -0,0 +1,19 @@
Theme in Yellow by Carl Sandburg
I spot the hills
With yellow balls in autumn.
I light the prairie cornfields
Orange and tawny gold clusters
And I am called pumpkins.
On the last of October
When dusk is fallen
Children join hands
And circle round me
Singing ghost songs
And love to the harvest moon;
I am a jack-o'-lantern
With terrible teeth
And the children know
I am fooling.
source: "https://poets.org/anthology/poems-your-poetry-project-public-domain"

View File

@ -0,0 +1 @@
## This is test file content

View File

@ -0,0 +1,24 @@
# Apps Data folder Example
This example demonstrates how to utilize the Apps Data folder to store data that is not part of the app itself, such as user data, configuration files, and so forth.
## What is the Apps Data Folder?
The **Apps Data** folder is a folder used to store data for external apps that are not part of the main firmware.
The path to the current application folder is related to the `appid` of the app. The `appid` is used to identify the app in the app store and is stored in the `application.fam` file.
The Apps Data folder is located only on the external storage, the SD card.
For example, if the `appid` of the app is `snake_game`, the path to the Apps Data folder will be `/ext/apps_data/snake_game`. But using raw paths is not recommended, because the path to the Apps Data folder can change in the future. Use the `/data` alias instead.
## How to get the path to the Apps Data folder?
You can use `/data` alias to get the path to the current application data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `/data/config.txt`. But this way is not recommended, because even the `/data` alias can change in the future.
We recommend to use the `APP_DATA_PATH` macro to get the path to the Apps Data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `APP_DATA_PATH("config.txt")`.
## What is the difference between the Apps Assets folder and the Apps Data folder?
The Apps Assets folder is used to store the data <u>provided</u> with the application. For example, if you want to create a game, you can store game levels (contant data) in the Apps Assets folder.
The Apps Data folder is used to store data <u>generated</u> by the application. For example, if you want to create a game, you can save the progress of the game (user-generated data) in the Apps Data folder.

View File

@ -0,0 +1,9 @@
App(
appid="example_apps_data",
name="Example: Apps Data",
apptype=FlipperAppType.EXTERNAL,
entry_point="example_apps_data_main",
requires=["gui"],
stack_size=1 * 1024,
fap_category="Examples",
)

View File

@ -0,0 +1,40 @@
#include <furi.h>
#include <storage/storage.h>
// Define log tag
#define TAG "example_apps_data"
// Application entry point
int32_t example_apps_data_main(void* p) {
// Mark argument as unused
UNUSED(p);
// Open storage
Storage* storage = furi_record_open(RECORD_STORAGE);
// Allocate file
File* file = storage_file_alloc(storage);
// Get the path to the current application data folder
// That is: /ext/apps_data/<app_name>
// And it will create folders in the path if they don't exist
// In this example it will create /ext/apps_data/example_apps_data
// And file will be /ext/apps_data/example_apps_data/test.txt
// Open file, write data and close it
if(!storage_file_open(file, APP_DATA_PATH("test.txt"), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
FURI_LOG_E(TAG, "Failed to open file");
}
if(!storage_file_write(file, "Hello World!", strlen("Hello World!"))) {
FURI_LOG_E(TAG, "Failed to write to file");
}
storage_file_close(file);
// Deallocate file
storage_file_free(file);
// Close storage
furi_record_close(RECORD_STORAGE);
return 0;
}

View File

@ -436,7 +436,7 @@ static bool archive_is_dir_exists(FuriString* path) {
FileInfo file_info;
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) {
if(file_info.flags & FSF_DIRECTORY) {
if(file_info_is_dir(&file_info)) {
state = true;
}
}
@ -510,12 +510,16 @@ void archive_enter_dir(ArchiveBrowserView* browser, FuriString* path) {
browser->view, ArchiveBrowserViewModel * model, { idx_temp = model->item_idx; }, false);
furi_string_set(browser->path, path);
file_browser_worker_folder_enter(browser->worker, path, idx_temp);
}
void archive_leave_dir(ArchiveBrowserView* browser) {
furi_assert(browser);
size_t dirname_start = furi_string_search_rchar(browser->path, '/');
furi_string_left(browser->path, dirname_start);
file_browser_worker_folder_exit(browser->worker);
}

View File

@ -160,7 +160,7 @@ bool archive_favorites_read(void* context) {
if(storage_file_exists(storage, furi_string_get_cstr(buffer))) {
storage_common_stat(storage, furi_string_get_cstr(buffer), &file_info);
archive_add_file_item(
browser, (file_info.flags & FSF_DIRECTORY), furi_string_get_cstr(buffer));
browser, file_info_is_dir(&file_info), furi_string_get_cstr(buffer));
file_count++;
} else {
need_refresh = true;

View File

@ -91,7 +91,7 @@ void archive_delete_file(void* context, const char* format, ...) {
bool res = false;
if(fileinfo.flags & FSF_DIRECTORY) {
if(file_info_is_dir(&fileinfo)) {
res = storage_simply_remove_recursive(fs_api, furi_string_get_cstr(filename));
} else {
res = (storage_common_remove(fs_api, furi_string_get_cstr(filename)) == FSE_OK);

View File

@ -6,11 +6,11 @@
#include <furi_hal_usb_hid.h>
#include <storage/storage.h>
#include "bad_usb_script.h"
#include "mnemonic.h"
#include <dolphin/dolphin.h>
#define TAG "BadUSB"
#define WORKER_TAG TAG "Worker"
#define FILE_BUFFER_LEN 16
#define SCRIPT_STATE_ERROR (-1)
#define SCRIPT_STATE_END (-2)
@ -26,24 +26,6 @@ typedef enum {
WorkerEvtDisconnect = (1 << 3),
} WorkerEvtFlags;
struct BadUsbScript {
FuriHalUsbHidConfig hid_cfg;
BadUsbState st;
FuriString* file_path;
uint32_t defdelay;
uint16_t layout[128];
uint32_t stringdelay;
FuriThread* thread;
uint8_t file_buf[FILE_BUFFER_LEN + 1];
uint8_t buf_start;
uint8_t buf_len;
bool file_end;
FuriString* line;
FuriString* line_prev;
uint32_t repeat_cnt;
};
typedef struct {
char* name;
uint16_t keycode;
@ -112,40 +94,21 @@ static const char ducky_cmd_comment[] = {"REM"};
static const char ducky_cmd_id[] = {"ID"};
static const char ducky_cmd_delay[] = {"DELAY "};
static const char ducky_cmd_string[] = {"STRING "};
static const char ducky_cmd_stringln[] = {"STRINGLN "};
static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "};
static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "};
static const char ducky_cmd_stringdelay_1[] = {"STRINGDELAY "};
static const char ducky_cmd_stringdelay_2[] = {"STRING_DELAY "};
static const char ducky_cmd_repeat[] = {"REPEAT "};
static const char ducky_cmd_sysrq[] = {"SYSRQ "};
static const char ducky_cmd_hold[] = {"HOLD "};
static const char ducky_cmd_release[] = {"RELEASE "};
static const char ducky_cmd_altchar[] = {"ALTCHAR "};
static const char ducky_cmd_altstr_1[] = {"ALTSTRING "};
static const char ducky_cmd_altstr_2[] = {"ALTCODE "};
static const uint8_t numpad_keys[10] = {
HID_KEYPAD_0,
HID_KEYPAD_1,
HID_KEYPAD_2,
HID_KEYPAD_3,
HID_KEYPAD_4,
HID_KEYPAD_5,
HID_KEYPAD_6,
HID_KEYPAD_7,
HID_KEYPAD_8,
HID_KEYPAD_9,
};
static bool ducky_get_number(const char* param, uint32_t* val) {
uint32_t value = 0;
if(sscanf(param, "%lu", &value) == 1) {
*val = value;
return true;
}
return false;
}
static uint32_t ducky_get_command_len(const char* line) {
uint32_t ducky_get_command_len(const char* line) {
uint32_t len = strlen(line);
for(uint32_t i = 0; i < len; i++) {
if(line[i] == ' ') return i;
@ -153,84 +116,11 @@ static uint32_t ducky_get_command_len(const char* line) {
return 0;
}
static bool ducky_is_line_end(const char chr) {
bool ducky_is_line_end(const char chr) {
return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'));
}
static void ducky_numlock_on() {
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
}
}
static bool ducky_numpad_press(const char num) {
if((num < '0') || (num > '9')) return false;
uint16_t key = numpad_keys[num - '0'];
furi_hal_hid_kb_press(key);
furi_hal_hid_kb_release(key);
return true;
}
static bool ducky_altchar(const char* charcode) {
uint8_t i = 0;
bool state = false;
FURI_LOG_I(WORKER_TAG, "char %s", charcode);
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
while(!ducky_is_line_end(charcode[i])) {
state = ducky_numpad_press(charcode[i]);
if(state == false) break;
i++;
}
furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
return state;
}
static bool ducky_altstring(const char* param) {
uint32_t i = 0;
bool state = false;
while(param[i] != '\0') {
if((param[i] < ' ') || (param[i] > '~')) {
i++;
continue; // Skip non-printable chars
}
char temp_str[4];
snprintf(temp_str, 4, "%u", param[i]);
state = ducky_altchar(temp_str);
if(state == false) break;
i++;
}
return state;
}
static bool ducky_string(BadUsbScript* bad_usb, const char* param) {
uint32_t i = 0;
while(param[i] != '\0') {
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
if(keycode != HID_KEYBOARD_NONE) {
furi_hal_hid_kb_press(keycode);
furi_hal_hid_kb_release(keycode);
if(bad_usb->stringdelay > 0) {
furi_delay_ms(bad_usb->stringdelay);
}
}
i++;
}
bad_usb->stringdelay = 0;
return true;
}
static uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) {
uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) {
for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) {
size_t key_cmd_len = strlen(ducky_keys[i].name);
if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
@ -248,119 +138,68 @@ static int32_t
ducky_parse_line(BadUsbScript* bad_usb, FuriString* line, char* error, size_t error_len) {
uint32_t line_len = furi_string_size(line);
const char* line_tmp = furi_string_get_cstr(line);
bool state = false;
const char* ducky_cmd_table[] = {
ducky_cmd_comment,
ducky_cmd_id,
ducky_cmd_delay,
ducky_cmd_string,
ducky_cmd_defdelay_1,
ducky_cmd_defdelay_2,
ducky_cmd_stringdelay_1,
ducky_cmd_stringdelay_2,
ducky_cmd_repeat,
ducky_cmd_sysrq,
ducky_cmd_altchar,
ducky_cmd_altstr_1,
ducky_cmd_altstr_2,
ducky_cmd_stringln,
ducky_cmd_hold,
ducky_cmd_release,
NULL};
int32_t (*fnc_ptr[])(BadUsbScript*, FuriString*, const char*, char*, size_t) = {
&ducky_fnc_noop,
&ducky_fnc_noop,
&ducky_fnc_delay,
&ducky_fnc_string,
&ducky_fnc_defdelay,
&ducky_fnc_defdelay,
&ducky_fnc_strdelay,
&ducky_fnc_strdelay,
&ducky_fnc_repeat,
&ducky_fnc_sysrq,
&ducky_fnc_altchar,
&ducky_fnc_altstring,
&ducky_fnc_altstring,
&ducky_fnc_stringln,
&ducky_fnc_hold,
&ducky_fnc_release,
NULL};
if(line_len == 0) {
return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
}
FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
// General commands
if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
// REM - comment line
return (0);
} else if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
// ID - executed in ducky_script_preload
return (0);
} else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
// DELAY
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
uint32_t delay_val = 0;
state = ducky_get_number(line_tmp, &delay_val);
if((state) && (delay_val > 0)) {
return (int32_t)delay_val;
}
if(error != NULL) {
snprintf(error, error_len, "Invalid number %s", line_tmp);
}
return SCRIPT_STATE_ERROR;
} else if(
(strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) ||
(strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) {
// DEFAULT_DELAY
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_get_number(line_tmp, &bad_usb->defdelay);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid number %s", line_tmp);
}
return (state) ? (0) : SCRIPT_STATE_ERROR;
} else if(
(strncmp(line_tmp, ducky_cmd_stringdelay_1, strlen(ducky_cmd_stringdelay_1)) == 0) ||
(strncmp(line_tmp, ducky_cmd_stringdelay_2, strlen(ducky_cmd_stringdelay_2)) == 0)) {
//STRINGDELAY, finally it's here
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_get_number(line_tmp, &bad_usb->stringdelay);
if((state) && (bad_usb->stringdelay > 0)) {
return state;
}
if(error != NULL) {
snprintf(error, error_len, "Invalid number %s", line_tmp);
}
return SCRIPT_STATE_ERROR;
} else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
// STRING
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_string(bad_usb, line_tmp);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid string %s", line_tmp);
}
return (state) ? (0) : SCRIPT_STATE_ERROR;
} else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) {
// ALTCHAR
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
ducky_numlock_on();
state = ducky_altchar(line_tmp);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid altchar %s", line_tmp);
}
return (state) ? (0) : SCRIPT_STATE_ERROR;
} else if(
(strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) ||
(strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) {
// ALTSTRING
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
ducky_numlock_on();
state = ducky_altstring(line_tmp);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid altstring %s", line_tmp);
}
return (state) ? (0) : SCRIPT_STATE_ERROR;
} else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) {
// REPEAT
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid number %s", line_tmp);
}
return (state) ? (0) : SCRIPT_STATE_ERROR;
} else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) {
// SYSRQ
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true);
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
furi_hal_hid_kb_press(key);
furi_hal_hid_kb_release_all();
return (0);
} else {
// Special keys + modifiers
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
if(key == HID_KEYBOARD_NONE) {
if(error != NULL) {
snprintf(error, error_len, "No keycode defined for %s", line_tmp);
}
return SCRIPT_STATE_ERROR;
}
if((key & 0xFF00) != 0) {
// It's a modifier key
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
key |= ducky_get_keycode(bad_usb, line_tmp, true);
}
furi_hal_hid_kb_press(key);
furi_hal_hid_kb_release(key);
return (0);
// Ducky Lang Functions
for(size_t i = 0; ducky_cmd_table[i]; i++) {
if(strncmp(line_tmp, ducky_cmd_table[i], strlen(ducky_cmd_table[i])) == 0)
return ((fnc_ptr[i])(bad_usb, line, line_tmp, error, error_len));
}
// Special keys + modifiers
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
if(key == HID_KEYBOARD_NONE) {
if(error != NULL) {
snprintf(error, error_len, "No keycode defined for %s", line_tmp);
}
return SCRIPT_STATE_ERROR;
}
if((key & 0xFF00) != 0) {
// It's a modifier key
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
key |= ducky_get_keycode(bad_usb, line_tmp, true);
}
furi_hal_hid_kb_press(key);
furi_hal_hid_kb_release(key);
return (0);
}
static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) {

View File

@ -5,8 +5,9 @@ extern "C" {
#endif
#include <furi.h>
#include <furi_hal.h>
typedef struct BadUsbScript BadUsbScript;
#define FILE_BUFFER_LEN 16
typedef enum {
BadUsbStateInit,
@ -29,6 +30,24 @@ typedef struct {
char error[64];
} BadUsbState;
typedef struct BadUsbScript {
FuriHalUsbHidConfig hid_cfg;
BadUsbState st;
FuriString* file_path;
uint32_t defdelay;
uint16_t layout[128];
uint32_t stringdelay;
FuriThread* thread;
uint8_t file_buf[FILE_BUFFER_LEN + 1];
uint8_t buf_start;
uint8_t buf_len;
bool file_end;
FuriString* line;
FuriString* line_prev;
uint32_t repeat_cnt;
} BadUsbScript;
BadUsbScript* bad_usb_script_open(FuriString* file_path);
void bad_usb_script_close(BadUsbScript* bad_usb);
@ -43,6 +62,12 @@ void bad_usb_script_toggle(BadUsbScript* bad_usb);
BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb);
uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars);
uint32_t ducky_get_command_len(const char* line);
bool ducky_is_line_end(const char chr);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,327 @@
#include <furi_hal.h>
#include <furi_hal_usb_hid.h>
#include "mnemonic.h"
#define TAG "BadUSB"
#define WORKER_TAG TAG "Worker"
#define FILE_BUFFER_LEN 16
#define SCRIPT_STATE_ERROR (-1)
#define SCRIPT_STATE_END (-2)
#define SCRIPT_STATE_NEXT_LINE (-3)
#define BADUSB_ASCII_TO_KEY(script, x) \
(((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
static const uint8_t numpad_keys[10] = {
HID_KEYPAD_0,
HID_KEYPAD_1,
HID_KEYPAD_2,
HID_KEYPAD_3,
HID_KEYPAD_4,
HID_KEYPAD_5,
HID_KEYPAD_6,
HID_KEYPAD_7,
HID_KEYPAD_8,
HID_KEYPAD_9,
};
static bool ducky_get_number(const char* param, uint32_t* val) {
uint32_t value = 0;
if(sscanf(param, "%lu", &value) == 1) {
*val = value;
return true;
}
return false;
}
static void ducky_numlock_on() {
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
}
}
static bool ducky_numpad_press(const char num) {
if((num < '0') || (num > '9')) return false;
uint16_t key = numpad_keys[num - '0'];
furi_hal_hid_kb_press(key);
furi_hal_hid_kb_release(key);
return true;
}
static bool ducky_altchar(const char* charcode) {
uint8_t i = 0;
bool state = false;
FURI_LOG_I(WORKER_TAG, "char %s", charcode);
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
while(!ducky_is_line_end(charcode[i])) {
state = ducky_numpad_press(charcode[i]);
if(state == false) break;
i++;
}
furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
return state;
}
static bool ducky_altstring(const char* param) {
uint32_t i = 0;
bool state = false;
while(param[i] != '\0') {
if((param[i] < ' ') || (param[i] > '~')) {
i++;
continue; // Skip non-printable chars
}
char temp_str[4];
snprintf(temp_str, 4, "%u", param[i]);
state = ducky_altchar(temp_str);
if(state == false) break;
i++;
}
return state;
}
static bool ducky_string(BadUsbScript* bad_usb, const char* param) {
uint32_t i = 0;
while(param[i] != '\0') {
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
if(keycode != HID_KEYBOARD_NONE) {
furi_hal_hid_kb_press(keycode);
furi_hal_hid_kb_release(keycode);
if(bad_usb->stringdelay > 0) {
furi_delay_ms(bad_usb->stringdelay);
}
}
i++;
}
bad_usb->stringdelay = 0;
return true;
}
int32_t ducky_fnc_noop(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
(void)bad_usb;
(void)line;
(void)line_tmp;
(void)error;
(void)error_len;
return (0);
}
int32_t ducky_fnc_delay(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
bool state = false;
(void)bad_usb;
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
uint32_t delay_val = 0;
state = ducky_get_number(line_tmp, &delay_val);
if((state) && (delay_val > 0)) {
return (int32_t)delay_val;
}
if(error != NULL) {
snprintf(error, error_len, "Invalid number %s", line_tmp);
}
return SCRIPT_STATE_ERROR;
}
int32_t ducky_fnc_defdelay(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
bool state = false;
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_get_number(line_tmp, &bad_usb->defdelay);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid number %s", line_tmp);
}
return (state) ? (0) : SCRIPT_STATE_ERROR;
}
int32_t ducky_fnc_strdelay(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
bool state = false;
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_get_number(line_tmp, &bad_usb->stringdelay);
if((state) && (bad_usb->stringdelay > 0)) {
return state;
}
if(error != NULL) {
snprintf(error, error_len, "Invalid number %s", line_tmp);
}
return SCRIPT_STATE_ERROR;
}
int32_t ducky_fnc_string(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
bool state = false;
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_string(bad_usb, line_tmp);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid string %s", line_tmp);
}
return (state) ? (0) : SCRIPT_STATE_ERROR;
}
int32_t ducky_fnc_repeat(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
bool state = false;
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid number %s", line_tmp);
}
return (state) ? (0) : SCRIPT_STATE_ERROR;
}
int32_t ducky_fnc_sysrq(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
(void)error;
(void)error_len;
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true);
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
furi_hal_hid_kb_press(key);
furi_hal_hid_kb_release_all();
return (0);
}
int32_t ducky_fnc_altchar(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
bool state = false;
(void)bad_usb;
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
ducky_numlock_on();
state = ducky_altchar(line_tmp);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid altchar %s", line_tmp);
}
return (state) ? (0) : SCRIPT_STATE_ERROR;
}
int32_t ducky_fnc_altstring(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
bool state = false;
(void)bad_usb;
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
ducky_numlock_on();
state = ducky_altstring(line_tmp);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid altstring %s", line_tmp);
}
return (state) ? (0) : SCRIPT_STATE_ERROR;
}
int32_t ducky_fnc_stringln(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
bool state = false;
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_string(bad_usb, line_tmp);
if(!state && error != NULL) {
snprintf(error, error_len, "Invalid string %s", line_tmp);
}
furi_hal_hid_kb_press(HID_KEYBOARD_RETURN);
furi_hal_hid_kb_release(HID_KEYBOARD_RETURN);
return (state) ? (0) : SCRIPT_STATE_ERROR;
}
int32_t ducky_fnc_hold(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true);
if(key == HID_KEYBOARD_NONE) {
if(error != NULL) {
snprintf(error, error_len, "No keycode defined for %s", line_tmp);
}
return SCRIPT_STATE_ERROR;
}
furi_hal_hid_kb_press(key);
return (0);
}
int32_t ducky_fnc_release(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len) {
(void)line;
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true);
if(key == HID_KEYBOARD_NONE) {
if(error != NULL) {
snprintf(error, error_len, "No keycode defined for %s", line_tmp);
}
return SCRIPT_STATE_ERROR;
}
furi_hal_hid_kb_release(key);
return (0);
}

View File

@ -0,0 +1,96 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "bad_usb_script.h"
// A no opperation function
int32_t ducky_fnc_noop(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// DELAY
int32_t ducky_fnc_delay(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// DEFAULTDELAY
int32_t ducky_fnc_defdelay(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// STRINGDELAY
int32_t ducky_fnc_strdelay(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// STRING
int32_t ducky_fnc_string(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// STRINGLN
int32_t ducky_fnc_stringln(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// REPEAT
int32_t ducky_fnc_repeat(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// SYSRQ
int32_t ducky_fnc_sysrq(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// ALTCHAR
int32_t ducky_fnc_altchar(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// ALTSTRING
int32_t ducky_fnc_altstring(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// HOLD
int32_t ducky_fnc_hold(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
// RELEASE
int32_t ducky_fnc_release(
BadUsbScript* bad_usb,
FuriString* line,
const char* line_tmp,
char* error,
size_t error_len);
#ifdef __cplusplus
}
#endif

View File

@ -17,7 +17,7 @@ void bad_usb_scene_config_on_enter(void* context) {
submenu_add_item(
submenu,
"Keyboard layout",
"Keyboard Layout",
SubmenuIndexKeyboardLayout,
bad_usb_scene_config_submenu_callback,
bad_usb);

View File

@ -5,6 +5,7 @@
#include <storage/storage.h>
#include <gui/modules/loading.h>
#include <dialogs/dialogs.h>
#include <toolbox/path.h>
#include <flipper_application/flipper_application.h>
#include "elf_cpp/elf_hashtable.h"
#include "fap_loader_app.h"
@ -105,6 +106,12 @@ static bool fap_loader_run_selected_app(FapLoader* loader) {
FURI_LOG_I(TAG, "FAP Loader is starting app");
FuriThread* thread = flipper_application_spawn(loader->app, NULL);
FuriString* app_name = furi_string_alloc();
path_extract_filename_no_ext(furi_string_get_cstr(loader->fap_path), app_name);
furi_thread_set_appid(thread, furi_string_get_cstr(app_name));
furi_string_free(app_name);
furi_thread_start(thread);
furi_thread_join(thread);

View File

@ -1,10 +1,6 @@
#include "ibutton.h"
#include "assets_icons.h"
#include "ibutton_i.h"
#include "ibutton/scenes/ibutton_scene.h"
#include <toolbox/path.h>
#include <flipper_format/flipper_format.h>
#include <rpc/rpc_app.h>
#include <dolphin/dolphin.h>
#define TAG "iButtonApp"
@ -34,50 +30,13 @@ static const NotificationSequence* ibutton_notification_sequences[] = {
};
static void ibutton_make_app_folder(iButton* ibutton) {
if(!storage_simply_mkdir(ibutton->storage, IBUTTON_APP_FOLDER)) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(!storage_simply_mkdir(storage, IBUTTON_APP_FOLDER)) {
dialog_message_show_storage_error(ibutton->dialogs, "Cannot create\napp folder");
}
}
bool ibutton_load_key_data(iButton* ibutton, FuriString* key_path, bool show_dialog) {
FlipperFormat* file = flipper_format_file_alloc(ibutton->storage);
bool result = false;
FuriString* data;
data = furi_string_alloc();
do {
if(!flipper_format_file_open_existing(file, furi_string_get_cstr(key_path))) break;
// header
uint32_t version;
if(!flipper_format_read_header(file, data, &version)) break;
if(furi_string_cmp_str(data, IBUTTON_APP_FILE_TYPE) != 0) break;
if(version != 1) break;
// key type
iButtonKeyType type;
if(!flipper_format_read_string(file, "Key type", data)) break;
if(!ibutton_key_get_type_by_string(furi_string_get_cstr(data), &type)) break;
// key data
uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0};
if(!flipper_format_read_hex(file, "Data", key_data, ibutton_key_get_size_by_type(type)))
break;
ibutton_key_set_type(ibutton->key, type);
ibutton_key_set_data(ibutton->key, key_data, IBUTTON_KEY_DATA_SIZE);
result = true;
} while(false);
flipper_format_free(file);
furi_string_free(data);
if((!result) && (show_dialog)) {
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
}
return result;
furi_record_close(RECORD_STORAGE);
}
static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) {
@ -87,14 +46,14 @@ static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context)
if(event == RpcAppEventSessionClose) {
view_dispatcher_send_custom_event(
ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose);
rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
ibutton->rpc_ctx = NULL;
rpc_system_app_set_callback(ibutton->rpc, NULL, NULL);
ibutton->rpc = NULL;
} else if(event == RpcAppEventAppExit) {
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
} else if(event == RpcAppEventLoadFile) {
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad);
} else {
rpc_system_app_confirm(ibutton->rpc_ctx, event, false);
rpc_system_app_confirm(ibutton->rpc, event, false);
}
}
@ -135,13 +94,13 @@ iButton* ibutton_alloc() {
ibutton->gui = furi_record_open(RECORD_GUI);
ibutton->storage = furi_record_open(RECORD_STORAGE);
ibutton->dialogs = furi_record_open(RECORD_DIALOGS);
ibutton->notifications = furi_record_open(RECORD_NOTIFICATION);
ibutton->key = ibutton_key_alloc();
ibutton->key_worker = ibutton_worker_alloc();
ibutton_worker_start_thread(ibutton->key_worker);
ibutton->protocols = ibutton_protocols_alloc();
ibutton->key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(ibutton->protocols));
ibutton->worker = ibutton_worker_alloc(ibutton->protocols);
ibutton_worker_start_thread(ibutton->worker);
ibutton->submenu = submenu_alloc();
view_dispatcher_add_view(
@ -163,9 +122,9 @@ iButton* ibutton_alloc() {
view_dispatcher_add_view(
ibutton->view_dispatcher, iButtonViewWidget, widget_get_view(ibutton->widget));
ibutton->dialog_ex = dialog_ex_alloc();
ibutton->loading = loading_alloc();
view_dispatcher_add_view(
ibutton->view_dispatcher, iButtonViewDialogEx, dialog_ex_get_view(ibutton->dialog_ex));
ibutton->view_dispatcher, iButtonViewLoading, loading_get_view(ibutton->loading));
return ibutton;
}
@ -173,8 +132,8 @@ iButton* ibutton_alloc() {
void ibutton_free(iButton* ibutton) {
furi_assert(ibutton);
view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewDialogEx);
dialog_ex_free(ibutton->dialog_ex);
view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewLoading);
loading_free(ibutton->loading);
view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewWidget);
widget_free(ibutton->widget);
@ -194,9 +153,6 @@ void ibutton_free(iButton* ibutton) {
view_dispatcher_free(ibutton->view_dispatcher);
scene_manager_free(ibutton->scene_manager);
furi_record_close(RECORD_STORAGE);
ibutton->storage = NULL;
furi_record_close(RECORD_NOTIFICATION);
ibutton->notifications = NULL;
@ -206,103 +162,83 @@ void ibutton_free(iButton* ibutton) {
furi_record_close(RECORD_GUI);
ibutton->gui = NULL;
ibutton_worker_stop_thread(ibutton->key_worker);
ibutton_worker_free(ibutton->key_worker);
ibutton_worker_stop_thread(ibutton->worker);
ibutton_worker_free(ibutton->worker);
ibutton_key_free(ibutton->key);
ibutton_protocols_free(ibutton->protocols);
furi_string_free(ibutton->file_path);
free(ibutton);
}
bool ibutton_file_select(iButton* ibutton) {
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px);
browser_options.base_path = IBUTTON_APP_FOLDER;
bool ibutton_load_key(iButton* ibutton) {
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading);
bool success = dialog_file_browser_show(
ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options);
const bool success = ibutton_protocols_load(
ibutton->protocols, ibutton->key, furi_string_get_cstr(ibutton->file_path));
if(success) {
success = ibutton_load_key_data(ibutton, ibutton->file_path, true);
if(!success) {
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
} else {
FuriString* tmp = furi_string_alloc();
path_extract_filename(ibutton->file_path, tmp, true);
strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE);
furi_string_free(tmp);
}
return success;
}
bool ibutton_save_key(iButton* ibutton, const char* key_name) {
// Create ibutton directory if necessary
bool ibutton_select_and_load_key(iButton* ibutton) {
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px);
browser_options.base_path = IBUTTON_APP_FOLDER;
if(furi_string_empty(ibutton->file_path)) {
furi_string_set(ibutton->file_path, browser_options.base_path);
}
return dialog_file_browser_show(
ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options) &&
ibutton_load_key(ibutton);
}
bool ibutton_save_key(iButton* ibutton) {
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading);
ibutton_make_app_folder(ibutton);
FlipperFormat* file = flipper_format_file_alloc(ibutton->storage);
iButtonKey* key = ibutton->key;
const bool success =
ibutton_protocols_save(ibutton->protocols, key, furi_string_get_cstr(ibutton->file_path));
bool result = false;
do {
// Check if we has old key
if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
// First remove old key
ibutton_delete_key(ibutton);
// Remove old key name from path
size_t filename_start = furi_string_search_rchar(ibutton->file_path, '/');
furi_string_left(ibutton->file_path, filename_start);
}
furi_string_cat_printf(ibutton->file_path, "/%s%s", key_name, IBUTTON_APP_EXTENSION);
// Open file for write
if(!flipper_format_file_open_always(file, furi_string_get_cstr(ibutton->file_path))) break;
// Write header
if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break;
// Write key type
if(!flipper_format_write_comment_cstr(file, "Key type can be Cyfral, Dallas or Metakom"))
break;
const char* key_type = ibutton_key_get_string_by_type(ibutton_key_get_type(key));
if(!flipper_format_write_string_cstr(file, "Key type", key_type)) break;
// Write data
if(!flipper_format_write_comment_cstr(
file, "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8"))
break;
if(!flipper_format_write_hex(
file, "Data", ibutton_key_get_data_p(key), ibutton_key_get_data_size(key)))
break;
result = true;
} while(false);
flipper_format_free(file);
if(!result) { //-V547
if(!success) {
dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file");
}
return result;
return success;
}
bool ibutton_delete_key(iButton* ibutton) {
bool result = false;
result = storage_simply_remove(ibutton->storage, furi_string_get_cstr(ibutton->file_path));
Storage* storage = furi_record_open(RECORD_STORAGE);
result = storage_simply_remove(storage, furi_string_get_cstr(ibutton->file_path));
furi_record_close(RECORD_STORAGE);
ibutton_reset_key(ibutton);
return result;
}
void ibutton_text_store_set(iButton* ibutton, const char* text, ...) {
va_list args;
va_start(args, text);
vsnprintf(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE, text, args);
va_end(args);
}
void ibutton_text_store_clear(iButton* ibutton) {
memset(ibutton->text_store, 0, IBUTTON_TEXT_STORE_SIZE + 1);
void ibutton_reset_key(iButton* ibutton) {
memset(ibutton->key_name, 0, IBUTTON_KEY_NAME_SIZE + 1);
furi_string_reset(ibutton->file_path);
ibutton_key_reset(ibutton->key);
}
void ibutton_notification_message(iButton* ibutton, uint32_t message) {
@ -310,36 +246,44 @@ void ibutton_notification_message(iButton* ibutton, uint32_t message) {
notification_message(ibutton->notifications, ibutton_notification_sequences[message]);
}
int32_t ibutton_app(void* p) {
void ibutton_submenu_callback(void* context, uint32_t index) {
iButton* ibutton = context;
view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
}
void ibutton_widget_callback(GuiButtonType result, InputType type, void* context) {
iButton* ibutton = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
}
}
int32_t ibutton_app(void* arg) {
iButton* ibutton = ibutton_alloc();
ibutton_make_app_folder(ibutton);
bool key_loaded = false;
bool rpc_mode = false;
if(p && strlen(p)) {
uint32_t rpc_ctx = 0;
if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
if((arg != NULL) && (strlen(arg) != 0)) {
if(sscanf(arg, "RPC %lX", (uint32_t*)&ibutton->rpc) == 1) {
FURI_LOG_D(TAG, "Running in RPC mode");
ibutton->rpc_ctx = (void*)rpc_ctx;
rpc_mode = true;
rpc_system_app_set_callback(ibutton->rpc_ctx, ibutton_rpc_command_callback, ibutton);
rpc_system_app_send_started(ibutton->rpc_ctx);
rpc_system_app_set_callback(ibutton->rpc, ibutton_rpc_command_callback, ibutton);
rpc_system_app_send_started(ibutton->rpc);
} else {
furi_string_set(ibutton->file_path, (const char*)p);
if(ibutton_load_key_data(ibutton, ibutton->file_path, true)) {
key_loaded = true;
// TODO: Display an error if the key from p could not be loaded
}
furi_string_set(ibutton->file_path, (const char*)arg);
key_loaded = ibutton_load_key(ibutton);
}
}
if(rpc_mode) {
if(ibutton->rpc != NULL) {
view_dispatcher_attach_to_gui(
ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeDesktop);
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc);
DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
} else {
view_dispatcher_attach_to_gui(
ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeFullscreen);
@ -353,9 +297,9 @@ int32_t ibutton_app(void* p) {
view_dispatcher_run(ibutton->view_dispatcher);
if(ibutton->rpc_ctx) {
rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
rpc_system_app_send_exited(ibutton->rpc_ctx);
if(ibutton->rpc) {
rpc_system_app_set_callback(ibutton->rpc, NULL, NULL);
rpc_system_app_send_exited(ibutton->rpc);
}
ibutton_free(ibutton);
return 0;

View File

@ -1,11 +1,15 @@
#include <furi.h>
#include <furi_hal.h>
#include <stdarg.h>
#include <cli/cli.h>
#include <lib/toolbox/args.h>
#include <one_wire/ibutton/ibutton_worker.h>
#include <toolbox/args.h>
#include <one_wire/one_wire_host.h>
#include <one_wire/ibutton/ibutton_key.h>
#include <one_wire/ibutton/ibutton_worker.h>
#include <one_wire/ibutton/ibutton_protocols.h>
static void ibutton_cli(Cli* cli, FuriString* args, void* context);
static void onewire_cli(Cli* cli, FuriString* args, void* context);
@ -22,7 +26,7 @@ void ibutton_on_system_start() {
#endif
}
void ibutton_cli_print_usage() {
static void ibutton_cli_print_usage() {
printf("Usage:\r\n");
printf("ikey read\r\n");
printf("ikey emulate <key_type> <key_data>\r\n");
@ -34,30 +38,52 @@ void ibutton_cli_print_usage() {
printf("\t<key_data> are hex-formatted\r\n");
};
bool ibutton_cli_get_key_type(FuriString* data, iButtonKeyType* type) {
static bool ibutton_cli_parse_key(iButtonProtocols* protocols, iButtonKey* key, FuriString* args) {
bool result = false;
FuriString* name = furi_string_alloc();
if(furi_string_cmp_str(data, "Dallas") == 0 || furi_string_cmp_str(data, "dallas") == 0) {
result = true;
*type = iButtonKeyDS1990;
} else if(furi_string_cmp_str(data, "Cyfral") == 0 || furi_string_cmp_str(data, "cyfral") == 0) {
result = true;
*type = iButtonKeyCyfral;
} else if(furi_string_cmp_str(data, "Metakom") == 0 || furi_string_cmp_str(data, "metakom") == 0) {
result = true;
*type = iButtonKeyMetakom;
}
do {
// Read protocol name
if(!args_read_string_and_trim(args, name)) break;
// Make the protocol name uppercase
const char first = furi_string_get_char(name, 0);
furi_string_set_char(name, 0, toupper((int)first));
const iButtonProtocolId id =
ibutton_protocols_get_id_by_name(protocols, furi_string_get_cstr(name));
if(id == iButtonProtocolIdInvalid) break;
ibutton_key_set_protocol_id(key, id);
// Get the data pointer
iButtonEditableData data;
ibutton_protocols_get_editable_data(protocols, key, &data);
// Read data
if(!args_read_hex_bytes(args, data.ptr, data.size)) break;
result = true;
} while(false);
furi_string_free(name);
return result;
}
void ibutton_cli_print_key_data(iButtonKey* key) {
const uint8_t* key_data = ibutton_key_get_data_p(key);
iButtonKeyType type = ibutton_key_get_type(key);
static void ibutton_cli_print_key(iButtonProtocols* protocols, iButtonKey* key) {
const char* name = ibutton_protocols_get_name(protocols, ibutton_key_get_protocol_id(key));
printf("%s ", ibutton_key_get_string_by_type(type));
for(size_t i = 0; i < ibutton_key_get_size_by_type(type); i++) {
printf("%02X", key_data[i]);
if(strncmp(name, "DS", 2) == 0) {
name = "Dallas";
}
printf("%s ", name);
iButtonEditableData data;
ibutton_protocols_get_editable_data(protocols, key, &data);
for(size_t i = 0; i < data.size; i++) {
printf("%02X", data.ptr[i]);
}
printf("\r\n");
@ -71,9 +97,10 @@ static void ibutton_cli_worker_read_cb(void* context) {
furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE);
}
void ibutton_cli_read(Cli* cli) {
iButtonKey* key = ibutton_key_alloc();
iButtonWorker* worker = ibutton_worker_alloc();
static void ibutton_cli_read(Cli* cli) {
iButtonProtocols* protocols = ibutton_protocols_alloc();
iButtonWorker* worker = ibutton_worker_alloc(protocols);
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
FuriEventFlag* event = furi_event_flag_alloc();
ibutton_worker_start_thread(worker);
@ -81,32 +108,25 @@ void ibutton_cli_read(Cli* cli) {
printf("Reading iButton...\r\nPress Ctrl+C to abort\r\n");
ibutton_worker_read_start(worker, key);
while(true) {
uint32_t flags =
furi_event_flag_wait(event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100);
if(flags & EVENT_FLAG_IBUTTON_COMPLETE) {
ibutton_cli_print_key_data(key);
if(ibutton_key_get_type(key) == iButtonKeyDS1990) {
if(!ibutton_key_dallas_crc_is_valid(key)) {
printf("Warning: invalid CRC\r\n");
}
if(!ibutton_key_dallas_is_1990_key(key)) {
printf("Warning: not a key\r\n");
}
}
ibutton_cli_print_key(protocols, key);
break;
}
if(cli_cmd_interrupt_received(cli)) break;
}
ibutton_worker_stop(worker);
ibutton_worker_stop(worker);
ibutton_worker_stop_thread(worker);
ibutton_worker_free(worker);
ibutton_key_free(key);
ibutton_worker_free(worker);
ibutton_protocols_free(protocols);
furi_event_flag_free(event);
};
@ -124,48 +144,33 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult
}
void ibutton_cli_write(Cli* cli, FuriString* args) {
iButtonKey* key = ibutton_key_alloc();
iButtonWorker* worker = ibutton_worker_alloc();
iButtonKeyType type;
iButtonWriteContext write_context;
uint8_t key_data[IBUTTON_KEY_DATA_SIZE];
FuriString* data;
iButtonProtocols* protocols = ibutton_protocols_alloc();
iButtonWorker* worker = ibutton_worker_alloc(protocols);
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
iButtonWriteContext write_context;
write_context.event = furi_event_flag_alloc();
data = furi_string_alloc();
ibutton_worker_start_thread(worker);
ibutton_worker_write_set_callback(worker, ibutton_cli_worker_write_cb, &write_context);
do {
if(!args_read_string_and_trim(args, data)) {
if(!ibutton_cli_parse_key(protocols, key, args)) {
ibutton_cli_print_usage();
break;
}
if(!ibutton_cli_get_key_type(data, &type)) {
if(!(ibutton_protocols_get_features(protocols, ibutton_key_get_protocol_id(key)) &
iButtonProtocolFeatureWriteBlank)) {
ibutton_cli_print_usage();
break;
}
if(type != iButtonKeyDS1990) {
ibutton_cli_print_usage();
break;
}
if(!args_read_hex_bytes(args, key_data, ibutton_key_get_size_by_type(type))) {
ibutton_cli_print_usage();
break;
}
ibutton_key_set_type(key, type);
ibutton_key_set_data(key, key_data, ibutton_key_get_size_by_type(type));
printf("Writing key ");
ibutton_cli_print_key_data(key);
ibutton_cli_print_key(protocols, key);
printf("Press Ctrl+C to abort\r\n");
ibutton_worker_write_start(worker, key);
ibutton_worker_write_blank_start(worker, key);
while(true) {
uint32_t flags = furi_event_flag_wait(
write_context.event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100);
@ -183,64 +188,53 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
if(cli_cmd_interrupt_received(cli)) break;
}
ibutton_worker_stop(worker);
} while(false);
furi_string_free(data);
ibutton_worker_stop(worker);
ibutton_worker_stop_thread(worker);
ibutton_worker_free(worker);
ibutton_key_free(key);
ibutton_worker_free(worker);
ibutton_protocols_free(protocols);
furi_event_flag_free(write_context.event);
};
}
void ibutton_cli_emulate(Cli* cli, FuriString* args) {
iButtonKey* key = ibutton_key_alloc();
iButtonWorker* worker = ibutton_worker_alloc();
iButtonKeyType type;
uint8_t key_data[IBUTTON_KEY_DATA_SIZE];
FuriString* data;
iButtonProtocols* protocols = ibutton_protocols_alloc();
iButtonWorker* worker = ibutton_worker_alloc(protocols);
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
data = furi_string_alloc();
ibutton_worker_start_thread(worker);
do {
if(!args_read_string_and_trim(args, data)) {
if(!ibutton_cli_parse_key(protocols, key, args)) {
ibutton_cli_print_usage();
break;
}
if(!ibutton_cli_get_key_type(data, &type)) {
ibutton_cli_print_usage();
break;
}
if(!args_read_hex_bytes(args, key_data, ibutton_key_get_size_by_type(type))) {
ibutton_cli_print_usage();
break;
}
ibutton_key_set_type(key, type);
ibutton_key_set_data(key, key_data, ibutton_key_get_size_by_type(type));
printf("Emulating key ");
ibutton_cli_print_key_data(key);
ibutton_cli_print_key(protocols, key);
printf("Press Ctrl+C to abort\r\n");
ibutton_worker_emulate_start(worker, key);
while(!cli_cmd_interrupt_received(cli)) {
furi_delay_ms(100);
};
ibutton_worker_stop(worker);
} while(false);
furi_string_free(data);
ibutton_worker_stop(worker);
ibutton_worker_stop_thread(worker);
ibutton_worker_free(worker);
ibutton_key_free(key);
ibutton_worker_free(worker);
ibutton_protocols_free(protocols);
};
static void ibutton_cli(Cli* cli, FuriString* args, void* context) {
void ibutton_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
UNUSED(context);
FuriString* cmd;
cmd = furi_string_alloc();
@ -264,7 +258,7 @@ static void ibutton_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
void onewire_cli_print_usage() {
static void onewire_cli_print_usage() {
printf("Usage:\r\n");
printf("onewire search\r\n");
};
@ -281,7 +275,7 @@ static void onewire_cli_search(Cli* cli) {
furi_hal_power_enable_otg();
while(!done) {
if(onewire_host_search(onewire, address, NORMAL_SEARCH) != 1) {
if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) {
printf("Search finished\r\n");
onewire_host_reset_search(onewire);
done = true;

View File

@ -6,6 +6,7 @@ enum iButtonCustomEvent {
iButtonCustomEventBack,
iButtonCustomEventTextEditResult,
iButtonCustomEventByteEditChanged,
iButtonCustomEventByteEditResult,
iButtonCustomEventWorkerEmulated,
iButtonCustomEventWorkerRead,

View File

@ -4,31 +4,40 @@
#include <gui/gui.h>
#include <gui/view.h>
#include <assets_icons.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <notification/notification_messages.h>
#include <gui/view_dispatcher.h>
#include <one_wire/ibutton/ibutton_worker.h>
#include <one_wire/ibutton/ibutton_protocols.h>
#include <rpc/rpc_app.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <gui/modules/submenu.h>
#include <gui/modules/popup.h>
#include <gui/modules/dialog_ex.h>
#include <gui/modules/text_input.h>
#include <gui/modules/byte_input.h>
#include <gui/modules/widget.h>
#include <gui/modules/loading.h>
#include <assets_icons.h>
#include "ibutton_custom_event.h"
#include "scenes/ibutton_scene.h"
#define IBUTTON_FILE_NAME_SIZE 100
#define IBUTTON_TEXT_STORE_SIZE 128
#define IBUTTON_APP_FOLDER ANY_PATH("ibutton")
#define IBUTTON_APP_EXTENSION ".ibtn"
#define IBUTTON_APP_FILE_TYPE "Flipper iButton key"
#define IBUTTON_KEY_NAME_SIZE 22
typedef enum {
iButtonWriteModeInvalid,
iButtonWriteModeBlank,
iButtonWriteModeCopy,
} iButtonWriteMode;
struct iButton {
SceneManager* scene_manager;
@ -38,21 +47,22 @@ struct iButton {
Storage* storage;
DialogsApp* dialogs;
NotificationApp* notifications;
RpcAppSystem* rpc;
iButtonWorker* key_worker;
iButtonKey* key;
iButtonWorker* worker;
iButtonProtocols* protocols;
iButtonWriteMode write_mode;
FuriString* file_path;
char text_store[IBUTTON_TEXT_STORE_SIZE + 1];
char key_name[IBUTTON_KEY_NAME_SIZE + 1];
Submenu* submenu;
ByteInput* byte_input;
TextInput* text_input;
Popup* popup;
Widget* widget;
DialogEx* dialog_ex;
void* rpc_ctx;
Loading* loading;
};
typedef enum {
@ -61,7 +71,7 @@ typedef enum {
iButtonViewTextInput,
iButtonViewPopup,
iButtonViewWidget,
iButtonViewDialogEx,
iButtonViewLoading,
} iButtonView;
typedef enum {
@ -78,10 +88,12 @@ typedef enum {
iButtonNotificationMessageBlinkStop,
} iButtonNotificationMessage;
bool ibutton_file_select(iButton* ibutton);
bool ibutton_load_key_data(iButton* ibutton, FuriString* key_path, bool show_dialog);
bool ibutton_save_key(iButton* ibutton, const char* key_name);
bool ibutton_select_and_load_key(iButton* ibutton);
bool ibutton_load_key(iButton* ibutton);
bool ibutton_save_key(iButton* ibutton);
bool ibutton_delete_key(iButton* ibutton);
void ibutton_text_store_set(iButton* ibutton, const char* text, ...);
void ibutton_text_store_clear(iButton* ibutton);
void ibutton_reset_key(iButton* ibutton);
void ibutton_notification_message(iButton* ibutton, uint32_t message);
void ibutton_submenu_callback(void* context, uint32_t index);
void ibutton_widget_callback(GuiButtonType result, InputType type, void* context);

View File

@ -1,54 +1,48 @@
#include "../ibutton_i.h"
enum SubmenuIndex {
SubmenuIndexCyfral,
SubmenuIndexDallas,
SubmenuIndexMetakom,
};
void ibutton_scene_add_type_submenu_callback(void* context, uint32_t index) {
iButton* ibutton = context;
view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
}
void ibutton_scene_add_type_on_enter(void* context) {
iButton* ibutton = context;
Submenu* submenu = ibutton->submenu;
submenu_add_item(
submenu, "Cyfral", SubmenuIndexCyfral, ibutton_scene_add_type_submenu_callback, ibutton);
submenu_add_item(
submenu, "Dallas", SubmenuIndexDallas, ibutton_scene_add_type_submenu_callback, ibutton);
submenu_add_item(
submenu, "Metakom", SubmenuIndexMetakom, ibutton_scene_add_type_submenu_callback, ibutton);
FuriString* tmp = furi_string_alloc();
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddType));
for(uint32_t protocol_id = 0; protocol_id < ibutton_protocols_get_protocol_count();
++protocol_id) {
furi_string_printf(
tmp,
"%s %s",
ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id),
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
submenu_add_item(
submenu, furi_string_get_cstr(tmp), protocol_id, ibutton_submenu_callback, context);
}
const uint32_t prev_protocol_id =
scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddType);
submenu_set_selected_item(submenu, prev_protocol_id);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
furi_string_free(tmp);
}
bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) {
iButton* ibutton = context;
iButtonKey* key = ibutton->key;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneAddType, event.event);
consumed = true;
if(event.event == SubmenuIndexCyfral) {
ibutton_key_set_type(key, iButtonKeyCyfral);
} else if(event.event == SubmenuIndexDallas) {
ibutton_key_set_type(key, iButtonKeyDS1990);
} else if(event.event == SubmenuIndexMetakom) {
ibutton_key_set_type(key, iButtonKeyMetakom);
} else {
furi_crash("Unknown key type");
}
const iButtonProtocolId protocol_id = event.event;
furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
ibutton_key_clear_data(key);
ibutton_key_reset(key);
ibutton_key_set_protocol_id(key, protocol_id);
ibutton_protocols_apply_edits(ibutton->protocols, key);
scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneAddType, protocol_id);
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue);
consumed = true;
}
return consumed;

View File

@ -1,42 +1,52 @@
#include "../ibutton_i.h"
void ibutton_scene_add_type_byte_input_callback(void* context) {
static void ibutton_scene_add_type_byte_input_callback(void* context) {
iButton* ibutton = context;
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditResult);
}
static void ibutton_scene_add_type_byte_changed_callback(void* context) {
iButton* ibutton = context;
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditChanged);
}
void ibutton_scene_add_value_on_enter(void* context) {
iButton* ibutton = context;
iButtonKey* key = ibutton->key;
uint8_t* new_key_data = malloc(IBUTTON_KEY_DATA_SIZE);
byte_input_set_header_text(ibutton->byte_input, "Enter the key");
scene_manager_set_scene_state(
ibutton->scene_manager, iButtonSceneAddValue, (uint32_t)new_key_data);
memcpy(new_key_data, ibutton_key_get_data_p(key), ibutton_key_get_data_size(key));
iButtonEditableData editable_data;
ibutton_protocols_get_editable_data(ibutton->protocols, ibutton->key, &editable_data);
byte_input_set_result_callback(
ibutton->byte_input,
ibutton_scene_add_type_byte_input_callback,
NULL,
ibutton,
new_key_data,
ibutton_key_get_data_size(key));
ibutton_scene_add_type_byte_changed_callback,
context,
editable_data.ptr,
editable_data.size);
byte_input_set_header_text(ibutton->byte_input, "Enter the key");
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewByteInput);
}
bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
iButton* ibutton = context;
uint8_t* new_key_data =
(uint8_t*)scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddValue);
SceneManager* scene_manager = ibutton->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventByteEditResult) {
ibutton_key_set_data(ibutton->key, new_key_data, IBUTTON_KEY_DATA_SIZE);
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName);
scene_manager_next_scene(scene_manager, iButtonSceneSaveName);
} else if(event.event == iButtonCustomEventByteEditChanged) {
ibutton_protocols_apply_edits(ibutton->protocols, ibutton->key);
}
} else if(event.type == SceneManagerEventTypeBack) {
// User cancelled editing, reload the key from storage
if(scene_manager_has_previous_scene(scene_manager, iButtonSceneSavedKeyMenu)) {
if(!ibutton_load_key(ibutton)) {
consumed = scene_manager_search_and_switch_to_previous_scene(
scene_manager, iButtonSceneStart);
}
}
}
@ -45,10 +55,7 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
void ibutton_scene_add_value_on_exit(void* context) {
iButton* ibutton = context;
uint8_t* new_key_data =
(uint8_t*)scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddValue);
byte_input_set_result_callback(ibutton->byte_input, NULL, NULL, NULL, NULL, 0);
byte_input_set_header_text(ibutton->byte_input, NULL);
free(new_key_data);
}

View File

@ -6,8 +6,7 @@ ADD_SCENE(ibutton, info, Info)
ADD_SCENE(ibutton, read, Read)
ADD_SCENE(ibutton, read_key_menu, ReadKeyMenu)
ADD_SCENE(ibutton, read_success, ReadSuccess)
ADD_SCENE(ibutton, read_crc_error, ReadCRCError)
ADD_SCENE(ibutton, read_not_key_error, ReadNotKeyError)
ADD_SCENE(ibutton, read_error, ReadError)
ADD_SCENE(ibutton, select_key, SelectKey)
ADD_SCENE(ibutton, add_type, AddType)
ADD_SCENE(ibutton, add_value, AddValue)
@ -18,4 +17,5 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm)
ADD_SCENE(ibutton, delete_success, DeleteSuccess)
ADD_SCENE(ibutton, retry_confirm, RetryConfirm)
ADD_SCENE(ibutton, exit_confirm, ExitConfirm)
ADD_SCENE(ibutton, view_data, ViewData)
ADD_SCENE(ibutton, rpc, Rpc)

View File

@ -1,74 +1,29 @@
#include "../ibutton_i.h"
#include <toolbox/path.h>
static void ibutton_scene_delete_confirm_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
iButton* ibutton = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
}
}
void ibutton_scene_delete_confirm_on_enter(void* context) {
iButton* ibutton = context;
Widget* widget = ibutton->widget;
iButtonKey* key = ibutton->key;
const uint8_t* key_data = ibutton_key_get_data_p(key);
Widget* widget = ibutton->widget;
FuriString* key_name;
key_name = furi_string_alloc();
path_extract_filename(ibutton->file_path, key_name, true);
FuriString* tmp = furi_string_alloc();
ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", furi_string_get_cstr(key_name));
widget_add_text_box_element(
widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, true);
widget_add_button_element(widget, GuiButtonTypeLeft, "Back", ibutton_widget_callback, context);
widget_add_button_element(
widget, GuiButtonTypeLeft, "Cancel", ibutton_scene_delete_confirm_widget_callback, ibutton);
widget_add_button_element(
widget,
GuiButtonTypeRight,
"Delete",
ibutton_scene_delete_confirm_widget_callback,
ibutton);
widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context);
switch(ibutton_key_get_type(key)) {
case iButtonKeyDS1990:
ibutton_text_store_set(
ibutton,
"%02X %02X %02X %02X %02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
key_data[3],
key_data[4],
key_data[5],
key_data[6],
key_data[7]);
widget_add_string_element(
widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Dallas");
break;
case iButtonKeyCyfral:
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
widget_add_string_element(
widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Cyfral");
break;
case iButtonKeyMetakom:
ibutton_text_store_set(
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
widget_add_string_element(
widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Metakom");
break;
}
furi_string_printf(tmp, "Delete %s?", ibutton->key_name);
widget_add_string_element(
widget, 64, 46, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store);
widget, 128 / 2, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
furi_string_reset(tmp);
ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
widget_add_string_multiline_element(
widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
furi_string_free(key_name);
furi_string_free(tmp);
}
bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) {
@ -81,8 +36,10 @@ bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent even
if(event.event == GuiButtonTypeRight) {
if(ibutton_delete_key(ibutton)) {
scene_manager_next_scene(scene_manager, iButtonSceneDeleteSuccess);
} else {
dialog_message_show_storage_error(ibutton->dialogs, "Cannot delete\nkey file");
scene_manager_previous_scene(scene_manager);
}
//TODO: What if the key could not be deleted?
} else if(event.event == GuiButtonTypeLeft) {
scene_manager_previous_scene(scene_manager);
}
@ -93,6 +50,5 @@ bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent even
void ibutton_scene_delete_confirm_on_exit(void* context) {
iButton* ibutton = context;
ibutton_text_store_clear(ibutton);
widget_reset(ibutton->widget);
}

View File

@ -14,61 +14,32 @@ static void ibutton_scene_emulate_callback(void* context, bool emulated) {
void ibutton_scene_emulate_on_enter(void* context) {
iButton* ibutton = context;
Widget* widget = ibutton->widget;
iButtonKey* key = ibutton->key;
const uint8_t* key_data = ibutton_key_get_data_p(key);
Widget* widget = ibutton->widget;
FuriString* tmp = furi_string_alloc();
FuriString* key_name;
key_name = furi_string_alloc();
if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
path_extract_filename(ibutton->file_path, key_name, true);
}
widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
// check that stored key has name
if(!furi_string_empty(key_name)) {
ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
} else {
// if not, show key data
switch(ibutton_key_get_type(key)) {
case iButtonKeyDS1990:
ibutton_text_store_set(
ibutton,
"%02X %02X %02X %02X\n%02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
key_data[3],
key_data[4],
key_data[5],
key_data[6],
key_data[7]);
break;
case iButtonKeyCyfral:
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
break;
case iButtonKeyMetakom:
ibutton_text_store_set(
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
break;
}
}
furi_string_printf(
tmp,
"%s\n[%s]",
furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name,
ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key)));
widget_add_text_box_element(
widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
widget_add_string_multiline_element(
widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating");
widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
widget_add_text_box_element(
widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true);
widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating");
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
ibutton_worker_emulate_set_callback(
ibutton->key_worker, ibutton_scene_emulate_callback, ibutton);
ibutton_worker_emulate_start(ibutton->key_worker, key);
furi_string_free(key_name);
ibutton_worker_emulate_set_callback(ibutton->worker, ibutton_scene_emulate_callback, ibutton);
ibutton_worker_emulate_start(ibutton->worker, key);
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
furi_string_free(tmp);
}
bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
@ -78,8 +49,7 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeTick) {
uint32_t cnt = scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneEmulate);
if(cnt > 0) {
cnt--;
if(cnt == 0) {
if(--cnt == 0) {
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink);
}
scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneEmulate, cnt);
@ -101,7 +71,7 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
void ibutton_scene_emulate_on_exit(void* context) {
iButton* ibutton = context;
ibutton_worker_stop(ibutton->key_worker);
ibutton_worker_stop(ibutton->worker);
widget_reset(ibutton->widget);
ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
}

View File

@ -19,7 +19,7 @@ void ibutton_scene_exit_confirm_on_enter(void* context) {
widget_add_button_element(
widget, GuiButtonTypeRight, "Stay", ibutton_scene_exit_confirm_widget_callback, ibutton);
widget_add_string_element(
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Exit to iButton menu?");
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Exit to iButton Menu?");
widget_add_string_element(
widget, 64, 31, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!");

View File

@ -1,66 +1,54 @@
#include "../ibutton_i.h"
#include <toolbox/path.h>
void ibutton_scene_info_on_enter(void* context) {
iButton* ibutton = context;
Widget* widget = ibutton->widget;
iButtonKey* key = ibutton->key;
Widget* widget = ibutton->widget;
const uint8_t* key_data = ibutton_key_get_data_p(key);
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
FuriString* key_name;
key_name = furi_string_alloc();
path_extract_filename(ibutton->file_path, key_name, true);
FuriString* tmp = furi_string_alloc();
furi_string_printf(
tmp,
"\e#%s [%s]\e#",
ibutton->key_name,
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
widget_add_text_box_element(
widget, 0, 0, 128, 23, AlignCenter, AlignCenter, ibutton->text_store, true);
widget, 0, 2, 128, 12, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true);
switch(ibutton_key_get_type(key)) {
case iButtonKeyDS1990:
ibutton_text_store_set(
ibutton,
"%02X %02X %02X %02X %02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
key_data[3],
key_data[4],
key_data[5],
key_data[6],
key_data[7]);
widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Dallas");
break;
furi_string_reset(tmp);
ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
case iButtonKeyMetakom:
ibutton_text_store_set(
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
widget_add_string_element(
widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Metakom");
break;
widget_add_string_multiline_element(
widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
case iButtonKeyCyfral:
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Cyfral");
break;
if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) &
iButtonProtocolFeatureExtData) {
widget_add_button_element(
widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context);
}
widget_add_string_element(
widget, 64, 50, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
furi_string_free(key_name);
furi_string_free(tmp);
}
bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
iButton* ibutton = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == GuiButtonTypeRight) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneViewData);
}
}
return consumed;
}
void ibutton_scene_info_on_exit(void* context) {
iButton* ibutton = context;
ibutton_text_store_clear(ibutton);
widget_reset(ibutton->widget);
}

View File

@ -10,14 +10,13 @@ void ibutton_scene_read_on_enter(void* context) {
iButton* ibutton = context;
Popup* popup = ibutton->popup;
iButtonKey* key = ibutton->key;
iButtonWorker* worker = ibutton->key_worker;
iButtonWorker* worker = ibutton->worker;
popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom);
popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop);
popup_set_text(popup, "Apply key to\nFlipper's back", 95, 30, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton);
ibutton_worker_read_start(worker, key);
@ -35,25 +34,14 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) {
} else if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventWorkerRead) {
bool success = false;
iButtonKey* key = ibutton->key;
if(ibutton_key_get_type(key) == iButtonKeyDS1990) {
if(!ibutton_key_dallas_crc_is_valid(key)) {
scene_manager_next_scene(scene_manager, iButtonSceneReadCRCError);
} else if(!ibutton_key_dallas_is_1990_key(key)) {
scene_manager_next_scene(scene_manager, iButtonSceneReadNotKeyError);
} else {
success = true;
}
} else {
success = true;
}
if(success) {
if(ibutton_protocols_is_valid(ibutton->protocols, ibutton->key)) {
ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess);
scene_manager_next_scene(scene_manager, iButtonSceneReadSuccess);
DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess);
} else {
scene_manager_next_scene(scene_manager, iButtonSceneReadError);
}
}
}
@ -64,7 +52,7 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) {
void ibutton_scene_read_on_exit(void* context) {
iButton* ibutton = context;
Popup* popup = ibutton->popup;
ibutton_worker_stop(ibutton->key_worker);
ibutton_worker_stop(ibutton->worker);
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 0, NULL);

View File

@ -1,70 +0,0 @@
#include "../ibutton_i.h"
#include <one_wire/maxim_crc.h>
static void ibutton_scene_read_crc_error_dialog_ex_callback(DialogExResult result, void* context) {
iButton* ibutton = context;
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
}
void ibutton_scene_read_crc_error_on_enter(void* context) {
iButton* ibutton = context;
DialogEx* dialog_ex = ibutton->dialog_ex;
iButtonKey* key = ibutton->key;
const uint8_t* key_data = ibutton_key_get_data_p(key);
ibutton_text_store_set(
ibutton,
"%02X %02X %02X %02X %02X %02X %02X %02X\nExpected CRC: %X",
key_data[0],
key_data[1],
key_data[2],
key_data[3],
key_data[4],
key_data[5],
key_data[6],
key_data[7],
maxim_crc8(key_data, 7, MAXIM_CRC8_INIT));
dialog_ex_set_header(dialog_ex, "CRC ERROR", 64, 10, AlignCenter, AlignCenter);
dialog_ex_set_text(dialog_ex, ibutton->text_store, 64, 19, AlignCenter, AlignTop);
dialog_ex_set_left_button_text(dialog_ex, "Retry");
dialog_ex_set_right_button_text(dialog_ex, "More");
dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_crc_error_dialog_ex_callback);
dialog_ex_set_context(dialog_ex, ibutton);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx);
ibutton_notification_message(ibutton, iButtonNotificationMessageError);
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn);
}
bool ibutton_scene_read_crc_error_on_event(void* context, SceneManagerEvent event) {
iButton* ibutton = context;
SceneManager* scene_manager = ibutton->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeBack) {
consumed = true;
scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
} else if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == DialogExResultRight) {
scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
} else if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(scene_manager);
}
}
return consumed;
}
void ibutton_scene_read_crc_error_on_exit(void* context) {
iButton* ibutton = context;
DialogEx* dialog_ex = ibutton->dialog_ex;
ibutton_text_store_clear(ibutton);
dialog_ex_reset(dialog_ex);
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff);
}

View File

@ -0,0 +1,58 @@
#include "../ibutton_i.h"
#include <one_wire/maxim_crc.h>
void ibutton_scene_read_error_on_enter(void* context) {
iButton* ibutton = context;
iButtonKey* key = ibutton->key;
Widget* widget = ibutton->widget;
FuriString* tmp = furi_string_alloc();
widget_add_button_element(
widget, GuiButtonTypeLeft, "Retry", ibutton_widget_callback, context);
widget_add_button_element(
widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context);
widget_add_string_element(
widget, 128 / 2, 2, AlignCenter, AlignTop, FontPrimary, "Read Error");
ibutton_protocols_render_error(ibutton->protocols, key, tmp);
widget_add_string_multiline_element(
widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
ibutton_notification_message(ibutton, iButtonNotificationMessageError);
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
furi_string_free(tmp);
}
bool ibutton_scene_read_error_on_event(void* context, SceneManagerEvent event) {
iButton* ibutton = context;
SceneManager* scene_manager = ibutton->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeBack) {
consumed = true;
scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
} else if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == GuiButtonTypeLeft) {
scene_manager_previous_scene(scene_manager);
} else if(event.event == GuiButtonTypeRight) {
scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
}
}
return consumed;
}
void ibutton_scene_read_error_on_exit(void* context) {
iButton* ibutton = context;
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff);
widget_reset(ibutton->widget);
}

View File

@ -4,7 +4,9 @@
typedef enum {
SubmenuIndexSave,
SubmenuIndexEmulate,
SubmenuIndexWrite,
SubmenuIndexViewData,
SubmenuIndexWriteBlank,
SubmenuIndexWriteCopy,
} SubmenuIndex;
void ibutton_scene_read_key_menu_submenu_callback(void* context, uint32_t index) {
@ -16,6 +18,9 @@ void ibutton_scene_read_key_menu_on_enter(void* context) {
iButton* ibutton = context;
Submenu* submenu = ibutton->submenu;
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(ibutton->key);
const uint32_t features = ibutton_protocols_get_features(ibutton->protocols, protocol_id);
submenu_add_item(
submenu, "Save", SubmenuIndexSave, ibutton_scene_read_key_menu_submenu_callback, ibutton);
submenu_add_item(
@ -24,36 +29,66 @@ void ibutton_scene_read_key_menu_on_enter(void* context) {
SubmenuIndexEmulate,
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
if(ibutton_key_get_type(ibutton->key) == iButtonKeyDS1990) {
if(features & iButtonProtocolFeatureExtData) {
submenu_add_item(
submenu,
"Write",
SubmenuIndexWrite,
"View Data",
SubmenuIndexViewData,
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
}
if(features & iButtonProtocolFeatureWriteBlank) {
submenu_add_item(
submenu,
"Write Blank",
SubmenuIndexWriteBlank,
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
}
if(features & iButtonProtocolFeatureWriteCopy) {
submenu_add_item(
submenu,
"Write Copy",
SubmenuIndexWriteCopy,
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
}
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneReadKeyMenu));
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
}
bool ibutton_scene_read_key_menu_on_event(void* context, SceneManagerEvent event) {
iButton* ibutton = context;
SceneManager* scene_manager = ibutton->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(
ibutton->scene_manager, iButtonSceneReadKeyMenu, event.event);
scene_manager_set_scene_state(scene_manager, iButtonSceneReadKeyMenu, event.event);
consumed = true;
if(event.event == SubmenuIndexSave) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName);
scene_manager_next_scene(scene_manager, iButtonSceneSaveName);
} else if(event.event == SubmenuIndexEmulate) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
scene_manager_next_scene(scene_manager, iButtonSceneEmulate);
DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite);
} else if(event.event == SubmenuIndexViewData) {
scene_manager_next_scene(scene_manager, iButtonSceneViewData);
} else if(event.event == SubmenuIndexWriteBlank) {
ibutton->write_mode = iButtonWriteModeBlank;
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
} else if(event.event == SubmenuIndexWriteCopy) {
ibutton->write_mode = iButtonWriteModeCopy;
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
}
} else if(event.event == SceneManagerEventTypeBack) {
scene_manager_set_scene_state(
ibutton->scene_manager, iButtonSceneReadKeyMenu, SubmenuIndexSave);
// Event is not consumed
}
return consumed;

View File

@ -1,71 +0,0 @@
#include "../ibutton_i.h"
#include <one_wire/maxim_crc.h>
static void
ibutton_scene_read_not_key_error_dialog_ex_callback(DialogExResult result, void* context) {
iButton* ibutton = context;
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
}
void ibutton_scene_read_not_key_error_on_enter(void* context) {
iButton* ibutton = context;
DialogEx* dialog_ex = ibutton->dialog_ex;
iButtonKey* key = ibutton->key;
const uint8_t* key_data = ibutton_key_get_data_p(key);
ibutton_text_store_set(
ibutton,
"THIS IS NOT A KEY\n%02X %02X %02X %02X %02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
key_data[3],
key_data[4],
key_data[5],
key_data[6],
key_data[7],
maxim_crc8(key_data, 7, MAXIM_CRC8_INIT));
dialog_ex_set_header(dialog_ex, "CRC ERROR", 64, 10, AlignCenter, AlignCenter);
dialog_ex_set_text(dialog_ex, ibutton->text_store, 64, 19, AlignCenter, AlignTop);
dialog_ex_set_left_button_text(dialog_ex, "Retry");
dialog_ex_set_right_button_text(dialog_ex, "More");
dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_not_key_error_dialog_ex_callback);
dialog_ex_set_context(dialog_ex, ibutton);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx);
ibutton_notification_message(ibutton, iButtonNotificationMessageError);
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn);
}
bool ibutton_scene_read_not_key_error_on_event(void* context, SceneManagerEvent event) {
iButton* ibutton = context;
SceneManager* scene_manager = ibutton->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeBack) {
consumed = true;
scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
} else if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == DialogExResultRight) {
scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
} else if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(scene_manager);
}
}
return consumed;
}
void ibutton_scene_read_not_key_error_on_exit(void* context) {
iButton* ibutton = context;
DialogEx* dialog_ex = ibutton->dialog_ex;
ibutton_text_store_clear(ibutton);
dialog_ex_reset(dialog_ex);
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff);
}

View File

@ -1,55 +1,40 @@
#include "../ibutton_i.h"
#include <dolphin/dolphin.h>
static void ibutton_scene_read_success_dialog_ex_callback(DialogExResult result, void* context) {
iButton* ibutton = context;
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
}
#include <dolphin/dolphin.h>
void ibutton_scene_read_success_on_enter(void* context) {
iButton* ibutton = context;
DialogEx* dialog_ex = ibutton->dialog_ex;
iButtonKey* key = ibutton->key;
const uint8_t* key_data = ibutton_key_get_data_p(key);
Widget* widget = ibutton->widget;
switch(ibutton_key_get_type(key)) {
case iButtonKeyDS1990:
ibutton_text_store_set(
ibutton,
"Dallas\n%02X %02X %02X %02X\n%02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
key_data[3],
key_data[4],
key_data[5],
key_data[6],
key_data[7]);
break;
case iButtonKeyCyfral:
ibutton_text_store_set(ibutton, "Cyfral\n%02X %02X", key_data[0], key_data[1]);
break;
case iButtonKeyMetakom:
ibutton_text_store_set(
ibutton,
"Metakom\n%02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
key_data[3]);
break;
}
FuriString* tmp = furi_string_alloc();
dialog_ex_set_text(dialog_ex, ibutton->text_store, 95, 30, AlignCenter, AlignCenter);
dialog_ex_set_left_button_text(dialog_ex, "Retry");
dialog_ex_set_right_button_text(dialog_ex, "More");
dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63);
dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_success_dialog_ex_callback);
dialog_ex_set_context(dialog_ex, ibutton);
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx);
widget_add_button_element(
widget, GuiButtonTypeLeft, "Retry", ibutton_widget_callback, context);
widget_add_button_element(
widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context);
furi_string_printf(
tmp,
"%s[%s]",
ibutton_protocols_get_name(ibutton->protocols, protocol_id),
ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id));
widget_add_string_element(
widget, 0, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
furi_string_reset(tmp);
ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
widget_add_string_multiline_element(
widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn);
furi_string_free(tmp);
}
bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) {
@ -62,9 +47,9 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event)
scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
} else if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == DialogExResultRight) {
if(event.event == GuiButtonTypeRight) {
scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
} else if(event.event == DialogExResultLeft) {
} else if(event.event == GuiButtonTypeLeft) {
scene_manager_next_scene(scene_manager, iButtonSceneRetryConfirm);
}
}
@ -74,11 +59,8 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event)
void ibutton_scene_read_success_on_exit(void* context) {
iButton* ibutton = context;
DialogEx* dialog_ex = ibutton->dialog_ex;
ibutton_text_store_clear(ibutton);
dialog_ex_reset(dialog_ex);
widget_reset(ibutton->widget);
ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOff);
}

View File

@ -19,7 +19,7 @@ void ibutton_scene_retry_confirm_on_enter(void* context) {
widget_add_button_element(
widget, GuiButtonTypeRight, "Stay", ibutton_scene_retry_confirm_widget_callback, ibutton);
widget_add_string_element(
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Return to reading?");
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?");
widget_add_string_element(
widget, 64, 29, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!");

View File

@ -1,6 +1,4 @@
#include "../ibutton_i.h"
#include <toolbox/path.h>
#include <rpc/rpc_app.h>
void ibutton_scene_rpc_on_enter(void* context) {
iButton* ibutton = context;
@ -17,8 +15,6 @@ void ibutton_scene_rpc_on_enter(void* context) {
}
bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
iButton* ibutton = context;
Popup* popup = ibutton->popup;
@ -26,40 +22,32 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventRpcLoad) {
const char* arg = rpc_system_app_get_data(ibutton->rpc_ctx);
bool result = false;
if(arg && (furi_string_empty(ibutton->file_path))) {
furi_string_set(ibutton->file_path, arg);
if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) {
ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key);
FuriString* key_name;
key_name = furi_string_alloc();
if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
path_extract_filename(ibutton->file_path, key_name, true);
}
if(!furi_string_empty(key_name)) {
ibutton_text_store_set(
ibutton, "emulating\n%s", furi_string_get_cstr(key_name));
} else {
ibutton_text_store_set(ibutton, "emulating");
}
popup_set_text(popup, ibutton->text_store, 82, 32, AlignCenter, AlignTop);
if(event.event == iButtonCustomEventRpcLoad) {
bool result = false;
const char* file_path = rpc_system_app_get_data(ibutton->rpc);
if(file_path && (furi_string_empty(ibutton->file_path))) {
furi_string_set(ibutton->file_path, file_path);
if(ibutton_load_key(ibutton)) {
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
ibutton_worker_emulate_start(ibutton->worker, ibutton->key);
furi_string_free(key_name);
result = true;
} else {
furi_string_reset(ibutton->file_path);
}
}
rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventLoadFile, result);
rpc_system_app_confirm(ibutton->rpc, RpcAppEventLoadFile, result);
} else if(event.event == iButtonCustomEventRpcExit) {
rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventAppExit, true);
rpc_system_app_confirm(ibutton->rpc, RpcAppEventAppExit, true);
scene_manager_stop(ibutton->scene_manager);
view_dispatcher_stop(ibutton->view_dispatcher);
} else if(event.event == iButtonCustomEventRpcSessionClose) {
scene_manager_stop(ibutton->scene_manager);
view_dispatcher_stop(ibutton->view_dispatcher);

View File

@ -1,6 +1,8 @@
#include "../ibutton_i.h"
#include <lib/toolbox/random_name.h>
#include <toolbox/random_name.h>
#include <toolbox/path.h>
#include <dolphin/dolphin.h>
static void ibutton_scene_save_name_text_input_callback(void* context) {
@ -12,17 +14,10 @@ void ibutton_scene_save_name_on_enter(void* context) {
iButton* ibutton = context;
TextInput* text_input = ibutton->text_input;
FuriString* key_name;
key_name = furi_string_alloc();
if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
path_extract_filename(ibutton->file_path, key_name, true);
}
const bool is_new_file = furi_string_empty(ibutton->file_path);
const bool key_name_is_empty = furi_string_empty(key_name);
if(key_name_is_empty) {
set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE);
} else {
ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
if(is_new_file) {
set_random_name(ibutton->key_name, IBUTTON_KEY_NAME_SIZE);
}
text_input_set_header_text(text_input, "Name the key");
@ -30,23 +25,15 @@ void ibutton_scene_save_name_on_enter(void* context) {
text_input,
ibutton_scene_save_name_text_input_callback,
ibutton,
ibutton->text_store,
ibutton->key_name,
IBUTTON_KEY_NAME_SIZE,
key_name_is_empty);
is_new_file);
FuriString* folder_path;
folder_path = furi_string_alloc();
path_extract_dirname(furi_string_get_cstr(ibutton->file_path), folder_path);
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
furi_string_get_cstr(folder_path), IBUTTON_APP_EXTENSION, furi_string_get_cstr(key_name));
ValidatorIsFile* validator_is_file =
validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, ibutton->key_name);
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput);
furi_string_free(key_name);
furi_string_free(folder_path);
}
bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
@ -56,8 +43,16 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventTextEditResult) {
if(ibutton_save_key(ibutton, ibutton->text_store)) {
furi_string_printf(
ibutton->file_path,
"%s/%s%s",
IBUTTON_APP_FOLDER,
ibutton->key_name,
IBUTTON_APP_EXTENSION);
if(ibutton_save_key(ibutton)) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
if(scene_manager_has_previous_scene(
ibutton->scene_manager, iButtonSceneSavedKeyMenu)) {
// Nothing, do not count editing as saving
@ -67,6 +62,7 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
} else {
DOLPHIN_DEED(DolphinDeedIbuttonSave);
}
} else {
const uint32_t possible_scenes[] = {
iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType};

View File

@ -3,72 +3,70 @@
enum SubmenuIndex {
SubmenuIndexEmulate,
SubmenuIndexWrite,
SubmenuIndexWriteBlank,
SubmenuIndexWriteCopy,
SubmenuIndexEdit,
SubmenuIndexDelete,
SubmenuIndexInfo,
};
void ibutton_scene_saved_key_menu_submenu_callback(void* context, uint32_t index) {
iButton* ibutton = context;
view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
}
void ibutton_scene_saved_key_menu_on_enter(void* context) {
iButton* ibutton = context;
Submenu* submenu = ibutton->submenu;
submenu_add_item(
submenu,
"Emulate",
SubmenuIndexEmulate,
ibutton_scene_saved_key_menu_submenu_callback,
ibutton);
if(ibutton_key_get_type(ibutton->key) == iButtonKeyDS1990) {
const uint32_t features = ibutton_protocols_get_features(
ibutton->protocols, ibutton_key_get_protocol_id(ibutton->key));
submenu_add_item(submenu, "Emulate", SubmenuIndexEmulate, ibutton_submenu_callback, ibutton);
if(features & iButtonProtocolFeatureWriteBlank) {
submenu_add_item(
submenu,
"Write",
SubmenuIndexWrite,
ibutton_scene_saved_key_menu_submenu_callback,
ibutton);
submenu, "Write Blank", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton);
}
submenu_add_item(
submenu, "Edit", SubmenuIndexEdit, ibutton_scene_saved_key_menu_submenu_callback, ibutton);
submenu_add_item(
submenu,
"Delete",
SubmenuIndexDelete,
ibutton_scene_saved_key_menu_submenu_callback,
ibutton);
submenu_add_item(
submenu, "Info", SubmenuIndexInfo, ibutton_scene_saved_key_menu_submenu_callback, ibutton);
if(features & iButtonProtocolFeatureWriteCopy) {
submenu_add_item(
submenu, "Write Copy", SubmenuIndexWriteCopy, ibutton_submenu_callback, ibutton);
}
submenu_add_item(submenu, "Edit", SubmenuIndexEdit, ibutton_submenu_callback, ibutton);
submenu_add_item(submenu, "Delete", SubmenuIndexDelete, ibutton_submenu_callback, ibutton);
submenu_add_item(submenu, "Info", SubmenuIndexInfo, ibutton_submenu_callback, ibutton);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneSavedKeyMenu));
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
}
bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event) {
iButton* ibutton = context;
SceneManager* scene_manager = ibutton->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(
ibutton->scene_manager, iButtonSceneSavedKeyMenu, event.event);
scene_manager_set_scene_state(scene_manager, iButtonSceneSavedKeyMenu, event.event);
consumed = true;
if(event.event == SubmenuIndexEmulate) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
scene_manager_next_scene(scene_manager, iButtonSceneEmulate);
DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite);
} else if(event.event == SubmenuIndexWriteBlank) {
ibutton->write_mode = iButtonWriteModeBlank;
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
} else if(event.event == SubmenuIndexWriteCopy) {
ibutton->write_mode = iButtonWriteModeCopy;
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
} else if(event.event == SubmenuIndexEdit) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue);
scene_manager_next_scene(scene_manager, iButtonSceneAddValue);
} else if(event.event == SubmenuIndexDelete) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneDeleteConfirm);
scene_manager_next_scene(scene_manager, iButtonSceneDeleteConfirm);
} else if(event.event == SubmenuIndexInfo) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneInfo);
scene_manager_next_scene(scene_manager, iButtonSceneInfo);
}
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_set_scene_state(
scene_manager, iButtonSceneSavedKeyMenu, SubmenuIndexEmulate);
// Event is not consumed
}
return consumed;

View File

@ -3,11 +3,11 @@
void ibutton_scene_select_key_on_enter(void* context) {
iButton* ibutton = context;
if(!ibutton_file_select(ibutton)) {
if(ibutton_select_and_load_key(ibutton)) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSavedKeyMenu);
} else {
scene_manager_search_and_switch_to_previous_scene(
ibutton->scene_manager, iButtonSceneStart);
} else {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSavedKeyMenu);
}
}

View File

@ -8,21 +8,15 @@ enum SubmenuIndex {
SubmenuIndexAdd,
};
void ibutton_scene_start_submenu_callback(void* context, uint32_t index) {
iButton* ibutton = context;
view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
}
void ibutton_scene_start_on_enter(void* context) {
iButton* ibutton = context;
Submenu* submenu = ibutton->submenu;
submenu_add_item(
submenu, "Read", SubmenuIndexRead, ibutton_scene_start_submenu_callback, ibutton);
submenu_add_item(
submenu, "Saved", SubmenuIndexSaved, ibutton_scene_start_submenu_callback, ibutton);
submenu_add_item(
submenu, "Add Manually", SubmenuIndexAdd, ibutton_scene_start_submenu_callback, ibutton);
ibutton_reset_key(ibutton);
submenu_add_item(submenu, "Read", SubmenuIndexRead, ibutton_submenu_callback, ibutton);
submenu_add_item(submenu, "Saved", SubmenuIndexSaved, ibutton_submenu_callback, ibutton);
submenu_add_item(submenu, "Add Manually", SubmenuIndexAdd, ibutton_submenu_callback, ibutton);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneStart));
@ -41,7 +35,6 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead);
DOLPHIN_DEED(DolphinDeedIbuttonRead);
} else if(event.event == SubmenuIndexSaved) {
furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey);
} else if(event.event == SubmenuIndexAdd) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType);

View File

@ -0,0 +1,26 @@
#include "../ibutton_i.h"
void ibutton_scene_view_data_on_enter(void* context) {
iButton* ibutton = context;
iButtonKey* key = ibutton->key;
Widget* widget = ibutton->widget;
FuriString* tmp = furi_string_alloc();
ibutton_protocols_render_data(ibutton->protocols, key, tmp);
widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(tmp));
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
furi_string_free(tmp);
}
bool ibutton_scene_view_data_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void ibutton_scene_view_data_on_exit(void* context) {
iButton* ibutton = context;
widget_reset(ibutton->widget);
}

View File

@ -1,5 +1,4 @@
#include "../ibutton_i.h"
#include "toolbox/path.h"
typedef enum {
iButtonSceneWriteStateDefault,
@ -13,61 +12,46 @@ static void ibutton_scene_write_callback(void* context, iButtonWorkerWriteResult
void ibutton_scene_write_on_enter(void* context) {
iButton* ibutton = context;
furi_assert(ibutton->write_mode != iButtonWriteModeInvalid);
iButtonKey* key = ibutton->key;
iButtonWorker* worker = ibutton->worker;
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
Widget* widget = ibutton->widget;
iButtonWorker* worker = ibutton->key_worker;
FuriString* tmp = furi_string_alloc();
const uint8_t* key_data = ibutton_key_get_data_p(key);
widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
FuriString* key_name;
key_name = furi_string_alloc();
if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
path_extract_filename(ibutton->file_path, key_name, true);
}
furi_string_printf(
tmp,
"%s\n[%s]",
ibutton->key_name,
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
// check that stored key has name
if(!furi_string_empty(key_name)) {
ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
} else {
// if not, show key data
switch(ibutton_key_get_type(key)) {
case iButtonKeyDS1990:
ibutton_text_store_set(
ibutton,
"%02X %02X %02X %02X\n%02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
key_data[3],
key_data[4],
key_data[5],
key_data[6],
key_data[7]);
break;
case iButtonKeyCyfral:
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
break;
case iButtonKeyMetakom:
ibutton_text_store_set(
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
break;
}
widget_add_text_box_element(
widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
furi_string_set(tmp, "iButton\nwriting ");
if(ibutton->write_mode == iButtonWriteModeBlank) {
furi_string_cat(tmp, "Blank");
ibutton_worker_write_blank_start(worker, key);
} else if(ibutton->write_mode == iButtonWriteModeCopy) {
furi_string_cat(tmp, "Copy");
ibutton_worker_write_copy_start(worker, key);
}
widget_add_string_multiline_element(
widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nwriting");
widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
widget_add_text_box_element(
widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
ibutton_worker_write_start(worker, key);
furi_string_free(key_name);
widget, 88, 10, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
furi_string_free(tmp);
}
bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
@ -94,7 +78,9 @@ bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
void ibutton_scene_write_on_exit(void* context) {
iButton* ibutton = context;
ibutton_worker_stop(ibutton->key_worker);
ibutton->write_mode = iButtonWriteModeInvalid;
ibutton_worker_stop(ibutton->worker);
widget_reset(ibutton->widget);
ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);

View File

@ -3,6 +3,8 @@
#include <string.h>
#include <dolphin/dolphin.h>
#define INFRARED_TX_MIN_INTERVAL_MS 50U
static const NotificationSequence* infrared_notification_sequences[] = {
&sequence_success,
&sequence_set_only_green_255,
@ -299,10 +301,13 @@ bool infrared_rename_current_remote(Infrared* infrared, const char* name) {
void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
if(infrared->app_state.is_transmitting) {
FURI_LOG_D(INFRARED_LOG_TAG, "Transmitter is already active");
return;
} else {
infrared->app_state.is_transmitting = true;
}
const uint32_t time_elapsed = furi_get_tick() - infrared->app_state.last_transmit_time;
if(time_elapsed < INFRARED_TX_MIN_INTERVAL_MS) {
return;
}
if(infrared_signal_is_raw(signal)) {
@ -319,6 +324,8 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
infrared_worker_tx_set_get_signal_callback(
infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
infrared_worker_tx_start(infrared->worker);
infrared->app_state.is_transmitting = true;
}
void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
@ -328,26 +335,24 @@ void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
InfraredSignal* signal = infrared_remote_button_get_signal(button);
infrared_tx_start_signal(infrared, signal);
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
}
void infrared_tx_start_received(Infrared* infrared) {
infrared_tx_start_signal(infrared, infrared->received_signal);
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
}
void infrared_tx_stop(Infrared* infrared) {
if(!infrared->app_state.is_transmitting) {
FURI_LOG_D(INFRARED_LOG_TAG, "Transmitter is already stopped");
return;
} else {
infrared->app_state.is_transmitting = false;
}
infrared_worker_tx_stop(infrared->worker);
infrared_worker_tx_set_get_signal_callback(infrared->worker, NULL, NULL);
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
infrared->app_state.is_transmitting = false;
infrared->app_state.last_transmit_time = furi_get_tick();
}
void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) {

View File

@ -69,6 +69,7 @@ typedef struct {
InfraredEditTarget edit_target : 8;
InfraredEditMode edit_mode : 8;
int32_t current_button_index;
uint32_t last_transmit_time;
} InfraredAppState;
struct Infrared {

View File

@ -10,13 +10,13 @@ void infrared_scene_ask_back_on_enter(void* context) {
DialogEx* dialog_ex = infrared->dialog_ex;
if(infrared->app_state.is_learning_new_remote) {
dialog_ex_set_header(dialog_ex, "Exit to Infrared Menu?", 64, 0, AlignCenter, AlignTop);
dialog_ex_set_header(dialog_ex, "Exit to Infrared Menu?", 64, 11, AlignCenter, AlignTop);
} else {
dialog_ex_set_header(dialog_ex, "Exit to Remote Menu?", 64, 0, AlignCenter, AlignTop);
dialog_ex_set_header(dialog_ex, "Exit to Remote Menu?", 64, 11, AlignCenter, AlignTop);
}
dialog_ex_set_text(
dialog_ex, "All unsaved data\nwill be lost!", 64, 31, AlignCenter, AlignCenter);
dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop);
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
dialog_ex_set_left_button_text(dialog_ex, "Exit");
dialog_ex_set_center_button_text(dialog_ex, NULL);

View File

@ -9,9 +9,9 @@ void infrared_scene_ask_retry_on_enter(void* context) {
Infrared* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
dialog_ex_set_header(dialog_ex, "Return to Reading?", 64, 0, AlignCenter, AlignTop);
dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop);
dialog_ex_set_text(
dialog_ex, "All unsaved data\nwill be lost!", 64, 31, AlignCenter, AlignCenter);
dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop);
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
dialog_ex_set_left_button_text(dialog_ex, "Exit");
dialog_ex_set_center_button_text(dialog_ex, NULL);

View File

@ -74,4 +74,4 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
void infrared_scene_universal_on_exit(void* context) {
Infrared* infrared = context;
submenu_reset(infrared->submenu);
}
}

View File

@ -7,7 +7,7 @@ void lfrfid_scene_retry_confirm_on_enter(void* context) {
widget_add_button_element(widget, GuiButtonTypeLeft, "Exit", lfrfid_widget_callback, app);
widget_add_button_element(widget, GuiButtonTypeRight, "Stay", lfrfid_widget_callback, app);
widget_add_string_element(
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Return to reading?");
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?");
widget_add_string_element(
widget, 64, 29, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!");

View File

@ -25,7 +25,7 @@ void nfc_scene_mf_classic_menu_on_enter(void* context) {
if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) {
submenu_add_item(
submenu,
"Detect reader",
"Detect Reader",
SubmenuIndexDetectReader,
nfc_scene_mf_classic_menu_submenu_callback,
nfc);

View File

@ -46,6 +46,9 @@ bool nfc_scene_read_card_success_on_event(void* context, SceneManagerEvent event
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(nfc->scene_manager);
}
} else if(event.type == SceneManagerEventTypeBack) {
consumed =
scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart);
}
return consumed;
}

View File

@ -14,7 +14,7 @@ void nfc_scene_retry_confirm_on_enter(void* context) {
dialog_ex_set_right_button_text(dialog_ex, "Stay");
dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop);
dialog_ex_set_text(
dialog_ex, "All unsaved data will be\nlost!", 64, 25, AlignCenter, AlignTop);
dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop);
dialog_ex_set_context(dialog_ex, nfc);
dialog_ex_set_result_callback(dialog_ex, nfc_scene_retry_confirm_dialog_callback);

View File

@ -51,20 +51,20 @@ void nfc_scene_saved_menu_on_enter(void* context) {
if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) {
submenu_add_item(
submenu,
"Detect reader",
"Detect Reader",
SubmenuIndexDetectReader,
nfc_scene_saved_menu_submenu_callback,
nfc);
}
submenu_add_item(
submenu,
"Write To Initial Card",
"Write to Initial Card",
SubmenuIndexWrite,
nfc_scene_saved_menu_submenu_callback,
nfc);
submenu_add_item(
submenu,
"Update From Initial Card",
"Update from Initial Card",
SubmenuIndexUpdate,
nfc_scene_saved_menu_submenu_callback,
nfc);
@ -75,13 +75,13 @@ void nfc_scene_saved_menu_on_enter(void* context) {
!mf_ul_is_full_capture(&nfc->dev->dev_data.mf_ul_data)) {
submenu_add_item(
submenu,
"Unlock With Reader",
"Unlock with Reader",
SubmenuIndexMfUlUnlockByReader,
nfc_scene_saved_menu_submenu_callback,
nfc);
submenu_add_item(
submenu,
"Unlock With Password",
"Unlock with Password",
SubmenuIndexMfUlUnlockByPassword,
nfc_scene_saved_menu_submenu_callback,
nfc);

View File

@ -53,6 +53,7 @@ typedef enum {
SubGhzLoadKeyStateUnknown,
SubGhzLoadKeyStateOK,
SubGhzLoadKeyStateParseErr,
SubGhzLoadKeyStateProtocolDescriptionErr,
} SubGhzLoadKeyState;
/** SubGhzLock */

View File

@ -16,7 +16,7 @@ void subghz_scene_need_saving_on_enter(void* context) {
SubGhz* subghz = context;
widget_add_string_multiline_element(
subghz->widget, 64, 13, AlignCenter, AlignCenter, FontPrimary, "Exit to Sub-GHz menu?");
subghz->widget, 64, 13, AlignCenter, AlignCenter, FontPrimary, "Exit to Sub-GHz Menu?");
widget_add_string_multiline_element(
subghz->widget,
64,
@ -24,7 +24,7 @@ void subghz_scene_need_saving_on_enter(void* context) {
AlignCenter,
AlignCenter,
FontSecondary,
"All unsaved will be\nlost.");
"All unsaved data\nwill be lost!");
widget_add_button_element(
subghz->widget, GuiButtonTypeRight, "Stay", subghz_scene_need_saving_callback, subghz);

View File

@ -22,7 +22,9 @@ static bool subghz_scene_receiver_info_update_parser(void* context) {
subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
subghz->txrx->receiver,
subghz_history_get_protocol_name(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
if(subghz->txrx->decoder_result) {
//todo we are trying to deserialize without checking for errors, since it is assumed that we just received this chignal
subghz_protocol_decoder_base_deserialize(
subghz->txrx->decoder_result,
subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
@ -128,7 +130,6 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
subghz,
subghz_history_get_raw_data(
subghz->txrx->history, subghz->txrx->idx_menu_chosen))) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
subghz_tx_stop(subghz);
}

View File

@ -34,8 +34,9 @@ bool subghz_scene_set_type_submenu_gen_data_protocol(
do {
Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data);
stream_clean(fff_data_stream);
if(!subghz_protocol_decoder_base_serialize(
subghz->txrx->decoder_result, subghz->txrx->fff_data, subghz->txrx->preset)) {
if(subghz_protocol_decoder_base_serialize(
subghz->txrx->decoder_result, subghz->txrx->fff_data, subghz->txrx->preset) !=
SubGhzProtocolStatusOk) {
FURI_LOG_E(TAG, "Unable to serialize");
break;
}

View File

@ -9,9 +9,8 @@ void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) {
}
bool subghz_scene_transmitter_update_data_show(void* context) {
//ToDo Fix
SubGhz* subghz = context;
bool ret = false;
if(subghz->txrx->decoder_result) {
FuriString* key_str;
FuriString* frequency_str;
@ -22,30 +21,29 @@ bool subghz_scene_transmitter_update_data_show(void* context) {
modulation_str = furi_string_alloc();
uint8_t show_button = 0;
subghz_protocol_decoder_base_deserialize(
subghz->txrx->decoder_result, subghz->txrx->fff_data);
subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, key_str);
if(subghz_protocol_decoder_base_deserialize(
subghz->txrx->decoder_result, subghz->txrx->fff_data) == SubGhzProtocolStatusOk) {
subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, key_str);
if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) ==
SubGhzProtocolFlag_Send) {
show_button = 1;
if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) ==
SubGhzProtocolFlag_Send) {
show_button = 1;
}
subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
subghz_view_transmitter_add_data_to_show(
subghz->subghz_transmitter,
furi_string_get_cstr(key_str),
furi_string_get_cstr(frequency_str),
furi_string_get_cstr(modulation_str),
show_button);
ret = true;
}
subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
subghz_view_transmitter_add_data_to_show(
subghz->subghz_transmitter,
furi_string_get_cstr(key_str),
furi_string_get_cstr(frequency_str),
furi_string_get_cstr(modulation_str),
show_button);
furi_string_free(frequency_str);
furi_string_free(modulation_str);
furi_string_free(key_str);
return true;
}
return false;
return ret;
}
void subghz_scene_transmitter_on_enter(void* context) {

View File

@ -153,7 +153,6 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) {
FURI_LOG_E(TAG, "Missing Protocol");
break;
}
//ToDo FIX
if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
FURI_LOG_E(TAG, "Unable Repeat");
break;
@ -163,7 +162,8 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) {
subghz->txrx->environment, furi_string_get_cstr(temp_str));
if(subghz->txrx->transmitter) {
if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format)) {
if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format) ==
SubGhzProtocolStatusOk) {
if(strcmp(furi_string_get_cstr(subghz->txrx->preset->name), "") != 0) {
subghz_begin(
subghz,
@ -186,7 +186,12 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) {
//Start TX
furi_hal_subghz_start_async_tx(
subghz_transmitter_yield, subghz->txrx->transmitter);
} else {
subghz_dialog_message_show_only_rx(subghz);
}
} else {
dialog_message_show_storage_error(
subghz->dialogs, "Error in protocol\nparameters\ndescription");
}
}
if(!ret) {
@ -333,8 +338,10 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
subghz->txrx->receiver, furi_string_get_cstr(temp_str));
if(subghz->txrx->decoder_result) {
if(!subghz_protocol_decoder_base_deserialize(
subghz->txrx->decoder_result, subghz->txrx->fff_data)) {
SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize(
subghz->txrx->decoder_result, subghz->txrx->fff_data);
if(status != SubGhzProtocolStatusOk) {
load_key_state = SubGhzLoadKeyStateProtocolDescriptionErr;
break;
}
} else {
@ -355,6 +362,12 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile");
}
return false;
case SubGhzLoadKeyStateProtocolDescriptionErr:
if(show_dialog) {
dialog_message_show_storage_error(
subghz->dialogs, "Error in protocol\nparameters\ndescription");
}
return false;
case SubGhzLoadKeyStateOK:
return true;

View File

@ -56,7 +56,7 @@ static void clock_render_callback(Canvas* canvas, void* ctx) {
31,
AlignLeft,
AlignCenter,
(data->datetime.hour > 12) ? "PM" : "AM");
(data->datetime.hour > 11) ? "PM" : "AM");
}
canvas_set_font(canvas, FontSecondary);
@ -133,4 +133,4 @@ int32_t clock_app(void* p) {
free(clock);
return 0;
}
}

View File

@ -376,7 +376,17 @@ int32_t hid_ble_app(void* p) {
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_storage_path(app->bt, HID_BT_KEYS_STORAGE_PATH);
// Migrate data from old sd-card folder
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_migrate(
storage,
EXT_PATH("apps/Tools/" HID_BT_KEYS_STORAGE_NAME),
APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
bt_keys_storage_set_storage_path(app->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
furi_record_close(RECORD_STORAGE);
if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
FURI_LOG_E(TAG, "Failed to switch to HID profile");

View File

@ -23,7 +23,7 @@
#include "views/hid_mouse_jiggler.h"
#include "views/hid_tiktok.h"
#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys")
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
typedef enum {
HidTransportUsb,

View File

@ -10,7 +10,6 @@
#define TAG "MusicPlayer"
#define MUSIC_PLAYER_APP_PATH_FOLDER ANY_PATH("music_player")
#define MUSIC_PLAYER_APP_EXTENSION "*"
#define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4
@ -307,18 +306,24 @@ int32_t music_player_app(void* p) {
if(p && strlen(p)) {
furi_string_set(file_path, (const char*)p);
} else {
furi_string_set(file_path, MUSIC_PLAYER_APP_PATH_FOLDER);
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_migrate(
storage, EXT_PATH("music_player"), STORAGE_APP_DATA_PATH_PREFIX);
furi_record_close(RECORD_STORAGE);
furi_string_set(file_path, STORAGE_APP_DATA_PATH_PREFIX);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px);
browser_options.hide_ext = false;
browser_options.base_path = MUSIC_PLAYER_APP_PATH_FOLDER;
browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
furi_record_close(RECORD_DIALOGS);
if(!res) {
FURI_LOG_E(TAG, "No file selected");
break;

View File

@ -11,6 +11,10 @@ void nfc_magic_scene_file_select_on_enter(void* context) {
// Process file_select return
nfc_device_set_loading_callback(nfc_magic->nfc_dev, nfc_magic_show_loading_popup, nfc_magic);
if(!furi_string_size(nfc_magic->nfc_dev->load_path)) {
furi_string_set_str(nfc_magic->nfc_dev->load_path, NFC_APP_FOLDER);
}
if(nfc_file_select(nfc_magic->nfc_dev)) {
if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic->nfc_dev)) {
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm);

View File

@ -3,8 +3,8 @@
#include <lib/toolbox/args.h>
#include <lib/flipper_format/flipper_format.h>
#define ICLASS_ELITE_DICT_FLIPPER_PATH EXT_PATH("picopass/assets/iclass_elite_dict.txt")
#define ICLASS_ELITE_DICT_USER_PATH EXT_PATH("picopass/assets/iclass_elite_dict_user.txt")
#define ICLASS_ELITE_DICT_FLIPPER_NAME APP_DATA_PATH("assets/iclass_elite_dict.txt")
#define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt")
#define TAG "IclassEliteDict"
@ -21,10 +21,10 @@ bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type) {
bool dict_present = false;
if(dict_type == IclassEliteDictTypeFlipper) {
dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_PATH, NULL) ==
FSE_OK;
dict_present =
(storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_NAME, NULL) == FSE_OK);
} else if(dict_type == IclassEliteDictTypeUser) {
dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_USER_PATH, NULL) == FSE_OK;
dict_present = (storage_common_stat(storage, ICLASS_ELITE_DICT_USER_NAME, NULL) == FSE_OK);
}
furi_record_close(RECORD_STORAGE);
@ -36,27 +36,26 @@ IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) {
IclassEliteDict* dict = malloc(sizeof(IclassEliteDict));
Storage* storage = furi_record_open(RECORD_STORAGE);
dict->stream = buffered_file_stream_alloc(storage);
furi_record_close(RECORD_STORAGE);
FuriString* next_line = furi_string_alloc();
bool dict_loaded = false;
do {
if(dict_type == IclassEliteDictTypeFlipper) {
if(!buffered_file_stream_open(
dict->stream, ICLASS_ELITE_DICT_FLIPPER_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
dict->stream, ICLASS_ELITE_DICT_FLIPPER_NAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
buffered_file_stream_close(dict->stream);
break;
}
} else if(dict_type == IclassEliteDictTypeUser) {
if(!buffered_file_stream_open(
dict->stream, ICLASS_ELITE_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
dict->stream, ICLASS_ELITE_DICT_USER_NAME, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
buffered_file_stream_close(dict->stream);
break;
}
}
// Read total amount of keys
while(true) {
while(true) { //-V547
if(!stream_read_line(dict->stream, next_line)) break;
if(furi_string_get_char(next_line, 0) == '#') continue;
if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue;
@ -69,12 +68,13 @@ IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) {
FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys);
} while(false);
if(!dict_loaded) {
if(!dict_loaded) { //-V547
buffered_file_stream_close(dict->stream);
free(dict);
dict = NULL;
}
furi_record_close(RECORD_STORAGE);
furi_string_free(next_line);
return dict;

View File

@ -171,6 +171,12 @@ void picopass_show_loading_popup(void* context, bool show) {
}
}
static void picopass_migrate_from_old_folder() {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_migrate(storage, "/ext/picopass", STORAGE_APP_DATA_PATH_PREFIX);
furi_record_close(RECORD_STORAGE);
}
bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) {
bool result = size > 0;
while(size > 0) {
@ -183,6 +189,8 @@ bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size)
int32_t picopass_app(void* p) {
UNUSED(p);
picopass_migrate_from_old_folder();
Picopass* picopass = picopass_alloc();
scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart);

View File

@ -48,13 +48,9 @@ static bool picopass_device_save_file(
if(use_load_path && !furi_string_empty(dev->load_path)) {
// Get directory name
path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str);
// Create picopass directory if necessary
if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(temp_str))) break;
// Make path to file to save
furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension);
} else {
// Create picopass directory if necessary
if(!storage_simply_mkdir(dev->storage, PICOPASS_APP_FOLDER)) break;
// First remove picopass device file if it was saved
furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
}
@ -126,10 +122,11 @@ static bool picopass_device_save_file(
bool picopass_device_save(PicopassDevice* dev, const char* dev_name) {
if(dev->format == PicopassDeviceSaveFormatHF) {
return picopass_device_save_file(
dev, dev_name, PICOPASS_APP_FOLDER, PICOPASS_APP_EXTENSION, true);
dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true);
} else if(dev->format == PicopassDeviceSaveFormatLF) {
return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true);
}
return false;
}
@ -170,6 +167,8 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo
}
size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0];
// Fix for unpersonalized cards that have app_limit set to 0xFF
if(app_limit > PICOPASS_MAX_APP_LIMIT) app_limit = PICOPASS_MAX_APP_LIMIT;
for(size_t i = 6; i < app_limit; i++) {
furi_string_printf(temp_str, "Block %d", i);
if(!flipper_format_read_hex(
@ -225,13 +224,12 @@ void picopass_device_free(PicopassDevice* picopass_dev) {
bool picopass_file_select(PicopassDevice* dev) {
furi_assert(dev);
// Input events and views are managed by file_browser
FuriString* picopass_app_folder;
picopass_app_folder = furi_string_alloc_set(PICOPASS_APP_FOLDER);
picopass_app_folder = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px);
browser_options.base_path = PICOPASS_APP_FOLDER;
browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
bool res = dialog_file_browser_show(
dev->dialogs, dev->load_path, picopass_app_folder, &browser_options);
@ -274,7 +272,7 @@ bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) {
furi_string_set(file_path, dev->load_path);
} else {
furi_string_printf(
file_path, "%s/%s%s", PICOPASS_APP_FOLDER, dev->dev_name, PICOPASS_APP_EXTENSION);
file_path, APP_DATA_PATH("%s%s"), dev->dev_name, PICOPASS_APP_EXTENSION);
}
if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break;
deleted = true;

View File

@ -24,7 +24,6 @@
#define PICOPASS_AIA_BLOCK_INDEX 5
#define PICOPASS_PACS_CFG_BLOCK_INDEX 6
#define PICOPASS_APP_FOLDER ANY_PATH("picopass")
#define PICOPASS_APP_EXTENSION ".picopass"
#define PICOPASS_APP_SHADOW_EXTENSION ".pas"
@ -81,7 +80,6 @@ typedef struct {
PicopassDeviceSaveFormat format;
PicopassLoadingCallback loading_cb;
void* loading_cb_ctx;
} PicopassDevice;
PicopassDevice* picopass_device_alloc();

View File

@ -0,0 +1,8 @@
#include "picopass_keys.h"
const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00};
const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87};
const uint8_t picopass_xice_key[] = {0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88};
const uint8_t picopass_xicl_key[] = {0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88};
const uint8_t picopass_xics_key[] = {0x66, 0x66, 0x20, 0x20, 0x66, 0x66, 0x88, 0x88};

View File

@ -0,0 +1,10 @@
#pragma once
#include "picopass_device.h"
extern const uint8_t picopass_iclass_key[PICOPASS_BLOCK_LEN];
extern const uint8_t picopass_factory_credit_key[PICOPASS_BLOCK_LEN];
extern const uint8_t picopass_factory_debit_key[PICOPASS_BLOCK_LEN];
extern const uint8_t picopass_xice_key[PICOPASS_BLOCK_LEN];
extern const uint8_t picopass_xicl_key[PICOPASS_BLOCK_LEN];
extern const uint8_t picopass_xics_key[PICOPASS_BLOCK_LEN];

View File

@ -4,10 +4,6 @@
#define TAG "PicopassWorker"
const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00};
const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87};
static void picopass_worker_enable_field() {
furi_hal_nfc_ll_txrx_on();
furi_hal_nfc_exit_sleep();
@ -176,55 +172,12 @@ ReturnCode picopass_read_preauth(PicopassBlock* AA1) {
return ERR_NONE;
}
static ReturnCode picopass_auth_standard(uint8_t* csn, uint8_t* div_key) {
rfalPicoPassReadCheckRes rcRes;
rfalPicoPassCheckRes chkRes;
ReturnCode err;
uint8_t mac[4] = {0};
uint8_t ccnr[12] = {0};
err = rfalPicoPassPollerReadCheck(&rcRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
return err;
}
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
loclass_diversifyKey(csn, picopass_iclass_key, div_key);
loclass_opt_doReaderMAC(ccnr, div_key, mac);
return rfalPicoPassPollerCheck(mac, &chkRes);
}
static ReturnCode picopass_auth_factory(uint8_t* csn, uint8_t* div_key) {
rfalPicoPassReadCheckRes rcRes;
rfalPicoPassCheckRes chkRes;
ReturnCode err;
uint8_t mac[4] = {0};
uint8_t ccnr[12] = {0};
err = rfalPicoPassPollerReadCheck(&rcRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
return err;
}
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
loclass_diversifyKey(csn, picopass_factory_debit_key, div_key);
loclass_opt_doReaderMAC(ccnr, div_key, mac);
return rfalPicoPassPollerCheck(mac, &chkRes);
}
static ReturnCode picopass_auth_dict(
uint8_t* csn,
PicopassPacs* pacs,
uint8_t* div_key,
IclassEliteDictType dict_type) {
IclassEliteDictType dict_type,
bool elite) {
rfalPicoPassReadCheckRes rcRes;
rfalPicoPassCheckRes chkRes;
@ -269,7 +222,7 @@ static ReturnCode picopass_auth_dict(
}
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
loclass_iclass_calc_div_key(csn, key, div_key, true);
loclass_iclass_calc_div_key(csn, key, div_key, elite);
loclass_opt_doReaderMAC(ccnr, div_key, mac);
err = rfalPicoPassPollerCheck(mac, &chkRes);
@ -287,38 +240,35 @@ static ReturnCode picopass_auth_dict(
ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) {
ReturnCode err;
FURI_LOG_I(TAG, "Trying standard legacy key");
err = picopass_auth_standard(
AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data);
if(err == ERR_NONE) {
memcpy(pacs->key, picopass_iclass_key, PICOPASS_BLOCK_LEN);
return ERR_NONE;
}
FURI_LOG_I(TAG, "Trying factory default key");
err = picopass_auth_factory(
AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data);
if(err == ERR_NONE) {
memcpy(pacs->key, picopass_factory_debit_key, PICOPASS_BLOCK_LEN);
return ERR_NONE;
}
FURI_LOG_I(TAG, "Starting user dictionary attack");
FURI_LOG_I(TAG, "Starting system dictionary attack [Standard KDF]");
err = picopass_auth_dict(
AA1[PICOPASS_CSN_BLOCK_INDEX].data,
pacs,
AA1[PICOPASS_KD_BLOCK_INDEX].data,
IclassEliteDictTypeUser);
IclassEliteDictTypeFlipper,
false);
if(err == ERR_NONE) {
return ERR_NONE;
}
FURI_LOG_I(TAG, "Starting system dictionary attack");
FURI_LOG_I(TAG, "Starting user dictionary attack [Elite KDF]");
err = picopass_auth_dict(
AA1[PICOPASS_CSN_BLOCK_INDEX].data,
pacs,
AA1[PICOPASS_KD_BLOCK_INDEX].data,
IclassEliteDictTypeFlipper);
IclassEliteDictTypeUser,
true);
if(err == ERR_NONE) {
return ERR_NONE;
}
FURI_LOG_I(TAG, "Starting system dictionary attack [Elite KDF]");
err = picopass_auth_dict(
AA1[PICOPASS_CSN_BLOCK_INDEX].data,
pacs,
AA1[PICOPASS_KD_BLOCK_INDEX].data,
IclassEliteDictTypeFlipper,
true);
if(err == ERR_NONE) {
return ERR_NONE;
}
@ -396,7 +346,7 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) {
}
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
loclass_diversifyKey(selRes.CSN, picopass_iclass_key, div_key);
loclass_iclass_calc_div_key(selRes.CSN, (uint8_t*)picopass_iclass_key, div_key, false);
loclass_opt_doReaderMAC(ccnr, div_key, mac);
err = rfalPicoPassPollerCheck(mac, &chkRes);
@ -438,7 +388,7 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) {
return ERR_NONE;
}
ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* newBlock) {
ReturnCode picopass_write_block(PicopassBlock* AA1, uint8_t blockNo, uint8_t* newBlock) {
rfalPicoPassIdentifyRes idRes;
rfalPicoPassSelectRes selRes;
rfalPicoPassReadCheckRes rcRes;
@ -446,7 +396,6 @@ ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* ne
ReturnCode err;
uint8_t div_key[8] = {0};
uint8_t mac[4] = {0};
uint8_t ccnr[12] = {0};
@ -469,9 +418,12 @@ ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* ne
}
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
loclass_diversifyKey(selRes.CSN, pacs->key, div_key);
loclass_opt_doReaderMAC(ccnr, div_key, mac);
if(memcmp(selRes.CSN, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN) != 0) {
FURI_LOG_E(TAG, "Wrong CSN for write");
return ERR_REQUEST;
}
loclass_opt_doReaderMAC(ccnr, AA1[PICOPASS_KD_BLOCK_INDEX].data, mac);
err = rfalPicoPassPollerCheck(mac, &chkRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
@ -489,7 +441,7 @@ ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* ne
newBlock[5],
newBlock[6],
newBlock[7]};
loclass_doMAC_N(data, sizeof(data), div_key, mac);
loclass_doMAC_N(data, sizeof(data), AA1[PICOPASS_KD_BLOCK_INDEX].data, mac);
FURI_LOG_D(
TAG,
"loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
@ -524,8 +476,8 @@ int32_t picopass_worker_task(void* context) {
picopass_worker_detect(picopass_worker);
} else if(picopass_worker->state == PicopassWorkerStateWrite) {
picopass_worker_write(picopass_worker);
} else if(picopass_worker->state == PicopassWorkerStateWriteStandardKey) {
picopass_worker_write_standard_key(picopass_worker);
} else if(picopass_worker->state == PicopassWorkerStateWriteKey) {
picopass_worker_write_key(picopass_worker);
}
picopass_worker_disable_field(ERR_NONE);
@ -633,7 +585,7 @@ void picopass_worker_write(PicopassWorker* picopass_worker) {
}
}
void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) {
void picopass_worker_write_key(PicopassWorker* picopass_worker) {
PicopassDeviceData* dev_data = picopass_worker->dev_data;
PicopassBlock* AA1 = dev_data->AA1;
PicopassPacs* pacs = &dev_data->pacs;
@ -646,7 +598,7 @@ void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) {
uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data;
uint8_t newKey[PICOPASS_BLOCK_LEN] = {0};
loclass_diversifyKey(csn, picopass_iclass_key, newKey);
loclass_iclass_calc_div_key(csn, pacs->key, newKey, false);
if((fuses & 0x80) == 0x80) {
FURI_LOG_D(TAG, "Plain write for personalized mode key change");
@ -658,9 +610,9 @@ void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) {
}
}
while(picopass_worker->state == PicopassWorkerStateWriteStandardKey) {
while(picopass_worker->state == PicopassWorkerStateWriteKey) {
if(picopass_detect_card(1000) == ERR_NONE) {
err = picopass_write_block(pacs, PICOPASS_KD_BLOCK_INDEX, newKey);
err = picopass_write_block(AA1, PICOPASS_KD_BLOCK_INDEX, newKey);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "picopass_write_block error %d", err);
nextState = PicopassWorkerEventFail;

View File

@ -1,6 +1,7 @@
#pragma once
#include "picopass_device.h"
#include "picopass_keys.h"
typedef struct PicopassWorker PicopassWorker;
@ -12,7 +13,7 @@ typedef enum {
// Main worker states
PicopassWorkerStateDetect,
PicopassWorkerStateWrite,
PicopassWorkerStateWriteStandardKey,
PicopassWorkerStateWriteKey,
// Transition
PicopassWorkerStateStop,
} PicopassWorkerState;

View File

@ -31,4 +31,4 @@ int32_t picopass_worker_task(void* context);
void picopass_worker_detect(PicopassWorker* picopass_worker);
void picopass_worker_write(PicopassWorker* picopass_worker);
void picopass_worker_write_standard_key(PicopassWorker* picopass_worker);
void picopass_worker_write_key(PicopassWorker* picopass_worker);

View File

@ -3,6 +3,7 @@
enum SubmenuIndex {
SubmenuIndexSave,
SubmenuIndexSaveAsLF,
SubmenuIndexChangeKey,
};
void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) {
@ -25,6 +26,13 @@ void picopass_scene_card_menu_on_enter(void* context) {
picopass_scene_card_menu_submenu_callback,
picopass);
}
submenu_add_item(
submenu,
"Change Key",
SubmenuIndexChangeKey,
picopass_scene_card_menu_submenu_callback,
picopass);
submenu_set_selected_item(
picopass->submenu,
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneCardMenu));
@ -49,6 +57,11 @@ bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) {
picopass->dev->format = PicopassDeviceSaveFormatLF;
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
consumed = true;
} else if(event.event == SubmenuIndexChangeKey) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexChangeKey);
scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyMenu);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
consumed = scene_manager_search_and_switch_to_previous_scene(

View File

@ -13,3 +13,4 @@ ADD_SCENE(picopass, write_card, WriteCard)
ADD_SCENE(picopass, write_card_success, WriteCardSuccess)
ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess)
ADD_SCENE(picopass, write_key, WriteKey)
ADD_SCENE(picopass, key_menu, KeyMenu)

View File

@ -0,0 +1,96 @@
#include "../picopass_i.h"
#include "../picopass_keys.h"
enum SubmenuIndex {
SubmenuIndexWriteStandard,
SubmenuIndexWriteiCE,
SubmenuIndexWriteiCL,
SubmenuIndexWriteiCS,
SubmenuIndexWriteCustom, //TODO: user input of key
};
void picopass_scene_key_menu_submenu_callback(void* context, uint32_t index) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, index);
}
void picopass_scene_key_menu_on_enter(void* context) {
Picopass* picopass = context;
Submenu* submenu = picopass->submenu;
submenu_add_item(
submenu,
"Write Standard",
SubmenuIndexWriteStandard,
picopass_scene_key_menu_submenu_callback,
picopass);
submenu_add_item(
submenu,
"Write iCE",
SubmenuIndexWriteiCE,
picopass_scene_key_menu_submenu_callback,
picopass);
submenu_add_item(
submenu,
"Write iCL",
SubmenuIndexWriteiCL,
picopass_scene_key_menu_submenu_callback,
picopass);
submenu_add_item(
submenu,
"Write iCS",
SubmenuIndexWriteiCS,
picopass_scene_key_menu_submenu_callback,
picopass);
submenu_set_selected_item(
picopass->submenu,
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneKeyMenu));
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu);
}
bool picopass_scene_key_menu_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexWriteStandard) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteStandard);
memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN);
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
consumed = true;
} else if(event.event == SubmenuIndexWriteiCE) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE);
memcpy(picopass->dev->dev_data.pacs.key, picopass_xice_key, PICOPASS_BLOCK_LEN);
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
consumed = true;
} else if(event.event == SubmenuIndexWriteiCL) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE);
memcpy(picopass->dev->dev_data.pacs.key, picopass_xicl_key, PICOPASS_BLOCK_LEN);
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
consumed = true;
} else if(event.event == SubmenuIndexWriteiCS) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE);
memcpy(picopass->dev->dev_data.pacs.key, picopass_xics_key, PICOPASS_BLOCK_LEN);
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
consumed = scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneStart);
}
return consumed;
}
void picopass_scene_key_menu_on_exit(void* context) {
Picopass* picopass = context;
submenu_reset(picopass->submenu);
}

View File

@ -1,7 +1,6 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
const uint8_t picopass_factory_key_check[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87};
#include "../picopass_keys.h"
void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) {
UNUSED(event);
@ -38,7 +37,7 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
if(event.event == PicopassCustomEventWorkerExit) {
if(memcmp(
picopass->dev->dev_data.pacs.key,
picopass_factory_key_check,
picopass_factory_debit_key,
PICOPASS_BLOCK_LEN) == 0) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess);
} else {

View File

@ -1,5 +1,6 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
#include "../picopass_keys.h"
void picopass_scene_read_factory_success_widget_callback(
GuiButtonType result,
@ -63,6 +64,7 @@ bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEve
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(picopass->scene_manager);
} else if(event.event == GuiButtonTypeCenter) {
memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN);
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
consumed = true;
}

View File

@ -31,12 +31,10 @@ void picopass_scene_save_name_on_enter(void* context) {
dev_name_empty);
FuriString* folder_path;
folder_path = furi_string_alloc();
folder_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
if(furi_string_end_with(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) {
path_extract_dirname(furi_string_get_cstr(picopass->dev->load_path), folder_path);
} else {
furi_string_set(folder_path, PICOPASS_APP_FOLDER);
}
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(

View File

@ -20,7 +20,7 @@ void picopass_scene_write_key_on_enter(void* context) {
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
picopass_worker_start(
picopass->worker,
PicopassWorkerStateWriteStandardKey,
PicopassWorkerStateWriteKey,
&picopass->dev->dev_data,
picopass_write_key_worker_callback,
picopass);

View File

@ -50,6 +50,7 @@ typedef struct {
Direction nextMovement; // if backward of currentMovement, ignore
Point fruit;
GameState state;
FuriMutex* mutex;
} SnakeState;
typedef enum {
@ -92,12 +93,10 @@ const NotificationSequence sequence_eat = {
};
static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
const SnakeState* snake_state = acquire_mutex((ValueMutex*)ctx, 25);
if(snake_state == NULL) {
return;
}
furi_assert(ctx);
const SnakeState* snake_state = ctx;
// Before the function is called, the state is set with the canvas_reset(canvas)
furi_mutex_acquire(snake_state->mutex, FuriWaitForever);
// Frame
canvas_draw_frame(canvas, 0, 0, 128, 64);
@ -134,7 +133,7 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
}
release_mutex((ValueMutex*)ctx, snake_state);
furi_mutex_release(snake_state->mutex);
}
static void snake_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
@ -324,15 +323,16 @@ int32_t snake_game_app(void* p) {
SnakeState* snake_state = malloc(sizeof(SnakeState));
snake_game_init_game(snake_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, snake_state, sizeof(SnakeState))) {
snake_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!snake_state->mutex) {
FURI_LOG_E("SnakeGame", "cannot create mutex\r\n");
free(snake_state);
return 255;
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, snake_game_render_callback, &state_mutex);
view_port_draw_callback_set(view_port, snake_game_render_callback, snake_state);
view_port_input_callback_set(view_port, snake_game_input_callback, event_queue);
FuriTimer* timer =
@ -352,7 +352,7 @@ int32_t snake_game_app(void* p) {
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
SnakeState* snake_state = (SnakeState*)acquire_mutex_block(&state_mutex);
furi_mutex_acquire(snake_state->mutex, FuriWaitForever);
if(event_status == FuriStatusOk) {
// press events
@ -391,7 +391,7 @@ int32_t snake_game_app(void* p) {
}
view_port_update(view_port);
release_mutex(&state_mutex, snake_state);
furi_mutex_release(snake_state->mutex);
}
// Return backlight to normal state
@ -404,7 +404,7 @@ int32_t snake_game_app(void* p) {
furi_record_close(RECORD_NOTIFICATION);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
furi_mutex_free(snake_state->mutex);
free(snake_state);
return 0;

View File

@ -60,7 +60,7 @@ bool spi_mem_scene_start_on_event(void* context, SceneManagerEvent event) {
scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect);
success = true;
} else if(event.event == SPIMemSceneStartSubmenuIndexSaved) {
furi_string_set(app->file_path, SPI_MEM_FILE_FOLDER);
furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX);
scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectFile);
success = true;
} else if(event.event == SPIMemSceneStartSubmenuIndexErase) {

View File

@ -16,9 +16,9 @@ static bool spi_mem_back_event_callback(void* context) {
}
SPIMemApp* spi_mem_alloc(void) {
SPIMemApp* instance = malloc(sizeof(SPIMemApp));
SPIMemApp* instance = malloc(sizeof(SPIMemApp)); //-V799
instance->file_path = furi_string_alloc();
instance->file_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
instance->gui = furi_record_open(RECORD_GUI);
instance->notifications = furi_record_open(RECORD_NOTIFICATION);
instance->view_dispatcher = view_dispatcher_alloc();
@ -37,7 +37,8 @@ SPIMemApp* spi_mem_alloc(void) {
instance->text_input = text_input_alloc();
instance->mode = SPIMemModeUnknown;
furi_string_set(instance->file_path, SPI_MEM_FILE_FOLDER);
// Migrate data from old sd-card folder
storage_common_migrate(instance->storage, EXT_PATH("spimem"), STORAGE_APP_DATA_PATH_PREFIX);
view_dispatcher_enable_queue(instance->view_dispatcher);
view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
@ -70,7 +71,7 @@ SPIMemApp* spi_mem_alloc(void) {
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external);
scene_manager_next_scene(instance->scene_manager, SPIMemSceneStart);
return instance;
}
} //-V773
void spi_mem_free(SPIMemApp* instance) {
view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewSubmenu);
@ -105,7 +106,6 @@ void spi_mem_free(SPIMemApp* instance) {
int32_t spi_mem_app(void* p) {
UNUSED(p);
SPIMemApp* instance = spi_mem_alloc();
spi_mem_file_create_folder(instance);
view_dispatcher_run(instance->view_dispatcher);
spi_mem_free(instance);
return 0;

View File

@ -24,7 +24,6 @@
#define TAG "SPIMem"
#define SPI_MEM_FILE_EXTENSION ".bin"
#define SPI_MEM_FILE_FOLDER EXT_PATH("spimem")
#define SPI_MEM_FILE_NAME_SIZE 100
#define SPI_MEM_TEXT_BUFFER_SIZE 128

View File

@ -1,11 +1,5 @@
#include "spi_mem_app_i.h"
void spi_mem_file_create_folder(SPIMemApp* app) {
if(!storage_simply_mkdir(app->storage, SPI_MEM_FILE_FOLDER)) {
dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder");
}
}
bool spi_mem_file_delete(SPIMemApp* app) {
return (storage_simply_remove(app->storage, furi_string_get_cstr(app->file_path)));
}
@ -13,7 +7,7 @@ bool spi_mem_file_delete(SPIMemApp* app) {
bool spi_mem_file_select(SPIMemApp* app) {
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, SPI_MEM_FILE_EXTENSION, &I_Dip8_10px);
browser_options.base_path = SPI_MEM_FILE_FOLDER;
browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
bool success =
dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options);
return success;

View File

@ -1,7 +1,6 @@
#pragma once
#include "spi_mem_app.h"
void spi_mem_file_create_folder(SPIMemApp* app);
bool spi_mem_file_select(SPIMemApp* app);
bool spi_mem_file_create(SPIMemApp* app, const char* file_name);
bool spi_mem_file_delete(SPIMemApp* app);

View File

@ -3,7 +3,7 @@
#include <furi.h>
#include <furi_hal.h>
#define WS_VERSION_APP "0.7"
#define WS_VERSION_APP "0.8"
#define WS_DEVELOPED "SkorP"
#define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"

View File

@ -258,7 +258,7 @@ uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context) {
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
bool ws_protocol_decoder_acurite_592txr_serialize(
SubGhzProtocolStatus ws_protocol_decoder_acurite_592txr_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
@ -267,22 +267,14 @@ bool ws_protocol_decoder_acurite_592txr_serialize(
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
}
bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) {
SubGhzProtocolStatus
ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
WSProtocolDecoderAcurite_592TXR* instance = context;
bool ret = false;
do {
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
break;
}
if(instance->generic.data_count_bit !=
ws_protocol_acurite_592txr_const.min_count_bit_for_found) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
break;
}
ret = true;
} while(false);
return ret;
return ws_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
ws_protocol_acurite_592txr_const.min_count_bit_for_found);
}
void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output) {

View File

@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context);
* @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return true On success
* @return status
*/
bool ws_protocol_decoder_acurite_592txr_serialize(
SubGhzProtocolStatus ws_protocol_decoder_acurite_592txr_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
@ -67,9 +67,10 @@ bool ws_protocol_decoder_acurite_592txr_serialize(
* Deserialize data WSProtocolDecoderAcurite_592TXR.
* @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return true On success
* @return status
*/
bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format);
SubGhzProtocolStatus
ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.

Some files were not shown because too many files have changed in this diff Show More