diff --git a/.gitignore b/.gitignore
index f816372fa..067264322 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+*~
*.swp
*.swo
*.gdb_history
diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c
index 582be7902..f0b45c598 100644
--- a/applications/debug/unit_tests/storage/storage_test.c
+++ b/applications/debug/unit_tests/storage/storage_test.c
@@ -362,8 +362,8 @@ 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, "/app/test");
- int32_t ret = storage_file_create(storage, "/app/test", "test");
+ storage_common_remove(storage, "/data/test");
+ int32_t ret = storage_file_create(storage, "/data/test", "test");
furi_record_close(RECORD_STORAGE);
return ret;
}
@@ -401,7 +401,7 @@ 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, "/app"));
+ mu_check(storage_dir_open(file, "/data"));
mu_check(storage_dir_close(file));
storage_file_free(file);
diff --git a/applications/examples/example_apps_assets/README.md b/applications/examples/example_apps_assets/README.md
new file mode 100644
index 000000000..a24183e88
--- /dev/null
+++ b/applications/examples/example_apps_assets/README.md
@@ -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 provided 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 generated 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.
\ No newline at end of file
diff --git a/applications/examples/example_apps_assets/application.fam b/applications/examples/example_apps_assets/application.fam
new file mode 100644
index 000000000..4f324277d
--- /dev/null
+++ b/applications/examples/example_apps_assets/application.fam
@@ -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",
+)
diff --git a/applications/examples/example_apps_assets/example_apps_assets.c b/applications/examples/example_apps_assets/example_apps_assets.c
new file mode 100644
index 000000000..f2d0272f0
--- /dev/null
+++ b/applications/examples/example_apps_assets/example_apps_assets.c
@@ -0,0 +1,48 @@
+#include
+#include
+#include
+#include
+
+// 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;
+}
diff --git a/applications/examples/example_apps_assets/files/poems/a jelly-fish.txt b/applications/examples/example_apps_assets/files/poems/a jelly-fish.txt
new file mode 100644
index 000000000..46a5a4dff
--- /dev/null
+++ b/applications/examples/example_apps_assets/files/poems/a jelly-fish.txt
@@ -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"
\ No newline at end of file
diff --git a/applications/examples/example_apps_assets/files/poems/my shadow.txt b/applications/examples/example_apps_assets/files/poems/my shadow.txt
new file mode 100644
index 000000000..e113e7df5
--- /dev/null
+++ b/applications/examples/example_apps_assets/files/poems/my shadow.txt
@@ -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 there’s none of him at all.
+
+He hasn’t 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, he’s a coward you can see;
+I’d 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"
\ No newline at end of file
diff --git a/applications/examples/example_apps_assets/files/poems/theme in yellow.txt b/applications/examples/example_apps_assets/files/poems/theme in yellow.txt
new file mode 100644
index 000000000..f392287bd
--- /dev/null
+++ b/applications/examples/example_apps_assets/files/poems/theme in yellow.txt
@@ -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"
\ No newline at end of file
diff --git a/applications/examples/example_apps_assets/files/test_asset.txt b/applications/examples/example_apps_assets/files/test_asset.txt
new file mode 100644
index 000000000..1adcb55ee
--- /dev/null
+++ b/applications/examples/example_apps_assets/files/test_asset.txt
@@ -0,0 +1 @@
+## This is test file content
\ No newline at end of file
diff --git a/applications/examples/example_apps_data/README.md b/applications/examples/example_apps_data/README.md
index fd8666077..c70ac055a 100644
--- a/applications/examples/example_apps_data/README.md
+++ b/applications/examples/example_apps_data/README.md
@@ -9,10 +9,16 @@ The **Apps Data** folder is a folder used to store data for external apps that a
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 `/app` alias instead.
+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 `/app` 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: `/app/config.txt`. But this way is not recommended, because even the `/app` alias can change in the future.
+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")`.
\ No newline at end of file
+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 provided 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 generated 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.
\ No newline at end of file
diff --git a/applications/main/bad_kb/bad_kb_script.c b/applications/main/bad_kb/bad_kb_script.c
index f6b65247d..17c833381 100644
--- a/applications/main/bad_kb/bad_kb_script.c
+++ b/applications/main/bad_kb/bad_kb_script.c
@@ -7,6 +7,7 @@
#include
#include
#include "bad_kb_script.h"
+#include "mnemonic.h"
#include
#include
@@ -17,7 +18,6 @@
#define TAG "BadKB"
#define WORKER_TAG TAG "Worker"
-#define FILE_BUFFER_LEN 16
#define SCRIPT_STATE_ERROR (-1)
#define SCRIPT_STATE_END (-2)
@@ -54,27 +54,6 @@ const uint8_t bt_hid_delays[LevelRssiNum] = {
14, // LevelRssi39_0
};
-struct BadKbScript {
- FuriHalUsbHidConfig hid_cfg;
- BadKbState st;
- FuriString* file_path;
- FuriString* keyboard_layout;
- 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;
-
- Bt* bt;
-};
-
typedef struct {
char* name;
uint16_t keycode;
@@ -143,32 +122,20 @@ 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 char ducky_cmd_lang[] = {"DUCKY_LANG"};
-
-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,
-};
-
uint8_t bt_timeout = 0;
static LevelRssiRange bt_remote_rssi_range(Bt* bt) {
@@ -198,16 +165,7 @@ static inline void update_bt_timeout(Bt* bt) {
}
}
-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;
@@ -215,112 +173,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(BadKbScript* bad_kb) {
- if(bad_kb->bt) {
- if((furi_hal_bt_hid_get_led_state() & HID_KB_LED_NUM) == 0) { // FIXME
- furi_hal_bt_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
- furi_delay_ms(bt_timeout);
- furi_hal_bt_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
- }
- } else {
- 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(BadKbScript* bad_kb, const char num) {
- if((num < '0') || (num > '9')) return false;
-
- uint16_t key = numpad_keys[num - '0'];
- if(bad_kb->bt) {
- furi_hal_bt_hid_kb_press(key);
- furi_delay_ms(bt_timeout);
- furi_hal_bt_hid_kb_release(key);
- } else {
- furi_hal_hid_kb_press(key);
- furi_hal_hid_kb_release(key);
- }
-
- return true;
-}
-
-static bool ducky_altchar(BadKbScript* bad_kb, const char* charcode) {
- uint8_t i = 0;
- bool state = false;
-
- FURI_LOG_I(WORKER_TAG, "char %s", charcode);
-
- if(bad_kb->bt) {
- furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT);
- } else {
- furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
- }
-
- while(!ducky_is_line_end(charcode[i])) {
- state = ducky_numpad_press(bad_kb, charcode[i]);
- if(state == false) break;
- i++;
- }
-
- if(bad_kb->bt) {
- furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT);
- } else {
- furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
- }
- return state;
-}
-
-static bool ducky_altstring(BadKbScript* bad_kb, 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(bad_kb, temp_str);
- if(state == false) break;
- i++;
- }
- return state;
-}
-
-static bool ducky_string(BadKbScript* bad_kb, const char* param) {
- uint32_t i = 0;
-
- while(param[i] != '\0') {
- uint16_t keycode = BADKB_ASCII_TO_KEY(bad_kb, param[i]);
- if(keycode != HID_KEYBOARD_NONE) {
- if(bad_kb->bt) {
- furi_hal_bt_hid_kb_press(keycode);
- furi_delay_ms(bt_timeout);
- furi_hal_bt_hid_kb_release(keycode);
- } else {
- furi_hal_hid_kb_press(keycode);
- furi_hal_hid_kb_release(keycode);
- }
- if(bad_kb->stringdelay > 0) {
- furi_delay_ms(bad_kb->stringdelay);
- }
- }
- i++;
- }
- bad_kb->stringdelay = 0;
- return true;
-}
-
-static uint16_t ducky_get_keycode(BadKbScript* bad_kb, const char* param, bool accept_chars) {
+uint16_t ducky_get_keycode(BadKbScript* bad_kb, 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) &&
@@ -338,135 +195,74 @@ static int32_t
ducky_parse_line(BadKbScript* bad_kb, 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[])(BadKbScript*, 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_lang, strlen(ducky_cmd_lang)) == 0) {
- // DUCKY_LANG - ignore command to retain compatibility with existing scripts
- 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_kb->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_kb->stringdelay);
- if((state) && (bad_kb->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_kb, 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(bad_kb);
- state = ducky_altchar(bad_kb, 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(bad_kb);
- state = ducky_altstring(bad_kb, 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_kb->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_kb, line_tmp, true);
- if(bad_kb->bt) {
- furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
- furi_hal_bt_hid_kb_press(key);
- furi_delay_ms(bt_timeout);
- furi_hal_bt_hid_kb_release(key);
- furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
- } else {
- 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_kb, 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_kb, line_tmp, true);
- }
- if(bad_kb->bt) {
- furi_hal_bt_hid_kb_press(key);
- furi_delay_ms(bt_timeout);
- furi_hal_bt_hid_kb_release(key);
- } else {
- 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_kb, line, line_tmp, error, error_len));
}
+ // Special keys + modifiers
+ uint16_t key = ducky_get_keycode(bad_kb, 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_kb, line_tmp, true);
+ }
+ if(bad_kb->bt) {
+ furi_hal_bt_hid_kb_press(key);
+ furi_delay_ms(bt_timeout);
+ furi_hal_bt_hid_kb_release(key);
+ } else {
+ furi_hal_hid_kb_press(key);
+ furi_hal_hid_kb_release(key);
+ }
+ return (0);
}
static bool ducky_set_usb_id(BadKbScript* bad_kb, const char* line) {
diff --git a/applications/main/bad_kb/bad_kb_script.h b/applications/main/bad_kb/bad_kb_script.h
index a724971ec..05b91c3a4 100644
--- a/applications/main/bad_kb/bad_kb_script.h
+++ b/applications/main/bad_kb/bad_kb_script.h
@@ -5,11 +5,14 @@ extern "C" {
#endif
#include
+#include
#include
typedef struct BadKbApp BadKbApp;
-typedef struct BadKbScript BadKbScript;
+#define FILE_BUFFER_LEN 16
+
+extern uint8_t bt_timeout;
typedef enum {
BadKbStateInit,
@@ -34,6 +37,27 @@ typedef struct {
char error[64];
} BadKbState;
+typedef struct BadKbScript {
+ FuriHalUsbHidConfig hid_cfg;
+ BadKbState st;
+ FuriString* file_path;
+ FuriString* keyboard_layout;
+ 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;
+
+ Bt* bt;
+} BadKbScript;
+
void bad_kb_config_switch_mode(BadKbApp* app);
void bad_kb_config_switch_remember_mode(BadKbApp* app);
@@ -56,6 +80,12 @@ void bad_kb_script_toggle(BadKbScript* bad_kb);
BadKbState* bad_kb_script_get_state(BadKbScript* bad_kb);
+uint16_t ducky_get_keycode(BadKbScript* bad_kb, 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
diff --git a/applications/main/bad_kb/mnemonic.c b/applications/main/bad_kb/mnemonic.c
new file mode 100644
index 000000000..84f128b0f
--- /dev/null
+++ b/applications/main/bad_kb/mnemonic.c
@@ -0,0 +1,379 @@
+#include
+#include
+#include
+#include "mnemonic.h"
+
+#define TAG "BadKB"
+#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 BADKB_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(BadKbScript* bad_kb) {
+ if(bad_kb->bt) {
+ if((furi_hal_bt_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
+ furi_hal_bt_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
+ furi_delay_ms(bt_timeout);
+ furi_hal_bt_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
+ }
+ } else {
+ 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(BadKbScript* bad_kb, const char num) {
+ if((num < '0') || (num > '9')) return false;
+
+ uint16_t key = numpad_keys[num - '0'];
+ if(bad_kb->bt) {
+ furi_hal_bt_hid_kb_press(key);
+ furi_delay_ms(bt_timeout);
+ furi_hal_bt_hid_kb_release(key);
+ } else {
+ furi_hal_hid_kb_press(key);
+ furi_hal_hid_kb_release(key);
+ }
+
+ return true;
+}
+
+static bool ducky_altchar(BadKbScript* bad_kb, const char* charcode) {
+ uint8_t i = 0;
+ bool state = false;
+
+ FURI_LOG_I(WORKER_TAG, "char %s", charcode);
+
+ if(bad_kb->bt) {
+ furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT);
+ } else {
+ furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
+ }
+
+ while(!ducky_is_line_end(charcode[i])) {
+ state = ducky_numpad_press(bad_kb, charcode[i]);
+ if(state == false) break;
+ i++;
+ }
+
+ if(bad_kb->bt) {
+ furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT);
+ } else {
+ furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
+ }
+ return state;
+}
+
+static bool ducky_altstring(BadKbScript* bad_kb, 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(bad_kb, temp_str);
+ if(state == false) break;
+ i++;
+ }
+ return state;
+}
+
+static bool ducky_string(BadKbScript* bad_kb, const char* param) {
+ uint32_t i = 0;
+
+ while(param[i] != '\0') {
+ uint16_t keycode = BADKB_ASCII_TO_KEY(bad_kb, param[i]);
+ if(keycode != HID_KEYBOARD_NONE) {
+ if(bad_kb->bt) {
+ furi_hal_bt_hid_kb_press(keycode);
+ furi_delay_ms(bt_timeout);
+ furi_hal_bt_hid_kb_release(keycode);
+ } else {
+ furi_hal_hid_kb_press(keycode);
+ furi_hal_hid_kb_release(keycode);
+ }
+ if(bad_kb->stringdelay > 0) {
+ furi_delay_ms(bad_kb->stringdelay);
+ }
+ }
+ i++;
+ }
+ bad_kb->stringdelay = 0;
+ return true;
+}
+
+int32_t ducky_fnc_noop(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len) {
+ (void)bad_kb;
+ (void)line;
+ (void)line_tmp;
+ (void)error;
+ (void)error_len;
+ return (0);
+}
+
+int32_t ducky_fnc_delay(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len) {
+ bool state = false;
+ (void)bad_kb;
+ (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(
+ BadKbScript* bad_kb,
+ 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_kb->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(
+ BadKbScript* bad_kb,
+ 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_kb->stringdelay);
+ if((state) && (bad_kb->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(
+ BadKbScript* bad_kb,
+ 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_kb, 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(
+ BadKbScript* bad_kb,
+ 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_kb->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(
+ BadKbScript* bad_kb,
+ 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_kb, line_tmp, true);
+ if(bad_kb->bt) {
+ furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
+ furi_hal_bt_hid_kb_press(key);
+ furi_delay_ms(bt_timeout);
+ furi_hal_bt_hid_kb_release(key);
+ furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
+ } else {
+ 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(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len) {
+ bool state = false;
+ (void)bad_kb;
+ (void)line;
+
+ line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
+ ducky_numlock_on(bad_kb);
+ state = ducky_altchar(bad_kb, 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(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len) {
+ bool state = false;
+ (void)bad_kb;
+ (void)line;
+
+ line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
+ ducky_numlock_on(bad_kb);
+ state = ducky_altstring(bad_kb, 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(
+ BadKbScript* bad_kb,
+ 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_kb, line_tmp);
+ if(!state && error != NULL) {
+ snprintf(error, error_len, "Invalid string %s", line_tmp);
+ }
+ if(bad_kb->bt) {
+ furi_hal_bt_hid_kb_press(HID_KEYBOARD_RETURN);
+ furi_delay_ms(bt_timeout);
+ furi_hal_bt_hid_kb_release(HID_KEYBOARD_RETURN);
+ } else {
+ 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(
+ BadKbScript* bad_kb,
+ 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_kb, 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;
+ }
+ if(bad_kb->bt) {
+ furi_hal_bt_hid_kb_press(key);
+ } else {
+ furi_hal_hid_kb_press(key);
+ }
+ return (0);
+}
+
+int32_t ducky_fnc_release(
+ BadKbScript* bad_kb,
+ 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_kb, 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;
+ }
+ if(bad_kb->bt) {
+ furi_hal_bt_hid_kb_release(key);
+ } else {
+ furi_hal_hid_kb_release(key);
+ }
+ return (0);
+}
diff --git a/applications/main/bad_kb/mnemonic.h b/applications/main/bad_kb/mnemonic.h
new file mode 100644
index 000000000..74f4f0b8b
--- /dev/null
+++ b/applications/main/bad_kb/mnemonic.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "bad_kb_script.h"
+
+// A no opperation function
+int32_t ducky_fnc_noop(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// DELAY
+int32_t ducky_fnc_delay(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// DEFAULTDELAY
+int32_t ducky_fnc_defdelay(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// STRINGDELAY
+int32_t ducky_fnc_strdelay(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// STRING
+int32_t ducky_fnc_string(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// STRINGLN
+int32_t ducky_fnc_stringln(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// REPEAT
+int32_t ducky_fnc_repeat(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// SYSRQ
+int32_t ducky_fnc_sysrq(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// ALTCHAR
+int32_t ducky_fnc_altchar(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// ALTSTRING
+int32_t ducky_fnc_altstring(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// HOLD
+int32_t ducky_fnc_hold(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+// RELEASE
+int32_t ducky_fnc_release(
+ BadKbScript* bad_kb,
+ FuriString* line,
+ const char* line_tmp,
+ char* error,
+ size_t error_len);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/applications/main/xtreme_app/scenes/xtreme_app_scene_misc.c b/applications/main/xtreme_app/scenes/xtreme_app_scene_misc.c
index 030256be0..eb6fe0bbd 100644
--- a/applications/main/xtreme_app/scenes/xtreme_app_scene_misc.c
+++ b/applications/main/xtreme_app/scenes/xtreme_app_scene_misc.c
@@ -30,11 +30,13 @@ static void xtreme_app_scene_misc_dark_mode_changed(VariableItem* item) {
}
static void xtreme_app_scene_misc_left_handed_changed(VariableItem* item) {
- XtremeApp* app = variable_item_get_context(item);
bool value = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, value ? "ON" : "OFF");
- XTREME_SETTINGS()->left_handed = value;
- app->save_settings = true;
+ if(value) {
+ furi_hal_rtc_set_flag(FuriHalRtcFlagHandOrient);
+ } else {
+ furi_hal_rtc_reset_flag(FuriHalRtcFlagHandOrient);
+ }
}
void xtreme_app_scene_misc_on_enter(void* context) {
@@ -59,8 +61,9 @@ void xtreme_app_scene_misc_on_enter(void* context) {
item = variable_item_list_add(
var_item_list, "Left Handed", 2, xtreme_app_scene_misc_left_handed_changed, app);
- variable_item_set_current_value_index(item, xtreme_settings->left_handed);
- variable_item_set_current_value_text(item, xtreme_settings->left_handed ? "ON" : "OFF");
+ bool value = furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient);
+ variable_item_set_current_value_index(item, value);
+ variable_item_set_current_value_text(item, value ? "ON" : "OFF");
variable_item_list_set_enter_callback(
var_item_list, xtreme_app_scene_misc_var_item_list_callback, app);
diff --git a/applications/plugins/heap_defence_game/heap_defence.c b/applications/plugins/heap_defence_game/heap_defence.c
index f7d6e4ef7..ae9ae76cc 100644
--- a/applications/plugins/heap_defence_game/heap_defence.c
+++ b/applications/plugins/heap_defence_game/heap_defence.c
@@ -495,7 +495,9 @@ static void heap_defense_input_callback(InputEvent* input_event, FuriMessageQueu
static void heap_defense_timer_callback(FuriMessageQueue* event_queue) {
furi_assert(event_queue);
- GameEvent event = {.type = EventGameTick, .input = {0}};
+ GameEvent event;
+ event.type = EventGameTick;
+ event.input = (InputEvent){0};
furi_message_queue_put(event_queue, &event, 0);
}
diff --git a/applications/plugins/subbrute b/applications/plugins/subbrute
index 19153c723..49d32f8ed 160000
--- a/applications/plugins/subbrute
+++ b/applications/plugins/subbrute
@@ -1 +1 @@
-Subproject commit 19153c72395c7d62efac425325b4c4c646e1fd3f
+Subproject commit 49d32f8ed6790d3b9a99f261ad306766325a691a
diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c
index eadbd4834..f9012683d 100644
--- a/applications/services/gui/canvas.c
+++ b/applications/services/gui/canvas.c
@@ -4,6 +4,7 @@
#include
#include
+#include
#include
#include
#include
@@ -27,8 +28,6 @@ Canvas* canvas_init() {
// Wake up display
u8g2_SetPowerSave(&canvas->fb, 0);
- canvas_set_orientation(canvas, CanvasOrientationHorizontal);
-
// Clear buffer and send to device
canvas_clear(canvas);
canvas_commit(canvas);
@@ -410,46 +409,36 @@ void canvas_set_bitmap_mode(Canvas* canvas, bool alpha) {
void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) {
furi_assert(canvas);
- if(XTREME_SETTINGS()->left_handed) {
- if(orientation == CanvasOrientationHorizontal) {
- orientation = CanvasOrientationHorizontalFlip;
- } else if(orientation == CanvasOrientationHorizontalFlip) {
- orientation = CanvasOrientationHorizontal;
- }
- }
+ const u8g2_cb_t* rotate_cb = NULL;
+ bool need_swap = false;
if(canvas->orientation != orientation) {
switch(orientation) {
case CanvasOrientationHorizontal:
- if(canvas->orientation == CanvasOrientationVertical ||
- canvas->orientation == CanvasOrientationVerticalFlip) {
- FURI_SWAP(canvas->width, canvas->height);
- }
- u8g2_SetDisplayRotation(&canvas->fb, U8G2_R0);
+ need_swap = canvas->orientation == CanvasOrientationVertical ||
+ canvas->orientation == CanvasOrientationVerticalFlip;
+ rotate_cb = U8G2_R0;
break;
case CanvasOrientationHorizontalFlip:
- if(canvas->orientation == CanvasOrientationVertical ||
- canvas->orientation == CanvasOrientationVerticalFlip) {
- FURI_SWAP(canvas->width, canvas->height);
- }
- u8g2_SetDisplayRotation(&canvas->fb, U8G2_R2);
+ need_swap = canvas->orientation == CanvasOrientationVertical ||
+ canvas->orientation == CanvasOrientationVerticalFlip;
+ rotate_cb = U8G2_R2;
break;
case CanvasOrientationVertical:
- if(canvas->orientation == CanvasOrientationHorizontal ||
- canvas->orientation == CanvasOrientationHorizontalFlip) {
- FURI_SWAP(canvas->width, canvas->height);
- };
- u8g2_SetDisplayRotation(&canvas->fb, U8G2_R3);
+ need_swap = canvas->orientation == CanvasOrientationHorizontal ||
+ canvas->orientation == CanvasOrientationHorizontalFlip;
+ rotate_cb = U8G2_R3;
break;
case CanvasOrientationVerticalFlip:
- if(canvas->orientation == CanvasOrientationHorizontal ||
- canvas->orientation == CanvasOrientationHorizontalFlip) {
- FURI_SWAP(canvas->width, canvas->height);
- }
- u8g2_SetDisplayRotation(&canvas->fb, U8G2_R1);
+ need_swap = canvas->orientation == CanvasOrientationHorizontal ||
+ canvas->orientation == CanvasOrientationHorizontalFlip;
+ rotate_cb = U8G2_R1;
break;
default:
furi_assert(0);
}
+
+ if(need_swap) FURI_SWAP(canvas->width, canvas->height);
+ u8g2_SetDisplayRotation(&canvas->fb, rotate_cb);
canvas->orientation = orientation;
}
}
diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c
index 835bca2c8..8d64d7ebf 100644
--- a/applications/services/gui/gui.c
+++ b/applications/services/gui/gui.c
@@ -51,7 +51,13 @@ static void gui_redraw_status_bar(Gui* gui, bool need_attention) {
uint8_t left_used = 0;
uint8_t right_used = 0;
uint8_t width;
- canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
+
+ if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) {
+ canvas_set_orientation(gui->canvas, CanvasOrientationHorizontalFlip);
+ } else {
+ canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
+ }
+
canvas_frame_set(
gui->canvas, GUI_STATUS_BAR_X, GUI_STATUS_BAR_Y, GUI_DISPLAY_WIDTH, GUI_STATUS_BAR_HEIGHT);
diff --git a/applications/services/gui/gui_i.h b/applications/services/gui/gui_i.h
index 45061bd58..a5cd84120 100644
--- a/applications/services/gui/gui_i.h
+++ b/applications/services/gui/gui_i.h
@@ -8,6 +8,7 @@
#include "gui.h"
#include
+#include
#include
#include
#include
diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c
index 046958749..920b3c139 100644
--- a/applications/services/gui/view_dispatcher.c
+++ b/applications/services/gui/view_dispatcher.c
@@ -320,6 +320,13 @@ void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t
furi_message_queue_put(view_dispatcher->queue, &message, FuriWaitForever) == FuriStatusOk);
}
+static const ViewPortOrientation view_dispatcher_view_port_orientation_table[] = {
+ [ViewOrientationVertical] = ViewPortOrientationVertical,
+ [ViewOrientationVerticalFlip] = ViewPortOrientationVerticalFlip,
+ [ViewOrientationHorizontal] = ViewPortOrientationHorizontal,
+ [ViewOrientationHorizontalFlip] = ViewPortOrientationHorizontalFlip,
+};
+
void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* view) {
furi_assert(view_dispatcher);
// Dispatch view exit event
@@ -330,15 +337,12 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
view_dispatcher->current_view = view;
// Dispatch view enter event
if(view_dispatcher->current_view) {
- if(view->orientation == ViewOrientationVertical) {
- view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationVertical);
- } else if(view->orientation == ViewOrientationVerticalFlip) {
- view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationVerticalFlip);
- } else if(view->orientation == ViewOrientationHorizontal) {
- view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationHorizontal);
- } else if(view->orientation == ViewOrientationHorizontalFlip) {
- view_port_set_orientation(
- view_dispatcher->view_port, ViewPortOrientationHorizontalFlip);
+ ViewPortOrientation orientation =
+ view_dispatcher_view_port_orientation_table[view->orientation];
+ if(view_port_get_orientation(view_dispatcher->view_port) != orientation) {
+ view_port_set_orientation(view_dispatcher->view_port, orientation);
+ // we just rotated input keys, now it's time to sacrifice some input
+ view_dispatcher->ongoing_input = 0;
}
view_enter(view_dispatcher->current_view);
view_port_enabled_set(view_dispatcher->view_port, true);
diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c
index 3e0889fbf..8c2ff6fe0 100644
--- a/applications/services/gui/view_port.c
+++ b/applications/services/gui/view_port.c
@@ -1,7 +1,8 @@
#include "view_port_i.h"
-#include "xtreme/settings.h"
#include
+#include
+#include
#include "gui.h"
#include "gui_i.h"
@@ -49,48 +50,45 @@ static const InputKey view_port_input_mapping[ViewPortOrientationMAX][InputKeyMA
InputKeyBack}, //ViewPortOrientationVerticalFlip
};
+static const InputKey view_port_left_hand_input_mapping[InputKeyMAX] =
+ {InputKeyDown, InputKeyUp, InputKeyLeft, InputKeyRight, InputKeyOk, InputKeyBack};
+
+static const CanvasOrientation view_port_orientation_mapping[ViewPortOrientationMAX] = {
+ [ViewPortOrientationHorizontal] = CanvasOrientationHorizontal,
+ [ViewPortOrientationHorizontalFlip] = CanvasOrientationHorizontalFlip,
+ [ViewPortOrientationVertical] = CanvasOrientationVertical,
+ [ViewPortOrientationVerticalFlip] = CanvasOrientationVerticalFlip,
+};
+
// Remaps directional pad buttons on Flipper based on ViewPort orientation
static void view_port_map_input(InputEvent* event, ViewPortOrientation orientation) {
furi_assert(orientation < ViewPortOrientationMAX && event->key < InputKeyMAX);
+
+ if(event->sequence_source != INPUT_SEQUENCE_SOURCE_HARDWARE) {
+ return;
+ }
+
if(orientation == ViewPortOrientationHorizontal ||
orientation == ViewPortOrientationHorizontalFlip) {
- if(XTREME_SETTINGS()->left_handed) {
- switch(event->key) {
- case InputKeyUp:
- event->key = InputKeyDown;
- break;
- case InputKeyDown:
- event->key = InputKeyUp;
- break;
- case InputKeyLeft:
- event->key = InputKeyRight;
- break;
- case InputKeyRight:
- event->key = InputKeyLeft;
- break;
- default:
- break;
- }
+ if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) {
+ event->key = view_port_left_hand_input_mapping[event->key];
}
}
event->key = view_port_input_mapping[orientation][event->key];
}
static void view_port_setup_canvas_orientation(const ViewPort* view_port, Canvas* canvas) {
- switch(view_port->orientation) {
- case ViewPortOrientationHorizontalFlip:
- canvas_set_orientation(canvas, CanvasOrientationHorizontalFlip);
- break;
- case ViewPortOrientationVertical:
- canvas_set_orientation(canvas, CanvasOrientationVertical);
- break;
- case ViewPortOrientationVerticalFlip:
- canvas_set_orientation(canvas, CanvasOrientationVerticalFlip);
- break;
- default:
- canvas_set_orientation(canvas, CanvasOrientationHorizontal);
- break;
- };
+ CanvasOrientation orientation = view_port_orientation_mapping[view_port->orientation];
+
+ if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) {
+ if(orientation == CanvasOrientationHorizontal) {
+ orientation = CanvasOrientationHorizontalFlip;
+ } else if(orientation == CanvasOrientationHorizontalFlip) {
+ orientation = CanvasOrientationHorizontal;
+ }
+ }
+
+ canvas_set_orientation(canvas, orientation);
}
ViewPort* view_port_alloc() {
diff --git a/applications/services/input/input.c b/applications/services/input/input.c
index e1e581c9f..8da0a3400 100644
--- a/applications/services/input/input.c
+++ b/applications/services/input/input.c
@@ -23,7 +23,8 @@ inline static void input_timer_stop(FuriTimer* timer_id) {
void input_press_timer_callback(void* arg) {
InputPinState* input_pin = arg;
InputEvent event;
- event.sequence = input_pin->counter;
+ event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
+ event.sequence_counter = input_pin->counter;
event.key = input_pin->pin->key;
input_pin->press_counter++;
if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) {
@@ -114,16 +115,17 @@ int32_t input_srv(void* p) {
// Common state info
InputEvent event;
+ event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.key = input->pin_states[i].pin->key;
// Short / Long / Repeat timer routine
if(state) {
input->counter++;
input->pin_states[i].counter = input->counter;
- event.sequence = input->pin_states[i].counter;
+ event.sequence_counter = input->pin_states[i].counter;
input_timer_start(input->pin_states[i].press_timer, INPUT_PRESS_TICKS);
} else {
- event.sequence = input->pin_states[i].counter;
+ event.sequence_counter = input->pin_states[i].counter;
input_timer_stop(input->pin_states[i].press_timer);
if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) {
event.type = InputTypeShort;
diff --git a/applications/services/input/input.h b/applications/services/input/input.h
index 062dc0fa5..a62e84569 100644
--- a/applications/services/input/input.h
+++ b/applications/services/input/input.h
@@ -12,6 +12,8 @@ extern "C" {
#endif
#define RECORD_INPUT_EVENTS "input_events"
+#define INPUT_SEQUENCE_SOURCE_HARDWARE (0u)
+#define INPUT_SEQUENCE_SOURCE_SOFTWARE (1u)
/** Input Types
* Some of them are physical events and some logical
@@ -27,7 +29,13 @@ typedef enum {
/** Input Event, dispatches with FuriPubSub */
typedef struct {
- uint32_t sequence;
+ union {
+ uint32_t sequence;
+ struct {
+ uint8_t sequence_source : 2;
+ uint32_t sequence_counter : 30;
+ };
+ };
InputKey key;
InputType type;
} InputEvent;
diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c
index e66553d51..c2af425e9 100644
--- a/applications/services/rpc/rpc_gui.c
+++ b/applications/services/rpc/rpc_gui.c
@@ -12,6 +12,8 @@ typedef enum {
#define RpcGuiWorkerFlagAny (RpcGuiWorkerFlagTransmit | RpcGuiWorkerFlagExit)
+#define RPC_GUI_INPUT_RESET (0u)
+
typedef struct {
RpcSession* session;
Gui* gui;
@@ -26,6 +28,9 @@ typedef struct {
bool virtual_display_not_empty;
bool is_streaming;
+
+ uint32_t input_key_counter[InputKeyMAX];
+ uint32_t input_counter;
} RpcGuiSystem;
static void
@@ -194,6 +199,22 @@ static void
return;
}
+ // Event sequence shenanigans
+ event.sequence_source = INPUT_SEQUENCE_SOURCE_SOFTWARE;
+ if(event.type == InputTypePress) {
+ rpc_gui->input_counter++;
+ if(rpc_gui->input_counter == RPC_GUI_INPUT_RESET) rpc_gui->input_counter++;
+ rpc_gui->input_key_counter[event.key] = rpc_gui->input_counter;
+ }
+ if(rpc_gui->input_key_counter[event.key] == RPC_GUI_INPUT_RESET) {
+ FURI_LOG_W(TAG, "Out of sequence input event: key %d, type %d,", event.key, event.type);
+ }
+ event.sequence_counter = rpc_gui->input_key_counter[event.key];
+ if(event.type == InputTypeRelease) {
+ rpc_gui->input_key_counter[event.key] = RPC_GUI_INPUT_RESET;
+ }
+
+ // Submit event
FuriPubSub* input_events = furi_record_open(RECORD_INPUT_EVENTS);
furi_check(input_events);
furi_pubsub_publish(input_events, &event);
diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h
index da583ce50..5991e765f 100644
--- a/applications/services/storage/storage.h
+++ b/applications/services/storage/storage.h
@@ -10,12 +10,14 @@ extern "C" {
#define STORAGE_INT_PATH_PREFIX "/int"
#define STORAGE_EXT_PATH_PREFIX "/ext"
#define STORAGE_ANY_PATH_PREFIX "/any"
-#define STORAGE_APP_DATA_PATH_PREFIX "/app"
+#define STORAGE_APP_DATA_PATH_PREFIX "/data"
+#define STORAGE_APP_ASSETS_PATH_PREFIX "/assets"
#define INT_PATH(path) STORAGE_INT_PATH_PREFIX "/" path
#define EXT_PATH(path) STORAGE_EXT_PATH_PREFIX "/" path
#define ANY_PATH(path) STORAGE_ANY_PATH_PREFIX "/" path
#define APP_DATA_PATH(path) STORAGE_APP_DATA_PATH_PREFIX "/" path
+#define APP_ASSETS_PATH(path) STORAGE_APP_ASSETS_PATH_PREFIX "/" path
#define RECORD_STORAGE "storage"
@@ -146,6 +148,17 @@ bool storage_file_eof(File* file);
*/
bool storage_file_exists(Storage* storage, const char* path);
+/**
+ * @brief Copy data from one opened file to another opened file
+ * Size bytes will be copied from current position of source file to current position of destination file
+ *
+ * @param source source file
+ * @param destination destination file
+ * @param size size of data to copy
+ * @return bool success flag
+ */
+bool storage_file_copy_to_file(File* source, File* destination, uint32_t size);
+
/******************* Dir Functions *******************/
/** Opens a directory to get objects from it
diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c
index 8d8220f81..ffc3da4bc 100644
--- a/applications/services/storage/storage_external_api.c
+++ b/applications/services/storage/storage_external_api.c
@@ -9,6 +9,7 @@
#define MAX_NAME_LENGTH 256
#define MAX_EXT_LEN 16
+#define FILE_BUFFER_SIZE 512
#define TAG "StorageAPI"
@@ -251,6 +252,26 @@ bool storage_file_exists(Storage* storage, const char* path) {
return exist;
}
+bool storage_file_copy_to_file(File* source, File* destination, uint32_t size) {
+ uint8_t* buffer = malloc(FILE_BUFFER_SIZE);
+
+ while(size) {
+ uint32_t read_size = size > FILE_BUFFER_SIZE ? FILE_BUFFER_SIZE : size;
+ if(storage_file_read(source, buffer, read_size) != read_size) {
+ break;
+ }
+
+ if(storage_file_write(destination, buffer, read_size) != read_size) {
+ break;
+ }
+
+ size -= read_size;
+ }
+
+ free(buffer);
+ return size == 0;
+}
+
/****************** DIR ******************/
static bool storage_dir_open_internal(File* file, const char* path) {
diff --git a/applications/services/storage/storage_i.h b/applications/services/storage/storage_i.h
index 85df5d926..cb7f16e47 100644
--- a/applications/services/storage/storage_i.h
+++ b/applications/services/storage/storage_i.h
@@ -13,6 +13,7 @@ extern "C" {
#define STORAGE_COUNT (ST_INT + 1)
#define APPS_DATA_PATH EXT_PATH("apps_data")
+#define APPS_ASSETS_PATH EXT_PATH("apps_assets")
typedef struct {
ViewPort* view_port;
diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c
index cab1edff5..2a335e366 100644
--- a/applications/services/storage/storage_processing.c
+++ b/applications/services/storage/storage_processing.c
@@ -454,7 +454,7 @@ void storage_process_alias(
FuriString* apps_data_path_with_appsid = furi_string_alloc_set(APPS_DATA_PATH "/");
furi_string_cat(apps_data_path_with_appsid, furi_thread_get_appid(thread_id));
- // "/app" -> "/ext/apps_data/appsid"
+ // "/data" -> "/ext/apps_data/appsid"
furi_string_replace_at(
path,
0,
@@ -472,6 +472,18 @@ void storage_process_alias(
}
furi_string_free(apps_data_path_with_appsid);
+ } else if(furi_string_start_with(path, STORAGE_APP_ASSETS_PATH_PREFIX)) {
+ FuriString* apps_assets_path_with_appsid = furi_string_alloc_set(APPS_ASSETS_PATH "/");
+ furi_string_cat(apps_assets_path_with_appsid, furi_thread_get_appid(thread_id));
+
+ // "/assets" -> "/ext/apps_assets/appsid"
+ furi_string_replace_at(
+ path,
+ 0,
+ strlen(STORAGE_APP_ASSETS_PATH_PREFIX),
+ furi_string_get_cstr(apps_assets_path_with_appsid));
+
+ furi_string_free(apps_assets_path_with_appsid);
}
}
diff --git a/applications/services/xtreme/settings.c b/applications/services/xtreme/settings.c
index bfc3629f4..060f61bf0 100644
--- a/applications/services/xtreme/settings.c
+++ b/applications/services/xtreme/settings.c
@@ -48,7 +48,6 @@ void XTREME_SETTINGS_LOAD() {
xtreme_settings->butthurt_timer = 43200; // 12 H
xtreme_settings->sort_dirs_first = true; // ON
xtreme_settings->dark_mode = false; // OFF
- xtreme_settings->left_handed = false; // OFF
}
}
}
diff --git a/applications/services/xtreme/settings.h b/applications/services/xtreme/settings.h
index 562a37c73..4375a3170 100644
--- a/applications/services/xtreme/settings.h
+++ b/applications/services/xtreme/settings.h
@@ -37,7 +37,6 @@ typedef struct {
int32_t butthurt_timer;
bool sort_dirs_first;
bool dark_mode;
- bool left_handed;
} XtremeSettings;
XtremeSettings* XTREME_SETTINGS();
diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv
index 8060d38a2..07c323a1b 100644
--- a/firmware/targets/f18/api_symbols.csv
+++ b/firmware/targets/f18/api_symbols.csv
@@ -1,5 +1,5 @@
entry,status,name,type,params
-Version,+,18.0,,
+Version,+,18.1,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
@@ -1636,6 +1636,7 @@ Function,-,storage_dir_rewind,_Bool,File*
Function,+,storage_error_get_desc,const char*,FS_Error
Function,+,storage_file_alloc,File*,Storage*
Function,+,storage_file_close,_Bool,File*
+Function,+,storage_file_copy_to_file,_Bool,"File*, File*, uint32_t"
Function,+,storage_file_eof,_Bool,File*
Function,+,storage_file_exists,_Bool,"Storage*, const char*"
Function,+,storage_file_free,void,File*
diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv
index 3de7bd978..202c6a4d8 100644
--- a/firmware/targets/f7/api_symbols.csv
+++ b/firmware/targets/f7/api_symbols.csv
@@ -1,5 +1,5 @@
entry,status,name,type,params
-Version,+,18.0,,
+Version,+,18.1,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
@@ -2615,6 +2615,7 @@ Function,-,storage_dir_rewind,_Bool,File*
Function,+,storage_error_get_desc,const char*,FS_Error
Function,+,storage_file_alloc,File*,Storage*
Function,+,storage_file_close,_Bool,File*
+Function,+,storage_file_copy_to_file,_Bool,"File*, File*, uint32_t"
Function,+,storage_file_eof,_Bool,File*
Function,+,storage_file_exists,_Bool,"Storage*, const char*"
Function,+,storage_file_free,void,File*
diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h
index fe095e749..b16b04a68 100644
--- a/firmware/targets/furi_hal_include/furi_hal_rtc.h
+++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h
@@ -29,6 +29,7 @@ typedef enum {
FuriHalRtcFlagFactoryReset = (1 << 1),
FuriHalRtcFlagLock = (1 << 2),
FuriHalRtcFlagC2Update = (1 << 3),
+ FuriHalRtcFlagHandOrient = (1 << 4),
} FuriHalRtcFlag;
typedef enum {
diff --git a/lib/flipper_application/application_assets.c b/lib/flipper_application/application_assets.c
new file mode 100644
index 000000000..1262870d5
--- /dev/null
+++ b/lib/flipper_application/application_assets.c
@@ -0,0 +1,361 @@
+#include "application_assets.h"
+#include
+#include
+
+// #define ELF_ASSETS_DEBUG_LOG 1
+
+#ifndef ELF_ASSETS_DEBUG_LOG
+#undef FURI_LOG_D
+#define FURI_LOG_D(...)
+#undef FURI_LOG_E
+#define FURI_LOG_E(...)
+#endif
+
+#define FLIPPER_APPLICATION_ASSETS_MAGIC 0x4F4C5A44
+#define FLIPPER_APPLICATION_ASSETS_VERSION 1
+#define FLIPPER_APPLICATION_ASSETS_SIGNATURE_FILENAME ".assets.signature"
+
+#define BUFFER_SIZE 512
+
+#define TAG "fap_assets"
+
+#pragma pack(push, 1)
+
+typedef struct {
+ uint32_t magic;
+ uint32_t version;
+ uint32_t dirs_count;
+ uint32_t files_count;
+} FlipperApplicationAssetsHeader;
+
+#pragma pack(pop)
+
+typedef enum {
+ AssetsSignatureResultEqual,
+ AssetsSignatureResultNotEqual,
+ AssetsSignatureResultError,
+} AssetsSignatureResult;
+
+static FuriString* flipper_application_assets_alloc_app_full_path(FuriString* app_name) {
+ furi_assert(app_name);
+ FuriString* full_path = furi_string_alloc_set(APPS_ASSETS_PATH "/");
+ furi_string_cat(full_path, app_name);
+ return full_path;
+}
+
+static FuriString* flipper_application_assets_alloc_signature_file_path(FuriString* app_name) {
+ furi_assert(app_name);
+ FuriString* signature_file_path = flipper_application_assets_alloc_app_full_path(app_name);
+ furi_string_cat(signature_file_path, "/" FLIPPER_APPLICATION_ASSETS_SIGNATURE_FILENAME);
+
+ return signature_file_path;
+}
+
+static uint8_t* flipper_application_assets_alloc_and_load_data(File* file, size_t* size) {
+ furi_assert(file);
+
+ uint8_t* data = NULL;
+ uint32_t length = 0;
+
+ // read data length
+ if(storage_file_read(file, &length, sizeof(length)) != sizeof(length)) {
+ return NULL;
+ }
+
+ data = malloc(length);
+
+ // read data
+ if(storage_file_read(file, (void*)data, length) != length) {
+ free((void*)data);
+ return NULL;
+ }
+
+ if(size != NULL) {
+ *size = length;
+ }
+
+ return data;
+}
+
+static bool flipper_application_assets_process_files(
+ Storage* storage,
+ File* file,
+ FuriString* app_name,
+ uint32_t files_count) {
+ furi_assert(storage);
+ furi_assert(file);
+ furi_assert(app_name);
+
+ UNUSED(storage);
+
+ bool success = false;
+ uint32_t length = 0;
+ char* path = NULL;
+ FuriString* file_path = furi_string_alloc();
+ File* destination = storage_file_alloc(storage);
+
+ FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name);
+
+ for(uint32_t i = 0; i < files_count; i++) {
+ path = (char*)flipper_application_assets_alloc_and_load_data(file, NULL);
+
+ if(path == NULL) {
+ break;
+ }
+
+ // read file size
+ if(storage_file_read(file, &length, sizeof(length)) != sizeof(length)) {
+ break;
+ }
+
+ furi_string_set(file_path, full_path);
+ furi_string_cat(file_path, "/");
+ furi_string_cat(file_path, path);
+
+ if(!storage_file_open(
+ destination, furi_string_get_cstr(file_path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+ FURI_LOG_E(TAG, "Can't create file: %s", furi_string_get_cstr(file_path));
+ break;
+ }
+
+ // copy data to file
+ if(!storage_file_copy_to_file(file, destination, length)) {
+ FURI_LOG_E(TAG, "Can't copy data to file: %s", furi_string_get_cstr(file_path));
+ break;
+ }
+
+ storage_file_close(destination);
+
+ free(path);
+ path = NULL;
+
+ if(i == files_count - 1) {
+ success = true;
+ }
+ }
+
+ if(path != NULL) {
+ free(path);
+ }
+
+ storage_file_free(destination);
+ furi_string_free(file_path);
+
+ return success;
+}
+
+static bool flipper_application_assets_process_dirs(
+ Storage* storage,
+ File* file,
+ FuriString* app_name,
+ uint32_t dirs_count) {
+ furi_assert(storage);
+ furi_assert(file);
+ furi_assert(app_name);
+
+ bool success = false;
+ FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name);
+
+ do {
+ if(!storage_simply_mkdir(storage, APPS_ASSETS_PATH)) {
+ break;
+ }
+
+ if(!storage_simply_mkdir(storage, furi_string_get_cstr(full_path))) {
+ break;
+ }
+
+ FuriString* dir_path = furi_string_alloc();
+ char* path = NULL;
+
+ for(uint32_t i = 0; i < dirs_count; i++) {
+ path = (char*)flipper_application_assets_alloc_and_load_data(file, NULL);
+
+ if(path == NULL) {
+ break;
+ }
+
+ furi_string_set(dir_path, full_path);
+ furi_string_cat(dir_path, "/");
+ furi_string_cat(dir_path, path);
+
+ if(!storage_simply_mkdir(storage, furi_string_get_cstr(dir_path))) {
+ FURI_LOG_E(TAG, "Can't create directory: %s", furi_string_get_cstr(dir_path));
+ break;
+ }
+
+ free(path);
+ path = NULL;
+
+ if(i == dirs_count - 1) {
+ success = true;
+ }
+ }
+
+ if(path != NULL) {
+ free(path);
+ }
+
+ furi_string_free(dir_path);
+ } while(false);
+
+ furi_string_free(full_path);
+
+ return success;
+}
+
+static AssetsSignatureResult flipper_application_assets_process_signature(
+ Storage* storage,
+ File* file,
+ FuriString* app_name,
+ uint8_t** signature_data,
+ size_t* signature_data_size) {
+ furi_assert(storage);
+ furi_assert(file);
+ furi_assert(app_name);
+ furi_assert(signature_data);
+ furi_assert(signature_data_size);
+
+ AssetsSignatureResult result = AssetsSignatureResultError;
+ File* signature_file = storage_file_alloc(storage);
+ FuriString* signature_file_path =
+ flipper_application_assets_alloc_signature_file_path(app_name);
+
+ do {
+ // read signature
+ *signature_data =
+ flipper_application_assets_alloc_and_load_data(file, signature_data_size);
+
+ if(*signature_data == NULL) { //-V547
+ FURI_LOG_E(TAG, "Can't read signature");
+ break;
+ }
+
+ result = AssetsSignatureResultNotEqual;
+
+ if(!storage_file_open(
+ signature_file,
+ furi_string_get_cstr(signature_file_path),
+ FSAM_READ_WRITE,
+ FSOM_OPEN_EXISTING)) {
+ FURI_LOG_E(TAG, "Can't open signature file");
+ break;
+ }
+
+ size_t signature_size = storage_file_size(signature_file);
+ uint8_t* signature_file_data = malloc(signature_size);
+ if(storage_file_read(signature_file, signature_file_data, signature_size) !=
+ signature_size) {
+ FURI_LOG_E(TAG, "Can't read signature file");
+ free(signature_file_data);
+ break;
+ }
+
+ if(memcmp(*signature_data, signature_file_data, signature_size) == 0) {
+ FURI_LOG_D(TAG, "Assets signature is equal");
+ result = AssetsSignatureResultEqual;
+ }
+
+ free(signature_file_data);
+ } while(0);
+
+ storage_file_free(signature_file);
+ furi_string_free(signature_file_path);
+
+ return result;
+}
+
+bool flipper_application_assets_load(File* file, const char* elf_path, size_t offset, size_t size) {
+ UNUSED(size);
+ furi_assert(file);
+ furi_assert(elf_path);
+ FlipperApplicationAssetsHeader header;
+ bool result = false;
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ uint8_t* signature_data = NULL;
+ size_t signature_data_size = 0;
+ FuriString* app_name = furi_string_alloc();
+ path_extract_filename_no_ext(elf_path, app_name);
+
+ FURI_LOG_D(TAG, "Loading assets for %s", furi_string_get_cstr(app_name));
+
+ do {
+ if(!storage_file_seek(file, offset, true)) {
+ break;
+ }
+
+ // read header
+ if(storage_file_read(file, &header, sizeof(header)) != sizeof(header)) {
+ break;
+ }
+
+ if(header.magic != FLIPPER_APPLICATION_ASSETS_MAGIC) {
+ break;
+ }
+
+ if(header.version != FLIPPER_APPLICATION_ASSETS_VERSION) {
+ break;
+ }
+
+ // process signature
+ AssetsSignatureResult signature_result = flipper_application_assets_process_signature(
+ storage, file, app_name, &signature_data, &signature_data_size);
+
+ if(signature_result == AssetsSignatureResultError) {
+ FURI_LOG_E(TAG, "Assets signature error");
+ break;
+ } else if(signature_result == AssetsSignatureResultEqual) {
+ FURI_LOG_D(TAG, "Assets signature equal, skip loading");
+ result = true;
+ break;
+ } else {
+ FURI_LOG_D(TAG, "Assets signature not equal, loading");
+
+ // remove old assets
+ FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name);
+ storage_simply_remove_recursive(storage, furi_string_get_cstr(full_path));
+ furi_string_free(full_path);
+
+ FURI_LOG_D(TAG, "Assets removed");
+ }
+
+ // process directories
+ if(!flipper_application_assets_process_dirs(storage, file, app_name, header.dirs_count)) {
+ break;
+ }
+
+ // process files
+ if(!flipper_application_assets_process_files(storage, file, app_name, header.files_count)) {
+ break;
+ }
+
+ // write signature
+ FuriString* signature_file_path =
+ flipper_application_assets_alloc_signature_file_path(app_name);
+ File* signature_file = storage_file_alloc(storage);
+
+ if(storage_file_open(
+ signature_file,
+ furi_string_get_cstr(signature_file_path),
+ FSAM_WRITE,
+ FSOM_CREATE_ALWAYS)) {
+ storage_file_write(signature_file, signature_data, signature_data_size);
+ }
+
+ storage_file_free(signature_file);
+ furi_string_free(signature_file_path);
+
+ result = true;
+ } while(false);
+
+ if(signature_data != NULL) {
+ free(signature_data);
+ }
+
+ furi_record_close(RECORD_STORAGE);
+ furi_string_free(app_name);
+
+ FURI_LOG_D(TAG, "Assets loading %s", result ? "success" : "failed");
+
+ return result;
+}
\ No newline at end of file
diff --git a/lib/flipper_application/application_assets.h b/lib/flipper_application/application_assets.h
new file mode 100644
index 000000000..83bb14fb6
--- /dev/null
+++ b/lib/flipper_application/application_assets.h
@@ -0,0 +1,17 @@
+/**
+ * @file application_assets.h
+ * Flipper application assets
+ */
+#pragma once
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+bool flipper_application_assets_load(File* file, const char* elf_path, size_t offset, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c
index 64d5755ef..58e315333 100644
--- a/lib/flipper_application/elf/elf_file.c
+++ b/lib/flipper_application/elf/elf_file.c
@@ -241,7 +241,7 @@ static void elf_relocate_jmp_call(ELFFile* elf, Elf32_Addr relAddr, int type, El
if(to_thumb || (symAddr & 2) || (!is_call)) {
FURI_LOG_D(
TAG,
- "can't relocate value at %x, %s, doing trampoline",
+ "can't relocate value at %lx, %s, doing trampoline",
relAddr,
elf_reloc_type_to_str(type));
@@ -421,29 +421,11 @@ typedef enum {
SectionTypeRelData = 1 << 2,
SectionTypeSymTab = 1 << 3,
SectionTypeStrTab = 1 << 4,
- SectionTypeManifest = 1 << 5,
- SectionTypeDebugLink = 1 << 6,
+ SectionTypeDebugLink = 1 << 5,
- SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab | SectionTypeManifest,
+ SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab,
} SectionType;
-static bool elf_load_metadata(
- ELFFile* elf,
- Elf32_Shdr* section_header,
- FlipperApplicationManifest* manifest) {
- if(section_header->sh_size < sizeof(FlipperApplicationManifest)) {
- return false;
- }
-
- if(manifest == NULL) {
- return true;
- }
-
- return storage_file_seek(elf->fd, section_header->sh_offset, true) &&
- storage_file_read(elf->fd, manifest, section_header->sh_size) ==
- section_header->sh_size;
-}
-
static bool elf_load_debug_link(ELFFile* elf, Elf32_Shdr* section_header) {
elf->debug_link_info.debug_link_size = section_header->sh_size;
elf->debug_link_info.debug_link = malloc(section_header->sh_size);
@@ -478,7 +460,7 @@ static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr*
return false;
}
- FURI_LOG_D(TAG, "0x%X", section->data);
+ FURI_LOG_D(TAG, "0x%p", section->data);
return true;
}
@@ -486,8 +468,7 @@ static SectionType elf_preload_section(
ELFFile* elf,
size_t section_idx,
Elf32_Shdr* section_header,
- FuriString* name_string,
- FlipperApplicationManifest* manifest) {
+ FuriString* name_string) {
const char* name = furi_string_get_cstr(name_string);
#ifdef ELF_DEBUG_LOG
@@ -572,16 +553,6 @@ static SectionType elf_preload_section(
return SectionTypeStrTab;
}
- // Load manifest section
- if(strcmp(name, ".fapmeta") == 0) {
- FURI_LOG_D(TAG, "Found .fapmeta section");
- if(elf_load_metadata(elf, section_header, manifest)) {
- return SectionTypeManifest;
- } else {
- return SectionTypeERROR;
- }
- }
-
// Load debug link section
if(strcmp(name, ".gnu_debuglink") == 0) {
FURI_LOG_D(TAG, "Found .gnu_debuglink section");
@@ -692,41 +663,12 @@ bool elf_file_open(ELFFile* elf, const char* path) {
return true;
}
-bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest) {
- bool result = false;
- FuriString* name;
- name = furi_string_alloc();
-
- FURI_LOG_D(TAG, "Looking for manifest section");
- for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) {
- Elf32_Shdr section_header;
-
- furi_string_reset(name);
- if(!elf_read_section(elf, section_idx, §ion_header, name)) {
- break;
- }
-
- if(furi_string_cmp(name, ".fapmeta") == 0) {
- if(elf_load_metadata(elf, §ion_header, manifest)) {
- FURI_LOG_D(TAG, "Load manifest done");
- result = true;
- break;
- } else {
- break;
- }
- }
- }
-
- furi_string_free(name);
- return result;
-}
-
-bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manifest) {
+bool elf_file_load_section_table(ELFFile* elf) {
SectionType loaded_sections = SectionTypeERROR;
- FuriString* name;
- name = furi_string_alloc();
+ FuriString* name = furi_string_alloc();
FURI_LOG_D(TAG, "Scan ELF indexs...");
+ // TODO: why we start from 1?
for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) {
Elf32_Shdr section_header;
@@ -738,8 +680,7 @@ bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manif
FURI_LOG_D(
TAG, "Preloading data for section #%d %s", section_idx, furi_string_get_cstr(name));
- SectionType section_type =
- elf_preload_section(elf, section_idx, §ion_header, name, manifest);
+ SectionType section_type = elf_preload_section(elf, section_idx, §ion_header, name);
loaded_sections |= section_type;
if(section_type == SectionTypeERROR) {
@@ -753,14 +694,49 @@ bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manif
return IS_FLAGS_SET(loaded_sections, SectionTypeValid);
}
+ElfProcessSectionResult elf_process_section(
+ ELFFile* elf,
+ const char* name,
+ ElfProcessSection* process_section,
+ void* context) {
+ ElfProcessSectionResult result = ElfProcessSectionResultNotFound;
+ FuriString* section_name = furi_string_alloc();
+ Elf32_Shdr section_header;
+
+ // find section
+ // TODO: why we start from 1?
+ for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) {
+ furi_string_reset(section_name);
+ if(!elf_read_section(elf, section_idx, §ion_header, section_name)) {
+ break;
+ }
+
+ if(furi_string_cmp(section_name, name) == 0) {
+ result = ElfProcessSectionResultCannotProcess;
+ break;
+ }
+ }
+
+ if(result != ElfProcessSectionResultNotFound) { //-V547
+ if(process_section(elf->fd, section_header.sh_offset, section_header.sh_size, context)) {
+ result = ElfProcessSectionResultSuccess;
+ } else {
+ result = ElfProcessSectionResultCannotProcess;
+ }
+ }
+
+ furi_string_free(section_name);
+
+ return result;
+}
+
ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) {
ELFFileLoadStatus status = ELFFileLoadStatusSuccess;
ELFSectionDict_it_t it;
AddressCache_init(elf->relocation_cache);
- for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it);
- ELFSectionDict_next(it)) {
+ for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) {
ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it);
FURI_LOG_D(TAG, "Relocating section '%s'", itref->key);
if(!elf_relocate_section(elf, &itref->value)) {
diff --git a/lib/flipper_application/elf/elf_file.h b/lib/flipper_application/elf/elf_file.h
index 673f165cc..f371cdb22 100644
--- a/lib/flipper_application/elf/elf_file.h
+++ b/lib/flipper_application/elf/elf_file.h
@@ -37,6 +37,14 @@ typedef enum {
ELFFileLoadStatusMissingImports,
} ELFFileLoadStatus;
+typedef enum {
+ ElfProcessSectionResultNotFound,
+ ElfProcessSectionResultCannotProcess,
+ ElfProcessSectionResultSuccess,
+} ElfProcessSectionResult;
+
+typedef bool(ElfProcessSection)(File* file, size_t offset, size_t size, void* context);
+
/**
* @brief Allocate ELFFile instance
* @param storage
@@ -59,21 +67,12 @@ void elf_file_free(ELFFile* elf_file);
*/
bool elf_file_open(ELFFile* elf_file, const char* path);
-/**
- * @brief Load ELF file manifest
- * @param elf
- * @param manifest
- * @return bool
- */
-bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest);
-
/**
* @brief Load ELF file section table (load stage #1)
* @param elf_file
- * @param manifest
* @return bool
*/
-bool elf_file_load_section_table(ELFFile* elf_file, FlipperApplicationManifest* manifest);
+bool elf_file_load_section_table(ELFFile* elf_file);
/**
* @brief Load and relocate ELF file sections (load stage #2)
@@ -122,6 +121,21 @@ void elf_file_init_debug_info(ELFFile* elf_file, ELFDebugInfo* debug_info);
*/
void elf_file_clear_debug_info(ELFDebugInfo* debug_info);
+/**
+ * @brief Process ELF file section
+ *
+ * @param elf_file
+ * @param name
+ * @param process_section
+ * @param context
+ * @return ElfProcessSectionResult
+ */
+ElfProcessSectionResult elf_process_section(
+ ELFFile* elf_file,
+ const char* name,
+ ElfProcessSection* process_section,
+ void* context);
+
#ifdef __cplusplus
}
#endif
\ No newline at end of file
diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c
index 58909218a..6e20c0809 100644
--- a/lib/flipper_application/flipper_application.c
+++ b/lib/flipper_application/flipper_application.c
@@ -1,6 +1,7 @@
#include "flipper_application.h"
#include "elf/elf_file.h"
#include
+#include "application_assets.h"
#define TAG "fapp"
@@ -55,24 +56,83 @@ static FlipperApplicationPreloadStatus
return FlipperApplicationPreloadStatusSuccess;
}
-/* Parse headers, load manifest */
-FlipperApplicationPreloadStatus
- flipper_application_preload_manifest(FlipperApplication* app, const char* path) {
- if(!elf_file_open(app->elf, path) || !elf_file_load_manifest(app->elf, &app->manifest)) {
+static bool flipper_application_process_manifest_section(
+ File* file,
+ size_t offset,
+ size_t size,
+ void* context) {
+ FlipperApplicationManifest* manifest = context;
+
+ if(size < sizeof(FlipperApplicationManifest)) {
+ return false;
+ }
+
+ if(manifest == NULL) {
+ return true;
+ }
+
+ return storage_file_seek(file, offset, true) &&
+ storage_file_read(file, manifest, size) == size;
+}
+
+// we can't use const char* as context because we will lose the const qualifier
+typedef struct {
+ const char* path;
+} FlipperApplicationPreloadAssetsContext;
+
+static bool flipper_application_process_assets_section(
+ File* file,
+ size_t offset,
+ size_t size,
+ void* context) {
+ FlipperApplicationPreloadAssetsContext* preload_context = context;
+ return flipper_application_assets_load(file, preload_context->path, offset, size);
+}
+
+static FlipperApplicationPreloadStatus
+ flipper_application_load(FlipperApplication* app, const char* path, bool load_full) {
+ if(!elf_file_open(app->elf, path)) {
+ return FlipperApplicationPreloadStatusInvalidFile;
+ }
+
+ // if we are loading full file
+ if(load_full) {
+ // load section table
+ if(!elf_file_load_section_table(app->elf)) {
+ return FlipperApplicationPreloadStatusInvalidFile;
+ }
+
+ // load assets section
+ FlipperApplicationPreloadAssetsContext preload_context = {.path = path};
+ if(elf_process_section(
+ app->elf,
+ ".fapassets",
+ flipper_application_process_assets_section,
+ &preload_context) == ElfProcessSectionResultCannotProcess) {
+ return FlipperApplicationPreloadStatusInvalidFile;
+ }
+ }
+
+ // load manifest section
+ if(elf_process_section(
+ app->elf, ".fapmeta", flipper_application_process_manifest_section, &app->manifest) !=
+ ElfProcessSectionResultSuccess) {
return FlipperApplicationPreloadStatusInvalidFile;
}
return flipper_application_validate_manifest(app);
}
+/* Parse headers, load manifest */
+FlipperApplicationPreloadStatus
+ flipper_application_preload_manifest(FlipperApplication* app, const char* path) {
+ return flipper_application_load(app, path, false);
+}
+
/* Parse headers, load full file */
FlipperApplicationPreloadStatus
flipper_application_preload(FlipperApplication* app, const char* path) {
- if(!elf_file_open(app->elf, path) || !elf_file_load_section_table(app->elf, &app->manifest)) {
- return FlipperApplicationPreloadStatusInvalidFile;
- }
-
- return flipper_application_validate_manifest(app);
+ return flipper_application_load(app, path, true);
}
const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app) {
diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c
index 0adcb1923..dc8d2b9c4 100644
--- a/lib/nfc/protocols/mifare_classic.c
+++ b/lib/nfc/protocols/mifare_classic.c
@@ -291,6 +291,10 @@ bool mf_classic_is_allowed_access_data_block(
uint8_t* sector_trailer =
data->block[mf_classic_get_sector_trailer_num_by_block(block_num)].value;
+ if(block_num == 0 && action == MfClassicActionDataWrite) {
+ return false;
+ }
+
uint8_t sector_block;
if(block_num <= 128) {
sector_block = block_num & 0x03;
diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py
index 624113101..7ccbd5529 100644
--- a/scripts/fbt/appmanifest.py
+++ b/scripts/fbt/appmanifest.py
@@ -67,6 +67,7 @@ class FlipperApplication:
fap_icon_assets_symbol: Optional[str] = None
fap_extbuild: List[ExternallyBuiltFile] = field(default_factory=list)
fap_private_libs: List[Library] = field(default_factory=list)
+ fap_file_assets: Optional[str] = None
# Internally used by fbt
_appdir: Optional[object] = None
_apppath: Optional[str] = None
diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py
index 214afd8af..1a2761c54 100644
--- a/scripts/fbt_tools/fbt_extapps.py
+++ b/scripts/fbt_tools/fbt_extapps.py
@@ -1,5 +1,5 @@
from dataclasses import dataclass, field
-from typing import Optional
+from typing import Optional, TypedDict
from SCons.Builder import Builder
from SCons.Action import Action
from SCons.Errors import UserError
@@ -15,6 +15,8 @@ import os
import pathlib
import itertools
import shutil
+import struct
+import hashlib
from ansi.color import fg
@@ -151,12 +153,24 @@ def BuildAppElf(env, app):
app_artifacts.compact,
[app_env["SDK_DEFINITION"], app_env.Value(manifest_vals)],
)
+
+ # Add dependencies on icon files
if app.fap_icon:
app_env.Depends(
app_artifacts.compact,
app_env.File(f"{app._apppath}/{app.fap_icon}"),
)
+ # Add dependencies on file assets
+ if app.fap_file_assets:
+ app_env.Depends(
+ app_artifacts.compact,
+ app_env.GlobRecursive(
+ "*",
+ app._appdir.Dir(app.fap_file_assets),
+ ),
+ )
+
app_artifacts.validator = app_env.ValidateAppImports(app_artifacts.compact)
app_env.AlwaysBuild(app_artifacts.validator)
app_env.Alias(app_alias, app_artifacts.validator)
@@ -266,6 +280,159 @@ def resources_fap_dist_action(target, source, env):
shutil.copy(src.path, target.path)
+def generate_embed_app_metadata_emitter(target, source, env):
+ app = env["APP"]
+
+ meta_file_name = source[0].path + ".meta"
+ target.append("#" + meta_file_name)
+
+ if app.fap_file_assets:
+ files_section = source[0].path + ".files.section"
+ target.append("#" + files_section)
+
+ return (target, source)
+
+
+class File(TypedDict):
+ path: str
+ size: int
+ content_path: str
+
+
+class Dir(TypedDict):
+ path: str
+
+
+def prepare_app_files(target, source, env):
+ app = env["APP"]
+
+ directory = app._appdir.Dir(app.fap_file_assets)
+ directory_path = directory.abspath
+
+ if not directory.exists():
+ raise UserError(f"File asset directory {directory} does not exist")
+
+ file_list: list[File] = []
+ directory_list: list[Dir] = []
+
+ for root, dirs, files in os.walk(directory_path):
+ for file_info in files:
+ file_path = os.path.join(root, file_info)
+ file_size = os.path.getsize(file_path)
+ file_list.append(
+ {
+ "path": os.path.relpath(file_path, directory_path),
+ "size": file_size,
+ "content_path": file_path,
+ }
+ )
+
+ for dir_info in dirs:
+ dir_path = os.path.join(root, dir_info)
+ dir_size = sum(
+ os.path.getsize(os.path.join(dir_path, f)) for f in os.listdir(dir_path)
+ )
+ directory_list.append(
+ {
+ "path": os.path.relpath(dir_path, directory_path),
+ }
+ )
+
+ file_list.sort(key=lambda f: f["path"])
+ directory_list.sort(key=lambda d: d["path"])
+
+ files_section = source[0].path + ".files.section"
+
+ with open(files_section, "wb") as f:
+ # u32 magic
+ # u32 version
+ # u32 dirs_count
+ # u32 files_count
+ # u32 signature_size
+ # u8[] signature
+ # Dirs:
+ # u32 dir_name length
+ # u8[] dir_name
+ # Files:
+ # u32 file_name length
+ # u8[] file_name
+ # u32 file_content_size
+ # u8[] file_content
+
+ # Write header magic and version
+ f.write(struct.pack("