Merge remote-tracking branch 'mntm/dev' into js-app-internal

This commit is contained in:
Willy-JL
2024-10-29 23:18:56 +00:00
74 changed files with 3606 additions and 1072 deletions
+37 -9
View File
@@ -8,35 +8,48 @@
- Non-exhaustive list of changes to help you fix your scripts:
- `badusb`:
- `setup()`: `mfr_name`, `prod_name`, `layout_path` parameters renamed to `mfrName`, `prodName`, `layoutPath`
- existing scripts using badusb will need to simply rename these
- effort required to update old scripts using badusb: very minimal
- `dialog`:
- removed, now replaced by `gui/dialog` and `gui/file_picker` (see below)
- `event_loop`:
- new module, allows timer functionality, callbacks and event-driven programming, used heavily alongside gpio and gui modules
- `gpio`:
- fully overhauled, now you `get()` pin instances and perform actions on them like `.init()`
- existing scripts using gpio will need some reworking
- now supports interrupts, callbacks and more cool things
- effort required to update old scripts using gpio: moderate
- `gui`:
- new module, fully overhauled, replaces dialog, keyboard, submenu, textbox modules
- higher barrier to entry than older modules (requires usage of `event_loop` and `gui.viewDispatcher`), but much more flexible, powerful and easier to extend
- includes all previously available js gui functionality (except `widget`), and also adds `gui/loading` and `gui/empty_screen` views
- existing scripts using gui in any way will need a huge amount of reworking
- currently `gui/file_picker` works different than other new view objects, it is a simple `.pickFile()` synchronous function, but this [may change later](https://github.com/flipperdevices/flipperzero-firmware/pull/3961#discussion_r1805579153)
- effort required to update old scripts using gui: extensive
- `keyboard`:
- removed, now replaced by `gui/text_input` and `gui/byte_input` (see above)
- `math`:
- `is_equal()` renamed to `isEqual()`
- `storage`:
- fully overhauled, now you `openFile()`s and perform actions on them like `.read()`
- now supports many more operations including different open modes, directories and much more
- `virtualInit()`, `virtualMount()`, `virtualQuit()` still work the same
- existing scripts using storage will need some reworking
- effort required to update old scripts using storage: moderate
- `submenu`:
- removed, now replaced by `gui/submenu` (see above)
- `textbox`:
- removed, now replace by `gui/text_box` (see above)
- `widget`:
- only gui functionality not ported to new gui module, remains unchanged for now but likely to be ported later on
- Added type definitions (typescript files for type checking in IDE, Flipper does not run typescript, and you code in javascript)
- globals:
- `__filepath` and `__dirpath` renamed to `__filename` and `__dirname` like in nodejs
- `to_string()` renamed and moved to number class as `n.toString()`, now supports optional base parameter
- `to_hex_string()` removed, now use `n.toString(16)`
- `parse_int()` renamed to `parseInt()`, now supports optional base parameter
- `to_upper_case()` and `to_lower_case()` renamed and moved to string class as `s.toUpperCase()` and `s.toLowerCase()`
- effort required to update old scripts using these: minimal
- Added type definitions (typescript files for type checking in IDE, Flipper does not run typescript)
- Documentation is incomplete and deprecated, from now on you should refer to type definitions (`applications/system/js_app/types`), those will always be correct
- Type definitions for extra modules we have that OFW doesn't will come later
- GUI: Refactored TextInput illegal symbols (by @Willy-JL)
- If your app used `text_input_add_illegal_symbols(text_input)` it should change to `text_input_show_illegal_symbols(text_input, true)`
### Added:
- Apps:
@@ -46,10 +59,12 @@
- Sub-GHz:
- Show satellites count with an icon (#215 by @m7i-org)
- Add Bresser 3CH weather station protocol (#217 #245 by @m7i-org)
- Add Vauno-EN8822 weather station protocol (#262 by @m7i-org)
- UL: Add Marantec24 protocol (static 24 bit) with add manually (by @xMasterX)
- UL: Add GangQi protocol (static 34 bit) with button parsing and add manually (by @xMasterX & @Skorpionm)
- UL: Add Hollarm protocol (static 42 bit) with button parsing and add manually (by @xMasterX & @Skorpionm)
- UL: Add Hay21 protocol (dynamic 21 bit) with button parsing (by @xMasterX)
- UL: Add Keeloq Monarch full support, with add manually (by @ashphx)
- UL: Princeton custom buttons support (by @xMasterX)
- NFC:
- OFW: MIFARE Classic Key Recovery Improvements (PR 3822 by @noproto)
@@ -61,9 +76,13 @@
- OFW: H World Hotel Chain Room Key Parser and MFC keys (by @zinongli)
- OFW: Parser for Tianjin Railway Transit (by @zinongli)
- Infrared:
- Bluray/DVD Universal Remote (#250 by @jaylikesbunda)
- Bluray/DVD Universal Remote (#250 #264 by @jaylikesbunda)
- Option to "Load from Library File" for Universal Remotes (#255 by @zxkmm)
- Updater: New Yappy themed icon while updating (#253 by @the1anonlypr3 & @Kuronons & @nescap)
- JS:
- New `i2c` module (#259 by @jamisonderek)
- New `spi` module (#272 by @jamisonderek)
- RPC: Added ASCII event support (#284 by @Willy-JL)
- BadKB:
- OFW: Add linux/gnome badusb demo files (by @thomasnemer)
- Add older qFlipper install demos for windows and macos (by @DXVVAY & @grugnoymeme)
@@ -71,6 +90,7 @@
- OFW: Dolphin: Happy mode in Desktop settings (by @portasynthinca3)
- OFW: CLI: Improvements part I, `neofetch` command (by @portasynthinca3), fix for lab.flipper.net (by @xMasterX)
- GUI:
- ByteInput supports ASCII input (by @Willy-JL)
- OFW: Add up and down button drawing functions to GUI elements (by @DerSkythe)
- OFW: Extended icon draw function in Canvas (by @RebornedBrain)
- OFW: RPC: Support 5V on GPIO control for ext. modules (by @gsurkov)
@@ -88,21 +108,27 @@
- DTMF Dolphin: Add EAS tone support (by @JendrBendr)
- NFC Playlist: Error screens for playlist already exists and item already in playlist, general improvements (by @acegoal07), refactor rename/new scene without thread (by @Willy-JL)
- CLI-GUI Bridge: Fixes and improvements (by @ranchordo)
- Seader: Enable T=1 (by @bettse)
- Seader: Enable T=1, show error for timeout, fix wrong LRC logging, fix crash scanning NTAG215 with MFC option (by @bettse)
- BLE Spam: Fix menu index callback (by @Willy-JL)
- Solitaire: App rewrite, Added quick solve, New effects and sounds, Removed hacky canvas manipulation (by @doofy-dev)
- CLI-GUI Bridge: Add more symbols to keyboard (#222 by @Willy-JL)
- UL: Sub-GHz Bruteforcer: Add new protocols for existing dump option (by @xMasterX), use FW functions for top buttons (by @DerSkythe)
- UL: NRF24 Apps: Use string library compatible with OFW SDK (by @xMasterX)
- UL: W5500 Ethernet: Various fixes and improvements (by @xMasterX)
- OFW: SPI Mem Manager: Fixed UI rendering bug related to line breaks (by @portasynthinca3)
- OFW: USB/BT Remote: Mouse clicker option to click as fast as possible (by @sumukhj1219)
- CLI: Print plugin name on load fail (by @Willy-JL)
- NFC:
- NDEF Parser:
- Mifare Classic support (#265 by @luu176), protocol-agnostic rewrite and more improvements (#265 by @Willy-JL)
- SLIX support, parse even with TLV terminator omitted, parse empty NDEF TLVs (#278 by @Willy-JL)
- Decoding of URL-encoded URI characters (#267 by @jaylikesbunda)
- SmartPoster record support (#275 by @Willy-JL)
- Enable parsing NTAG I2C Plus 1k and 2k chips too (#237 by @RocketGod-git)
- Added 6 new Mifare Classic keys from Bulgaria Hotel (#216 by @z3r0l1nk)
- NDEF parser supports NTAG I2C Plus 1k and 2k chips too (by @RocketGod-git)
- UL: Add iq aparts hotel key (by @xMasterX)
- OFW/UL: Rename 'Detect Reader' to 'Extract MFC Keys' (by @bettse & @xMasterX)
- OFW: Plantain parser improvements (by @assasinfil)
- OFW: Plantain parser improvements (by @assasinfil) and fixes (by @mxcdoam)
- OFW: Moscow social card parser (by @assasinfil)
- OFW: Fixes and improvements to iso14443_4a listener and poller (by @RebornedBrain)
- OFW: Update BART station codes in Clipper plugin (by @ted-logan)
@@ -152,6 +178,7 @@
- Fallback SD format prompt when storage settings is unavailable (by @Willy-JL)
- OFW: Fix folder rename fails (by @portasynthinca3)
- About: Fix BLE stack version string (by @Willy-JL)
- RPC: Fixed apps not updating and staying at 100% (by @Willy-JL)
- OFW: Loader: Warn about missing SD card for main apps (by @Willy-JL)
- NFC:
- UL: Read Ultralight block by block (by @mishamyte)
@@ -166,6 +193,7 @@
- OFW: Prevent idle priority threads from potentially starving the FreeRTOS idle task (by @CookiePLMonster)
- OFW: Wait for RNG ready state and no errors before sampling (by @n1kolasM)
- OFW: A Lot of Fixes (by @skotopes)
- OFW: CLI: Add warning about stealth mode in vibro command (by @ivanbarsukov)
- OFW: Debug: Use proper hook for handle_exit in flipperapps (by @skotopes)
- OFW: API: Fix kerel typo in documentation (by @EntranceJew)
+1 -1
View File
@@ -34,7 +34,7 @@ We offer a powerful and easy-to-use application tailor-made for our firmware, th
- <ins><b>Interface:</b></ins> Tweak every part of your Flipper, from the desktop animations, to the main menu, lockscreen behavior, file browser, etc.
- <ins><b>Protocols:</b></ins> Configure SubGhz settings, add/remove custom frequencies, and setup which GPIO pins are used by different external modules.
- <ins><b>Protocols:</b></ins> Configure SubGhz settings, add/remove custom frequencies, extend SubGhz frequencies to 281-361, 378-481, 749-962 MHz and setup which GPIO pins are used by different external modules.
- <ins><b>Misc:</b></ins> Everything else that doesn't fit the previous categories. Change your Flipper's name, XP level, screen options, and configure the <a href="https://github.com/Z3BRO/Flipper-Zero-RGB-Backlight">RGB backlight</a>.
@@ -0,0 +1,16 @@
Filetype: Flipper SubGhz Key File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok270Async
Lat: 0.000000
Lon: 0.000000
Protocol: Vauno-EN8822C
Id: 64
Bit: 42
Data: 00 00 01 00 02 16 88 1D
Batt: 0
Hum: 81
Ts: 1728836876
Ch: 0
Btn: 255
Temp: 13.300000
@@ -0,0 +1,6 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok270Async
Protocol: RAW
RAW_Data: 588 -4136 546 -4150 578 -4158 546 -2096 578 -2092 592 -2068 592 -4126 578 -4144 570 -2064 580 -2092 594 -2102 564 -2168 582 -2078 582 -2112 544 -2102 572 -2096 572 -4130 574 -2114 552 -2090 594 -2064 590 -2104 560 -2094 566 -2090 564 -2092 580 -2098 566 -2096 570 -2164 578 -2090 546 -4140 578 -2104 568 -4138 578 -2078 550 -2086 594 -9092 592 -61848 200 -17582 66 -5072 130 -11038 100 -60 164 -8288 166 -2374 302 -94 166 -300 66 -18706 96 -25730 192 -66 158 -29578 64 -99962 128 -99858 96 -99908 130 -9892 164 -96 98 -89580 162 -106 76 -11000 196 -88450 506 -3900 522 -1900 522 -3878 500 -1936 482 -1958 488 -1938 500 -3906 462 -1966 482 -1950 500 -1924 502 -1958 474 -1940 480 -1960 520 -3858 512 -3878 524 -3888 464 -1968 482 -3910 478 -7792 518 -1912 502 -7802 478 -1962 476 -3902 510 -1938 500 -1938 480 -1954 504 -1936 488 -1954 476 -1960 476 -1948 478 -1964 466 -1994 446 -1970 464 -1972 480 -1954 474 -1954 482 -1970 474 -3934 446 -1958 466 -1978 480 -1956 468 -1960 472 -3900 516 -1948 478 -3908 494 -3878 508 -1938 502 -3896 482 -1950 480 -1938 514 -1924 516 -3884 484 -1940 502 -1942 480 -1958 502 -1930 506 -1938 480 -1934 516 -3860 510 -3912 488 -3888 504 -1940 486 -3876 506 -7800 526 -1928 476 -7790 512 -1942 508 -3876 486 -1918 518 -1962 482 -1954 482 -1930 498 -1958 470 -1936 490 -1962 484 -1960 476 -1932 520 -1938 490 -1928 508 -1952 478 -1940 480 -1958 500 -3892 498 -1930 484 -1960 474 -1936 496 -1934 514 -3890 508 -1906 522 -3882 478 -3916 484 -1932 506 -3888 512 -1928 502 -1928 502 -1938 492 -3920 490 -1924 510 -1928 498 -1934 480 -1954 488 -1972 462 -1964 478 -3896 484 -3902 476 -3912 484 -1944 476 -3916 498 -7784 510 -1930 504 -7796 498 -1936 518 -3864 492 -1964 482 -1950 474 -1962 470 -1954 490 -1960 478 -1952 482 -1934 520 -1934 482 -1948 482 -1944 486 -1970 448 -1976 500 -1936 480 -1964 474 -3902 484 -1960 480 -1952 482 -1962 474 -1950 486 -3914 448 -1984 450 -3916 520 -3872 490 -1942 480 -3938 448 -1970 464 -1972 482 -1964 484 -3910 484 -1938 476 -1950 516 -1926 470 -1970 514 -1906 500 -1950 506 -3876 510 -3850 524 -3896 486 -1934 518 -3870 506 -7798 512 -1912 512 -7798 516 -1918 478 -3924 458 -1956 472 -1954 506 -1936 512 -1930 474 -1968 482 -1930 504 -1940 486 -1958 482 -1960 486 -1936 486 -1936 494 -1934 516 -1912 510 -1962 474 -3882 504 -1956 478 -1938 510 -1926 500 -1924 514 -3920 450 -1970 478 -3904 488 -3896 502 -1942 486 -3912 476 -1926 508 -1940 486 -1962 474 -3920 484 -1930 512 -1944 470 -1970 470 -1948 502 -1936 478 -1962 482 -3914 484 -3878 502 -3906 472 -1968 488 -3894 498 -7774 496 -1948
@@ -691,6 +691,13 @@ MU_TEST(subghz_decoder_bresser_3ch_v0_test) {
"Test decoder " WS_PROTOCOL_BRESSER_3CH_NAME " v0 error\r\n");
}
MU_TEST(subghz_decoder_vauno_en8822c_test) {
mu_assert(
subghz_decoder_test(
EXT_PATH("unit_tests/subghz/vauno_en8822c.sub"), WS_PROTOCOL_VAUNO_EN8822C_NAME),
"Test decoder " WS_PROTOCOL_VAUNO_EN8822C_NAME " error\r\n");
}
//test encoders
MU_TEST(subghz_encoder_princeton_test) {
mu_assert(
@@ -916,6 +923,7 @@ MU_TEST_SUITE(subghz) {
MU_RUN_TEST(subghz_decoder_solight_te44_test);
MU_RUN_TEST(subghz_decoder_bresser_3ch_v1_test);
MU_RUN_TEST(subghz_decoder_bresser_3ch_v0_test);
MU_RUN_TEST(subghz_decoder_vauno_en8822c_test);
MU_RUN_TEST(subghz_encoder_princeton_test);
MU_RUN_TEST(subghz_encoder_came_test);
File diff suppressed because it is too large Load Diff
@@ -76,30 +76,6 @@ address: 80 02 20 00
command: 70 00 00 00
#
name: POWER
type: parsed
protocol: RC6
address: 00 00 00 00
command: 0C 00 00 00
#
name: SOURCE
type: parsed
protocol: RC6
address: 00 00 00 00
command: 38 00 00 00
#
name: PLAY
type: parsed
protocol: RC6
address: 00 00 00 00
command: 2C 00 00 00
#
name: STOP
type: parsed
protocol: RC6
address: 00 00 00 00
command: 31 00 00 00
#
name: POWER
type: raw
frequency: 38000
duty_cycle: 0.330000
+22 -1
View File
@@ -255,8 +255,29 @@ App(
)
App(
appid="ndef_parser",
appid="ndef_ul_parser",
apptype=FlipperAppType.PLUGIN,
cdefines=[("NDEF_PROTO", "NDEF_PROTO_UL")],
entry_point="ndef_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/ndef.c"],
)
App(
appid="ndef_mfc_parser",
apptype=FlipperAppType.PLUGIN,
cdefines=[("NDEF_PROTO", "NDEF_PROTO_MFC")],
entry_point="ndef_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/ndef.c"],
)
App(
appid="ndef_slix_parser",
apptype=FlipperAppType.PLUGIN,
cdefines=[("NDEF_PROTO", "NDEF_PROTO_SLIX")],
entry_point="ndef_plugin_ep",
targets=["f7"],
requires=["nfc"],
File diff suppressed because it is too large Load Diff
@@ -201,8 +201,9 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
size_t uid_len = 0;
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
bool parsed = false;
@@ -220,12 +221,30 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
if(key != cfg.keys[cfg.data_sector].a) break;
furi_string_printf(parsed_data, "\e#Plantain card\n");
const uint8_t* temp_ptr = &uid[0];
// UID is read from last to first byte
uint8_t card_number_tmp[uid_len];
if(uid_len == 4) {
for(size_t i = 0; i < 4; i++) {
card_number_tmp[i] = temp_ptr[3 - i];
}
} else if(uid_len == 7) {
for(size_t i = 0; i < 7; i++) {
card_number_tmp[i] = temp_ptr[6 - i];
}
} else {
break;
}
//UID is converted to a card number
uint64_t card_number = 0;
for(size_t i = 0; i < 7; i++) {
card_number = (card_number << 8) | data->block[0].data[6 - i];
for(size_t i = 0; i < uid_len; i++) {
card_number = (card_number << 8) | card_number_tmp[i];
}
// Print card number with 4-digit groups
// Print card number with 4-digit groups. "3" in "3078" denotes a ticket type "3 - full ticket", will differ on discounted cards.
furi_string_cat_printf(parsed_data, "Number: ");
FuriString* card_number_s = furi_string_alloc();
furi_string_cat_printf(card_number_s, "%lld", card_number);
@@ -237,6 +256,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_string_push_back(tmp_s, ' ');
}
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s));
// this works for 2K Plantain
if(data->type == MfClassicType1k) {
//balance
uint32_t balance = 0;
@@ -290,20 +310,70 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
last_payment_date.year,
last_payment_date.hour,
last_payment_date.minute);
//payment summ
//payment amount. This needs to be investigated more, currently it shows incorrect amount on some cards.
uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8];
furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100);
furi_string_free(card_number_s);
furi_string_free(tmp_s);
//This is for 4K Plantains.
} else if(data->type == MfClassicType4k) {
//balance
uint32_t balance = 0;
for(uint8_t i = 0; i < 4; i++) {
balance = (balance << 8) | data->block[16].data[3 - i];
}
furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100);
//trips
uint8_t trips_metro = data->block[36].data[0];
uint8_t trips_ground = data->block[36].data[1];
uint8_t trips_metro = data->block[21].data[0];
uint8_t trips_ground = data->block[21].data[1];
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
//trip time
uint32_t last_trip_timestamp = 0;
for(uint8_t i = 0; i < 3; i++) {
last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i];
}
DateTime last_trip = {0};
from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010);
furi_string_cat_printf(
parsed_data,
"Trip start: %02d.%02d.%04d %02d:%02d\n",
last_trip.day,
last_trip.month,
last_trip.year,
last_trip.hour,
last_trip.minute);
//validator
uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4];
furi_string_cat_printf(parsed_data, "Validator: %d\n", validator);
//tariff
uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6];
furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100);
//trips in metro
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
//trips on ground
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
//last payment
uint32_t last_payment_timestamp = 0;
for(uint8_t i = 0; i < 3; i++) {
last_payment_timestamp = (last_payment_timestamp << 8) |
data->block[18].data[4 - i];
}
DateTime last_payment_date = {0};
from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010);
furi_string_cat_printf(
parsed_data,
"Last pay: %02d.%02d.%04d %02d:%02d\n",
last_payment_date.day,
last_payment_date.month,
last_payment_date.year,
last_payment_date.hour,
last_payment_date.minute);
//payment amount
uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8];
furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100);
furi_string_free(card_number_s);
furi_string_free(tmp_s);
}
parsed = true;
} while(false);
@@ -40,7 +40,10 @@ static bool trt_parse(const NfcDevice* device, FuriString* parsed_data) {
const uint8_t* full_record_pointer = &data->page[FULL_SALE_TIME_STAMP_PAGE].data[0];
uint32_t latest_sale_record = bit_lib_get_bits_32(partial_record_pointer, 3, 20);
uint32_t latest_sale_full_record = bit_lib_get_bits_32(full_record_pointer, 0, 27);
if(latest_sale_record != (latest_sale_full_record & 0xFFFFF)) break;
if(latest_sale_record != (latest_sale_full_record & 0xFFFFF))
break; // check if the copy matches
if((latest_sale_record == 0) || (latest_sale_full_record == 0))
break; // prevent false positive
// Parse date
// yyy yyyymmmm dddddhhh hhnnnnnn
@@ -45,6 +45,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) {
furi_string_set(m->header, "PRNG Analysis");
break;
case MfClassicNestedPhaseDictAttack:
case MfClassicNestedPhaseDictAttackVerify:
case MfClassicNestedPhaseDictAttackResume:
furi_string_set(m->header, "Nested Dictionary");
break;
@@ -91,6 +92,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) {
float dict_progress = 0;
if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG ||
m->nested_phase == MfClassicNestedPhaseDictAttack ||
m->nested_phase == MfClassicNestedPhaseDictAttackVerify ||
m->nested_phase == MfClassicNestedPhaseDictAttackResume) {
// Phase: Nested dictionary attack
uint8_t target_sector =
@@ -89,6 +89,7 @@ typedef enum {
SetTypeAllmatic433,
SetTypeAllmatic868,
SetTypeCenturion433,
SetTypeMonarch433,
SetTypeSommer_FM_434,
SetTypeSommer_FM_868,
SetTypeSommer_FM238_434,
@@ -1,60 +1,61 @@
Filetype: Flipper SubGhz Keystore File
Version: 0
Encryption: 1
IV: 41 72 65 20 79 6F 75 20 61 20 46 75 72 72 79 3F
8B441920A9F30EA40001F37E0F3C0D72161467678511928D219A7DBC42F8D9B7
1ADE5B8541491C46674EECC05A1C4297CB28259954FD7C6DA0EAADE8D57773FC
0497A50135FA4468B49694974DAADEF2A9CC2B7BAFC8465E0AA58E940C085850
4BC77520AE09B4F6A5A5365B6B9C16CA53566E096159A98EF0AB6E7FE5C2DD1C
B674AC1674312E74471E44917F6FC049DAB48BBB65B84F14A4CD64223A49E93DC0C507B8E17BFFDCBB67B55F1B4091C6
FBBE4E4F703A9C1F17268F73EC00A3F76517A4470783363102D2B7647F0F2E7F
21BE8A51F32CE62DA6B5E207E627D224639877860C71933C4F54AC7BD742B265
74E3EA89CC7AA99855C1BC87734506413B882CF36D68C3329022415EB29D1360
567937E84052F230610B3A2FC26CA1BE32B9617F7554EF5E6E7CB35A76718ADA
4C34F7FE5D6EAE40B0EB6BAB7DE4F0D059C32E4F312F9C62F2C50C54C5486276
74C8656F8EA033C60F3183FA0923C9AF1EB6689FAACC39E6548ECE16E3312D39DCF45AC6EC70A70421FBFFA43D91E19E
275903678A81B6E62CD2BB4C50E93CB3E4B092E2894EBAA04AC4E0244E4D5BA9FCDFC6BCF845EA41DF0691BF2AEDF300
BFE52324D666ADEB3D33D94331376B33A3D31566331DE0F372548FE7255F8085
8E1B2E82B4C78360B3174741FB8031EE0DFEB479859BE8708F8AF40C5228BFDF
6014A4A9D9703DAC11DA6B93FB7A0E0E0856E9379148F3CC125D77BA4C3D4931
B8C22EA72B35E60A7A3DD02956D2375EE7C07E243F17F413CA4AC3A1445AA250
B545037B8798370A8638B7ADCBE9EEF748F73C89EB5E04B460057BAFA52B1D7DF29B009FBB3FA07CE87247D185BE4926
09AEA3223E4FFB06333425BA977403E7C2F9B5F121FDA8313E73FE98D0041425
E7A97F65883E4D3CDA02483738219E219EED0681C2BB140EF3E5D02230433D42
4AE67B9EA288183047A9498F58978E7C41BB8905533F42FF5B94E41AFEFF0EB4907A8C472B9E32F6D659CFA6C391179B
1F9752FBA7A7A00D518D91BA8A8C1ADEF2EDF6282A5AFB2E4332B4351CF67F18317D2BCFF2DA6B5216EA138A4CCBDB81
EDC508FE0D0E894B529C5E4AB82A7188072592BE9D4D9A3D2485822A9F4C5A67
780582645A71D3A9841B0E8F5E84A2285F783872102AA1A98A956B98AEA19421
37E2858B90868BCAA6B3198C3B205DC1413A9D378AF1671BEA3B8F47FCE1C5CE
229719C5AFE1E567B8BD5F07EFF89C30B11CB79C9C841FFE7B53404CA5422EDB
CDA16E59B8C24DBE95ABF978CAD4D04A4F124DED1965124F92AACA4C5AEB8721
5C61D5C655DD9EB78C4F8E965101F98D7899155D2379EF6553FA9DEA0302A232
7D3DCFDE942398244624C7ABFE08363404BA688EDA39D45A05BB6FB982BB9042
ECCB10BCB2A8CF2093C61634E9AFEE9B69EB4A7C8B7139061730B5FA869F65BF
7AE6C19ED3C97F45D1167FC9EF52CCE321EA002D31D8AEA94106A2F711626551
793A984F6EBD35253C319FAB671348A4C6843AEA7CCCD5A52ACA9BBE96484B5E
BB3ADBB5FD60F0FB01E5A8B1610D30A24975DC67ED1B70472E509218E213393E
BB971C6304723B9928F50FA2E5C30A48ED5527556EE1BA5A7EA75AB7A4ED4137
489579673025F7A351072EB8D5C7FC1DBF651AA6233FB01D99DAC4BDC0A60F2F
ACA000B78B091A9B2621A9FA1A50FA6A3B63DDDF7D966224837028F93D570C08
C1EB4EA179EC0437D5B0E72C47AAD06803F7619C53D3D2FEF38DD3C068118766B73202B75C534C6249312245218C9285
639C6F53600C0F01410361164FEF715DB8E08C68D68B8AE76CF2D09A0C6C4CA1
F5F2D77DD3AF60489312055EBA3B511F7D9E312F02465639FFF3E71A862BB9EB
3EC1F7B2C43646249C4D14B4B1B85B3416B8D3B0B8DA9E87BD6199DC27BD45C2
AA24DB7345317FE980C917909F364EAA13591A664E0893271E9DBCC250782EA5
32C18DBA479AD57BFF9FE7B7BD1022D4969D8E2EC99BC0799148809796E227D5
494B44697DACDFDBBFEB0FDBEF6E9B5D7291B6CB43B4C93088F3ED8632AF23B5
B79CC1A90C7AC36DA2F318BF69E6BB1FAC32C178453663069C0CF2C4AE77B211
A059D006EF98446BDF9E1F5E09641A641CA88348E79499D79BCFDE6646C716A0BE2253421858D327E796EB5237B6E2C4
9E90AE6138A3676E3E5058FF277331032CD6BBF114768104A33D684B0825C5A5
7477B55332666FC6B1279F5FDF84DCA01176498984809B74E17AB726F888000F
6A0300B90BC11127A031E62537468A1CA7A0775BE85ED12069074E26181FBA50
4DAF986F9A850EB18F0FE3B88B9796D80EEC3984C92F4F8B32D513A176CCAA9A
EB84517DCAA7D10A6E7C4D0A7995AE278AAF3FDE52DAE30C7FD3FB853C799369
0941FB8965604ABC852B94F29396783783632E4C80AEE665E42C233B6E7AA7D0
71FEF69734589BE3C0893A1584FC1C0A75A5D4BF88CFF323C8C68E0EEC06993C
790A26B77912EE07BABCDF478D8E492422B73465DC209E5DBC8E31F43AA2DA7448EAB6312AC1DC4FE3F20D7A2EC93968
CE0AA966F33B7AC9E9982D2241524F2FC7613DAAC6C5C768AAAE29A630B414CE
93FA632D945E18DE6358D4F443CB6F023C640407B7FAD6E8AF6EF15139B044E3
059ABA7A866A865159D4E0B3E30FA1900BAB2BAB82A1D1F9D74CAEC11FA95393
9C5A4CF1D49E0F741577BAB868575739AEBBDC15D7178C87A46EA3B3E7E856EFD815AD5F998778E8FBAA2832F387B967
IV: 46 75 72 72 79 20 63 68 65 63 6B 20 3F 3F 21 21
3FF98B52C38558ECDB26C3E86D118FAC9AF22EDDDE7649CB53726855CEBCCF9E
06B901C6E36E381B4A83554ED972288977F999C232106D337C3BE4A9A44608F8
8153E6CAF4B272BA0EA168B9F0F29CF02CEB33E4ABBD4D5C858D1ADBFF474A25
F61216CE3A7E500E0C9B2173F91C2E7B1BB7D7AA65D4150EAC28169116647DDD
A3ABC262415035A190447EC9C15CFD1AB1720560AB7C82D7447215342305299732776A9A6DEA4D359C52A23BFCE6B015
5784D0E77A55E1361F47A1F6CFFBFAF715CFCAD2BD502ED266AA86DC47F98541
E082BF64C75023F23FB333C53F2590F408FD932EB71DF4ED4D0E0EAF2AEBD488
EFF328889D57D9F4B9111918F9C9BC641645104223009842FFF7B6F73E24E5B7
BDCD1DBCBCA789C5A5C3623C0A287D791F1CACCBBD7D144E2EF1F92DA5513D90
6310362BE6ED7CB5E3F4CA84D3093DF620BABD2C9D419A2BA5AA05F241EE7592
4C6F97C553276D5403103A6BA6C4DBBE017A9E4159E6E5AC4C28ACD645DB1E8D4551CDC228A0457BEEF49179A081E1FD
861ECA071AEEF3854005FEC9BF22C7DB76D07B7930968314FFAC70995E28680E473F8A1A7E56E8776EDD1C6E2DD6DD26
55ADDFBE97BCB0FB43EDEB9CAFF9F32DD788A6306D3702BC2923DA5C69F5F0A5
AB838DA2B25547AA43DEB7FB40B15E289B9209057BE564E7CB78F0D5DCFC65FD
21BACE924E522AD0E97F0AAF64A9DB6A5F7BA09B3A1759AAFD4016F3ADD4353D
4B37D449E44BAE7F377EE5CE52E94882E42617417F77ECE9803D9435892167F2
E63BA93D98BB3EEEAD34B38C5F271FB777AEABD9E6A05597AE09815E578AA811900AE9F144FBEC7DBFDDEC87D1ED368B
7DF24DCB7D73F1638FFD22325463B23C101653EE770F086D323BE90868A7E267
18E533990C408061309323675116429652B3F4D228F00D704310E7AB26F6BAF4
26042EC21D73490D9E7F968EDCC4CECF3989E6E982427DEB7012478E214CED8797A9BEBC481C81E9646214809A409B4A
AC4169C26E402720F1E2A0AD690AE2CF708CE203898BC7102178A738C70F361718E794F0D0CB1A2F1938EF35CCA11887
6962CADB9BD1B86B779DF7F4D7E06E92EB42474C7FD9EA23879F9982D7127357
23AEC0F6F9FF529DDDCD1CDAA77F7B136CE2CCD3AC8B949954D5D3B2ECFA8CBC
C6A3A849BC4A8DB438065255BD387DCD77AD7E7FEB3B0E11B6D3A43279AB9087
CCB71F8BDC8B31F36D6141B3B57BF31B7EDF72B87593B7497871F5738A0B7E00
345FE0FAD3F60C017D9793406981878EA2226072D624208AC33682793B415C41
78AF1FCFDB780744708DBB547F7C3F095BA5FAFB34FA83BF4323B32829836ED1
C2F19F077E71E710D39F5F11C1FE55C2A3CD6F33384CD8603288765F266F1BDF
084B1A9565AFC88900D0FF25413E659E17AAB649DB2B7A9F0381CB0DF6D2B8E5
568CECD994676CF3D6B225DEF21D40787DCB41C101F52B0C5AA43ED66709D158
0742E0AAB6504AC63A9C59FBDD980BD4C4760EFF3E556C8A6241442CDDD23A74
37BB60EE11BEC7D25A607DE9B0B6D3EAD23321AFEB94995FAB42184A95D1CADA
42FD71B98A3BEE9CF372B03E158D51181BFAA9CF54F7300A93FE7665402B3D1F
9346B7D12346E264E84F91145EBF86C53DD0061E0FA6556FAE5F6FBB8CEB799E
430B0D7FC09C09051AEB667A370E6D9D9BDF0C0C553AD2791682D43CE5DC48B7
5BBB5129797BC07CDC1D25A8A919A637B9FFF2F920BD42D1721018ABF8D34959AF877AFF450AD91548184E1A0D991CC8
0BC8D3E1C2D9A8FB445FC55564471007A28C09C9CE602203176F1BFF02AC6328
1C4CA912791BCD9CB231C64AB479AF240D02EC9F431D7C479B9A172E5B97F5CD
15A9CDF17E72255DE0942C09E67CD251C3D465246845F9C0B97A7EFEE4AF9110
F95543395BFF39B0A093AC80D0EF1BEFA218A3769D074250414D1357105A4D34
138D433824E691C6B67A08CF1DDE55FBCA2D65AC0B7D9320EA1FDCB1742B11BF
3BE3A385F1C0B8922C4E23EFF6912748DF715A4546CECB5E8972C1D1E47D0D3A
9ECA2554ED36700326F2E5140C434ADA8DFF55A53382F19541E9AAE45DA5CBF2
5C75D528678AF199E191C49F310913F401014F97EEA5FA507C7310B48A98FDE3297EA398B08959951FF99EDDB64C5E0D
22A9C66645B3944925A496D9F2312429CC787B6314948B482EAD9360124F59DA
DA3A8571664DBBFC1DE97B53E7C141554A2FBCEBA980696D32409CC5ADB7FC41
20A52AABC518FCC2FC75AE3F5CC7C4838AA4973111DA696B890D884A18098D91
EBB7163F580A1A5D26F12FBE650A227791193BA9AFCE277584B171F2FE1C77CF
86369EE5277CB81B9417B6232F8D994FAEAB34D0D5363B143257C62B10CACAFD
2E2EAB32891E172A3C31D434703480E69793435BB198E6AA06AA066EE8234D85
745FC576D77C41BAEFF15A822E6B4058A485A2CAA0A3B283928D17AC02299AD9
1FF8D49F2F7D785D64B6FE365CD9C2BD958E9527F66BB8A85C9AEBC73ECFE064
3CFB77F3E274C1EB2772CDFA7B5B17255C2554198BE60C058A3405AEC644FABC5AAECC8F9C7F4A4E5B2D5252E8C62628
AE514C44B55A1A4744E1106FD226C587D1B71CA7B5DCF010265D769E22012866
2B2D787A4B0F30CAB9CD3DBC7686165637F091B31745CAB53B369A804F76F9EE
EA4279C80F0B4AC0B32AB9E8B8CFB9C25FF81840BEE65B2160F85E56FA576C48
E41D853750D68643E929F94F46BCDC050040935883E9A0C45BB238CDE06340DD745BDA7D6C16AE2B028E073EDDC0FC49
@@ -28,6 +28,7 @@ static const char* submenu_names[SetTypeMAX] = {
[SetTypeAllmatic433] = "KL: Allmatic 433MHz",
[SetTypeAllmatic868] = "KL: Allmatic 868MHz",
[SetTypeCenturion433] = "KL: Centurion 433MHz",
[SetTypeMonarch433] = "KL: Monarch 433MHz",
[SetTypeSommer_FM_434] = "KL: Sommer 434MHz",
[SetTypeSommer_FM_868] = "KL: Sommer 868MHz",
[SetTypeSommer_FM238_434] = "KL: Sommer fm2 434Mhz",
@@ -417,6 +418,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
.keeloq.cnt = 0x03,
.keeloq.manuf = "Centurion"};
break;
case SetTypeMonarch433:
gen_info = (GenInfo){
.type = GenKeeloq,
.mod = "AM650",
.freq = 433920000,
.keeloq.serial = (key & 0x0000FFFF),
.keeloq.btn = 0x0A,
.keeloq.cnt = 0x03,
.keeloq.manuf = "Monarch"};
break;
case SetTypeElmesElectronic:
gen_info = (GenInfo){
.type = GenKeeloq,
+13 -1
View File
@@ -8,6 +8,7 @@
#include <task_control_block.h>
#include <time.h>
#include <notification/notification_messages.h>
#include <notification/notification_app.h>
#include <loader/loader.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
@@ -487,13 +488,24 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) {
void cli_command_vibro(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
UNUSED(context);
if(!furi_string_cmp(args, "0")) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message_block(notification, &sequence_reset_vibro);
furi_record_close(RECORD_NOTIFICATION);
} else if(!furi_string_cmp(args, "1")) {
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)) {
printf("Flipper is in stealth mode. Unmute the device to control vibration.");
return;
}
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message_block(notification, &sequence_set_vibro_on);
if(notification->settings.vibro_on) {
notification_message_block(notification, &sequence_set_vibro_on);
} else {
printf("Vibro is disabled in settings. Enable it to control vibration.");
}
furi_record_close(RECORD_NOTIFICATION);
} else {
cli_print_usage("vibro", "<1|0>", furi_string_get_cstr(args));
@@ -726,6 +726,51 @@ static bool byte_input_view_input_callback(InputEvent* event, void* context) {
return consumed;
}
static bool byte_input_view_ascii_callback(AsciiEvent* event, void* context) {
ByteInput* byte_input = context;
furi_assert(byte_input);
switch(event->value) {
case AsciiValueDC3: // Right
case AsciiValueDC4: // Left
with_view_model(
byte_input->view,
ByteInputModel * model,
{
if(event->value == AsciiValueDC3) {
byte_input_inc_selected_byte_mini(model);
} else {
byte_input_dec_selected_byte_mini(model);
}
},
true);
return true;
default: // Look in keyboard
for(size_t r = 0; r < keyboard_row_count; r++) {
const ByteInputKey* row = byte_input_get_row(r);
uint8_t size = byte_input_get_row_size(r);
for(size_t key = 0; key < size; key++) {
char value = row[key].value;
if(event->value == value) {
with_view_model(
byte_input->view,
ByteInputModel * model,
{
model->selected_row = r;
model->selected_column = key;
byte_input_handle_ok(model);
},
true);
return true;
}
}
}
break;
}
return false;
}
/** Reset all input-related data in model
*
* @param model The model
@@ -747,6 +792,7 @@ ByteInput* byte_input_alloc(void) {
view_allocate_model(byte_input->view, ViewModelTypeLocking, sizeof(ByteInputModel));
view_set_draw_callback(byte_input->view, byte_input_view_draw_callback);
view_set_input_callback(byte_input->view, byte_input_view_input_callback);
view_set_ascii_callback(byte_input->view, byte_input_view_ascii_callback);
with_view_model(
byte_input->view,
+57 -87
View File
@@ -2,7 +2,6 @@
#include <gui/elements.h>
#include <assets_icons.h>
#include <furi.h>
#include <core/dangerous_defines.h>
struct TextInput {
View* view;
@@ -38,10 +37,10 @@ typedef struct {
FuriString* validator_text;
bool validator_message_visible;
char extra_symbols[9];
bool illegal_symbols;
bool cursor_select;
size_t cursor_pos;
uint8_t selected_keyboard;
size_t cursor_pos;
} TextInputModel;
static const uint8_t keyboard_origin_x = 1;
@@ -102,7 +101,7 @@ static const TextInputKey keyboard_keys_row_3[] = {
{'9', 120, 32},
};
static TextInputKey symbol_keyboard_keys_row_1[] = {
static const TextInputKey symbol_keyboard_keys_row_1[] = {
{'!', 2, 8},
{'@', 12, 8},
{'#', 22, 8},
@@ -118,7 +117,7 @@ static TextInputKey symbol_keyboard_keys_row_1[] = {
{'3', 120, 8},
};
static TextInputKey symbol_keyboard_keys_row_2[] = {
static const TextInputKey symbol_keyboard_keys_row_2[] = {
{'~', 2, 20},
{'+', 12, 20},
{'-', 22, 20},
@@ -133,7 +132,7 @@ static TextInputKey symbol_keyboard_keys_row_2[] = {
{'6', 120, 20},
};
static TextInputKey symbol_keyboard_keys_row_3[] = {
static const TextInputKey symbol_keyboard_keys_row_3[] = {
{SWITCH_KEYBOARD_KEY, 0, 23},
{'.', 15, 32},
{',', 29, 32},
@@ -241,6 +240,33 @@ static char char_to_uppercase(const char letter) {
}
}
static char char_to_illegal_symbol(char original) {
switch(original) {
default:
return original;
case '0':
return '_';
case '1':
return '<';
case '2':
return '>';
case '3':
return ':';
case '4':
return '"';
case '5':
return '/';
case '6':
return '\\';
case '7':
return '|';
case '8':
return '?';
case '9':
return '*';
}
}
static void text_input_backspace_cb(TextInputModel* model) {
if(model->clear_default_text) {
model->text_buffer[0] = 0;
@@ -356,7 +382,8 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) {
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y -
(glyph == '_' || char_is_lowercase(glyph)),
glyph);
(symbols && model->illegal_symbols) ? char_to_illegal_symbol(glyph) :
glyph);
}
}
}
@@ -479,6 +506,10 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, I
model->selected_keyboard != symbol_keyboard.keyboard_index) {
selected = char_to_uppercase(selected);
}
if(model->selected_keyboard == symbol_keyboard.keyboard_index &&
model->illegal_symbols) {
selected = char_to_illegal_symbol(selected);
}
if(model->clear_default_text) {
model->text_buffer[0] = selected;
model->text_buffer[1] = '\0';
@@ -622,35 +653,34 @@ static bool text_input_view_ascii_callback(AsciiEvent* event, void* context) {
true);
return true;
default: // Look in keyboards
TextInputModel* model = view_get_model(text_input->view);
uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
bool uppercase = model->clear_default_text || text_length == 0;
for(size_t k = 0; k < keyboard_count; k++) {
bool symbols = k == symbol_keyboard.keyboard_index;
const Keyboard* keyboard = keyboards[k];
for(size_t r = 0; r < keyboard_row_count; r++) {
const TextInputKey* row = get_row(keyboard, r);
uint8_t size = get_row_size(keyboard, r);
for(size_t key = 0; key < size; key++) {
char lower = row[key].text;
char upper = char_to_uppercase(lower);
if(symbols && model->illegal_symbols) lower = char_to_illegal_symbol(lower);
char upper = symbols ? lower : char_to_uppercase(lower);
if(event->value == lower || event->value == upper) {
with_view_model(
text_input->view,
TextInputModel * model,
{
model->cursor_select = false;
model->selected_keyboard = k;
model->selected_row = r;
model->selected_column = key;
bool shift =
(event->value == upper) !=
(model->clear_default_text || strlen(model->text_buffer) == 0);
text_input_handle_ok(
text_input, model, shift ? InputTypeLong : InputTypeShort);
},
true);
model->cursor_select = false;
model->selected_keyboard = k;
model->selected_row = r;
model->selected_column = key;
bool shift = (event->value == upper) != uppercase && !symbols;
text_input_handle_ok(
text_input, model, shift ? InputTypeLong : InputTypeShort);
view_commit_model(text_input->view, true);
return true;
}
}
}
}
view_commit_model(text_input->view, false);
break;
}
@@ -668,33 +698,6 @@ void text_input_timer_callback(void* context) {
true);
}
static void reset_extra_symbols(TextInputModel* model) {
memset(model->extra_symbols, 0, sizeof(model->extra_symbols));
size_t symbol = 0;
for(size_t row = 0; row < 3; row++) {
size_t size = get_row_size(&symbol_keyboard, row) - 3;
for(size_t i = 0; i < 3; i++) {
FURI_CONST_ASSIGN_(
char, get_row(&symbol_keyboard, row)[size++].text, '1' + (symbol++));
}
}
FURI_CONST_ASSIGN_(
char, get_row(&symbol_keyboard, 0)[get_row_size(&symbol_keyboard, 0) - 4].text, '0');
}
static void apply_extra_symbols(TextInputModel* model) {
size_t symbol = 0;
for(size_t row = 0; row < 3; row++) {
size_t size = get_row_size(&symbol_keyboard, row) - 3;
for(size_t i = 0; i < 3; i++) {
FURI_CONST_ASSIGN_(
char, get_row(&symbol_keyboard, row)[size++].text, model->extra_symbols[symbol++]);
}
}
FURI_CONST_ASSIGN_(
char, get_row(&symbol_keyboard, 0)[get_row_size(&symbol_keyboard, 0) - 4].text, '_');
}
TextInput* text_input_alloc(void) {
TextInput* text_input = malloc(sizeof(TextInput));
text_input->view = view_alloc();
@@ -712,7 +715,7 @@ TextInput* text_input_alloc(void) {
{
model->validator_text = furi_string_alloc();
model->minimum_length = 1;
reset_extra_symbols(model);
model->illegal_symbols = false;
model->cursor_pos = 0;
model->cursor_select = false;
},
@@ -752,7 +755,7 @@ void text_input_reset(TextInput* text_input) {
model->selected_column = 0;
model->selected_keyboard = 0;
model->minimum_length = 1;
reset_extra_symbols(model);
model->illegal_symbols = false;
model->clear_default_text = false;
model->cursor_pos = 0;
model->cursor_select = false;
@@ -813,43 +816,10 @@ void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length)
true);
}
void text_input_add_extra_symbol(TextInput* text_input, char symbol) {
furi_check(text_input);
if(!symbol) return;
with_view_model(
text_input->view,
TextInputModel * model,
{
for(size_t i = 0; i < sizeof(model->extra_symbols); i++) {
if(!model->extra_symbols[i]) {
model->extra_symbols[i] = symbol;
apply_extra_symbols(model);
break;
}
}
},
true);
}
void text_input_add_illegal_symbols(TextInput* text_input) {
void text_input_show_illegal_symbols(TextInput* text_input, bool show) {
furi_check(text_input);
with_view_model(
text_input->view,
TextInputModel * model,
{
size_t i = 0;
model->extra_symbols[i++] = '<';
model->extra_symbols[i++] = '>';
model->extra_symbols[i++] = ':';
model->extra_symbols[i++] = '"';
model->extra_symbols[i++] = '/';
model->extra_symbols[i++] = '\\';
model->extra_symbols[i++] = '|';
model->extra_symbols[i++] = '?';
model->extra_symbols[i++] = '*';
apply_extra_symbols(model);
},
true);
text_input->view, TextInputModel * model, { model->illegal_symbols = show; }, true);
}
void text_input_set_validator(
@@ -77,11 +77,12 @@ void text_input_set_validator(
TextInputValidatorCallback callback,
void* callback_context);
// Add up to 9 extra characters for symbol keyboard
void text_input_add_extra_symbol(TextInput* text_input, char symbol);
// Add the 9 predefined illegal (windows) symbols <>:"/\|?*
void text_input_add_illegal_symbols(TextInput* text_input);
/**
* @brief Show the 9 illegal (windows) symbols <>:"/\|?* in the symbols keyboard instead of the numbers
* @param [in] text_input TextInput
* @param [in] show Whether to show the illegal symbols or not
*/
void text_input_show_illegal_symbols(TextInput* text_input, bool show);
TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input);
+34
View File
@@ -269,6 +269,37 @@ static void
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}
static void
rpc_system_gui_send_ascii_event_request_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_gui_send_ascii_event_request_tag);
furi_assert(context);
FURI_LOG_D(TAG, "SendAsciiEvent");
RpcGuiSystem* rpc_gui = context;
RpcSession* session = rpc_gui->session;
furi_assert(session);
bool is_valid = (request->content.gui_send_ascii_event_request.value <= 0xFF);
if(!is_valid) {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
return;
}
AsciiEvent event = {
.value = request->content.gui_send_ascii_event_request.value,
};
// Submit event
FuriPubSub* ascii_events = furi_record_open(RECORD_ASCII_EVENTS);
furi_pubsub_publish(ascii_events, &event);
furi_record_close(RECORD_ASCII_EVENTS);
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}
static void rpc_system_gui_virtual_display_render_callback(Canvas* canvas, void* context) {
furi_assert(canvas);
furi_assert(context);
@@ -459,6 +490,9 @@ void* rpc_system_gui_alloc(RpcSession* session) {
rpc_handler.message_handler = rpc_system_gui_send_input_event_request_process;
rpc_add_handler(session, PB_Main_gui_send_input_event_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gui_send_ascii_event_request_process;
rpc_add_handler(session, PB_Main_gui_send_ascii_event_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gui_start_virtual_display_process;
rpc_add_handler(session, PB_Main_gui_start_virtual_display_request_tag, &rpc_handler);
+1 -1
View File
@@ -629,7 +629,7 @@ static void rpc_system_storage_rename_process(const PB_Main* request, void* cont
rpc_system_storage_reset_state(rpc_storage, session, true);
if(path_contains_only_ascii(request->content.storage_rename_request.new_path)) {
FS_Error error = storage_common_rename_safe(
FS_Error error = storage_common_rename(
rpc_storage->api,
request->content.storage_rename_request.old_path,
request->content.storage_rename_request.new_path);
+23 -7
View File
@@ -53,7 +53,7 @@ App(
appid="js_gui",
apptype=FlipperAppType.PLUGIN,
entry_point="js_gui_ep",
requires=["js_app", "js_event_loop"],
requires=["js_app"],
sources=["modules/js_gui/js_gui.c", "modules/js_gui/js_gui_api_table.cpp"],
)
@@ -61,7 +61,7 @@ App(
appid="js_gui__loading",
apptype=FlipperAppType.PLUGIN,
entry_point="js_view_loading_ep",
requires=["js_app", "js_gui", "js_event_loop"],
requires=["js_app"],
sources=["modules/js_gui/loading.c"],
)
@@ -69,7 +69,7 @@ App(
appid="js_gui__empty_screen",
apptype=FlipperAppType.PLUGIN,
entry_point="js_view_empty_screen_ep",
requires=["js_app", "js_gui", "js_event_loop"],
requires=["js_app"],
sources=["modules/js_gui/empty_screen.c"],
)
@@ -77,7 +77,7 @@ App(
appid="js_gui__submenu",
apptype=FlipperAppType.PLUGIN,
entry_point="js_view_submenu_ep",
requires=["js_app", "js_gui"],
requires=["js_app"],
sources=["modules/js_gui/submenu.c"],
)
@@ -85,7 +85,7 @@ App(
appid="js_gui__text_input",
apptype=FlipperAppType.PLUGIN,
entry_point="js_view_text_input_ep",
requires=["js_app", "js_gui", "js_event_loop"],
requires=["js_app"],
sources=["modules/js_gui/text_input.c"],
)
@@ -93,7 +93,7 @@ App(
appid="js_gui__byte_input",
apptype=FlipperAppType.PLUGIN,
entry_point="js_view_byte_input_ep",
requires=["js_app", "js_gui", "js_event_loop"],
requires=["js_app"],
sources=["modules/js_gui/byte_input.c"],
)
@@ -149,7 +149,7 @@ App(
appid="js_gpio",
apptype=FlipperAppType.PLUGIN,
entry_point="js_gpio_ep",
requires=["js_app", "js_event_loop"],
requires=["js_app"],
sources=["modules/js_gpio.c"],
)
@@ -208,3 +208,19 @@ App(
requires=["js_app"],
sources=["modules/js_usbdisk/*.c"],
)
App(
appid="js_i2c",
apptype=FlipperAppType.PLUGIN,
entry_point="js_i2c_ep",
requires=["js_app"],
sources=["modules/js_i2c.c"],
)
App(
appid="js_spi",
apptype=FlipperAppType.PLUGIN,
entry_point="js_spi_ep",
requires=["js_app"],
sources=["modules/js_spi.c"],
)
@@ -45,7 +45,7 @@ eventLoop.subscribe(views.dialog.input, function (_sub, button, eventLoop, gui)
badusb.println("Flipper Model: " + flipper.getModel());
badusb.println("Flipper Name: " + flipper.getName());
badusb.println("Battery level: " + toString(flipper.getBatteryCharge()) + "%");
badusb.println("Battery level: " + flipper.getBatteryCharge().toString() + "%");
// Alt+Numpad method works only on Windows!!!
badusb.altPrintln("This was printed with Alt+Numpad method!");
@@ -45,7 +45,7 @@ function sendRandomModelAdvertisement() {
blebeacon.start();
print("Sent data for model ID " + toString(model));
print("Sent data for model ID " + model.toString());
currentIndex = (currentIndex + 1) % watchValues.length;
@@ -17,18 +17,16 @@ let views = {
empty: emptyView.make(),
keyboard: textInputView.makeWith({
header: "Enter your name",
defaultText: flipper.getName(),
defaultTextClear: true,
// Props for makeWith() are passed in reverse order, so maxLength must be after defaultText
minLength: 0,
maxLength: 32,
defaultText: flipper.getName(),
defaultTextClear: true,
}),
helloDialog: dialogView.make(),
bytekb: byteInputView.makeWith({
header: "Look ma, I'm a header text!",
defaultData: Uint8Array([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]),
// Props for makeWith() are passed in reverse order, so length must be after defaultData
length: 8,
defaultData: Uint8Array([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]),
}),
longText: textBoxView.makeWith({
text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.",
@@ -97,7 +95,7 @@ eventLoop.subscribe(views.bytekb.input, function (_sub, data, gui, views) {
let data_view = Uint8Array(data);
let text = "0x";
for (let i = 0; i < data_view.length; i++) {
text += toString(data_view[i], 16);
text += data_view[i].toString(16);
}
views.helloDialog.set("text", "You typed:\n" + text);
views.helloDialog.set("center", "Cool!");
@@ -0,0 +1,48 @@
// Connect an 24C32N EEPROM to the I2C bus of the board. SDA=pin 15, SCL=pin 16, VCC=pin 9, GND=pin 8.
let i2c = require("i2c");
function i2c_find_first_device() {
let addr = -1;
for (let try_addr = 0; try_addr !== 0xff; try_addr++) {
if (i2c.isDeviceReady(try_addr, 5)) {
addr = try_addr;
break;
}
}
return addr;
}
let addr = i2c_find_first_device();
if (addr === -1) {
print("I2C device not found");
print("Please connect a 24C32N EEPROM I2C device to the Flipper Zero.");
print("SDA=pin 15, SCL=pin 16, VCC=pin 9, GND=pin 8.");
} else {
print("I2C device found at address: " + addr.toString(16));
delay(1000);
// first two bytes are the start address (0x0000)
// the remaining bytes are the data to store.
// can also use Uint8Array([0x00, 0x00, ...]) as write parameter
i2c.write(addr, [0x00, 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47]);
while (i2c.isDeviceReady(addr, 9) === false) {
print("Waiting for device to be ready...");
}
// write the address to read from (we start at address 0x0001)
// read 3 bytes - 0x42, 0x43, 0x44
let data_buf = i2c.writeRead(addr, [0x00, 0x01], 3, 100);
let data = Uint8Array(data_buf);
print("Read bytes: " + data.length.toString());
for (let i = 0; i < data.length; i++) {
print("data[" + i.toString() + "] = " + data[i].toString(16));
}
// read two more bytes (0x45, 0x46) from current address
data_buf = i2c.read(addr, 2);
data = Uint8Array(data_buf);
print("Read bytes: " + data.length.toString());
for (let i = 0; i < data.length; i++) {
print("data[" + i.toString() + "] = " + data[i].toString(16));
}
}
@@ -1,3 +1,3 @@
let math = load(__dirpath + "/load_api.js");
let math = load(__dirname + "/load_api.js");
let result = math.add(5, 10);
print(result);
@@ -1,8 +1,8 @@
let storage = require("storage");
print("script has __dirpath of" + __dirpath);
print("script has __filepath of" + __filepath);
if (storage.fileExists(__dirpath + "/math.js")) {
print("script has __dirname of" + __dirname);
print("script has __filename of" + __filename);
if (storage.fileExists(__dirname + "/math.js")) {
print("math.js exist here.");
} else {
print("math.js does not exist here.");
@@ -0,0 +1,88 @@
// Connect a w25q32 SPI device to the Flipper Zero.
// D1=pin 2 (MOSI), SLK=pin 5 (SCK), GND=pin 8 (GND), D0=pin 3 (MISO), CS=pin 4 (CS), VCC=pin 9 (3V3)
let spi = require("spi");
// Display textbox so user can scroll to see all output.
let eventLoop = require("event_loop");
let gui = require("gui");
let text = "SPI demo\n";
let textBox = require("gui/text_box").makeWith({
focus: "end",
font: "text",
text: text,
});
function addText(add) {
text += add;
textBox.set("text", text);
}
gui.viewDispatcher.switchTo(textBox);
// writeRead returns a buffer the same length as the input buffer.
// We send 6 bytes of data, starting with 0x90, which is the command to read the manufacturer ID.
// Can also use Uint8Array([0x90, 0x00, ...]) as write parameter
// Optional timeout parameter in ms. We set to 100ms.
let data_buf = spi.writeRead([0x90, 0x0, 0x0, 0x0, 0x0, 0x0], 100);
let data = Uint8Array(data_buf);
if (data.length === 6) {
if (data[4] === 0xEF) {
addText("Found Winbond device\n");
if (data[5] === 0x15) {
addText("Device ID: W25Q32\n");
} else {
addText("Unknown device ID: " + data[5].toString(16) + "\n");
}
} else if (data[4] === 0x0) {
addText("Be sure Winbond W25Q32 is connected to Flipper Zero SPI pins.\n");
} else {
addText("Unknown device. Manufacturer ID: " + data[4].toString(16) + "\n");
}
}
addText("\nReading JEDEC ID\n");
// Acquire the SPI bus. Multiple calls will happen with Chip Select (CS) held low.
spi.acquire();
// Send command (0x9F) to read JEDEC ID.
// Can also use Uint8Array([0x9F]) as write parameter
// Note: you can pass an optional timeout parameter in milliseconds.
spi.write([0x9F]);
// Request 3 bytes of data.
// Note: you can pass an optional timeout parameter in milliseconds.
data_buf = spi.read(3);
// Release the SPI bus as soon as we are done with the set of SPI commands.
spi.release();
data = Uint8Array(data_buf);
addText("JEDEC MF ID: " + data[0].toString(16) + "\n");
addText("JEDEC Memory Type: " + data[1].toString(16) + "\n");
addText("JEDEC Capacity ID: " + data[2].toString(16) + "\n");
if (data[0] === 0xEF) {
addText("Found Winbond device\n");
}
let capacity = data[1] << 8 | data[2];
if (capacity === 0x4016) {
addText("Device: W25Q32\n");
} else if (capacity === 0x4015) {
addText("Device: W25Q16\n");
} else if (capacity === 0x4014) {
addText("Device: W25Q80\n");
} else {
addText("Unknown device\n");
}
// Wait for user to close the app
eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, eventLoop) {
eventLoop.stop();
}, eventLoop);
// This script has no interaction, only textbox, so event loop doesn't need to be running all the time
// We run it at the end to accept input for the back button press to quit
// But before that, user sees a textbox and pressing back has no effect
// This is fine because it allows simpler logic and the code above takes no time at all to run
eventLoop.run();
@@ -1,6 +1,6 @@
let sampleText = "Hello, World!";
let lengthOfText = "Length of text: " + toString(sampleText.length);
let lengthOfText = "Length of text: " + sampleText.length.toString();
print(lengthOfText);
let start = 7;
@@ -9,11 +9,11 @@ let substringResult = sampleText.slice(start, end);
print(substringResult);
let searchStr = "World";
let result2 = toString(sampleText.indexOf(searchStr));
let result2 = sampleText.indexOf(searchStr).toString();
print(result2);
let upperCaseText = "Text in upper case: " + toUpperCase(sampleText);
let upperCaseText = "Text in upper case: " + sampleText.toUpperCase();
print(upperCaseText);
let lowerCaseText = "Text in lower case: " + toLowerCase(sampleText);
let lowerCaseText = "Text in lower case: " + sampleText.toLowerCase();
print(lowerCaseText);
@@ -6,7 +6,7 @@ while (1) {
if (rx_data !== undefined) {
serial.write(rx_data);
let data_view = Uint8Array(rx_data);
print("0x" + toString(data_view[0], 16));
print("0x" + data_view[0].toString(16));
}
}
@@ -24,11 +24,10 @@ let views = {
}),
textInput: textInput.makeWith({
header: "Type JavaScript Code:",
defaultText: "2+2",
defaultTextClear: true,
// Props for makeWith() are passed in reverse order, so maxLength must be after defaultText
minLength: 0,
maxLength: 256,
defaultText: "2+2",
defaultTextClear: true,
}),
loading: loading.make(),
};
@@ -42,7 +41,7 @@ eventLoop.subscribe(views.dialog.input, function (_sub, button, gui, views) {
eventLoop.subscribe(views.textInput.input, function (_sub, text, gui, views, ctx) {
gui.viewDispatcher.switchTo(views.loading);
let path = ctx.tmpTemplate + toString(ctx.tmpNumber++);
let path = ctx.tmpTemplate + (ctx.tmpNumber++).toString();
let file = storage.openFile(path, "w", "create_always");
file.write(text);
file.close();
@@ -58,7 +57,7 @@ eventLoop.subscribe(views.textInput.input, function (_sub, text, gui, views, ctx
} else if (typeof result === "string") {
result = "'" + result + "'";
} else if (typeof result === "number") {
result = toString(result);
result = result.toString();
} else if (typeof result === "bigint") { // mJS doesn't support BigInt() but might aswell check
result = "bigint";
} else if (typeof result === "boolean") {
+16 -89
View File
@@ -1,6 +1,7 @@
#include <common/cs_dbg.h>
#include <toolbox/path.h>
#include <toolbox/stream/file_stream.h>
#include <toolbox/strint.h>
#include <loader/firmware_api/firmware_api.h>
#include <flipper_application/api_hashtable/api_hashtable.h>
#include <flipper_application/plugins/composite_resolver.h>
@@ -195,98 +196,27 @@ static void js_require(struct mjs* mjs) {
mjs_return(mjs, req_object);
}
static void js_global_to_string(struct mjs* mjs) {
int base = 10;
if(mjs_nargs(mjs) > 1) base = mjs_get_int(mjs, mjs_arg(mjs, 1));
double num = mjs_get_double(mjs, mjs_arg(mjs, 0));
char tmp_str[] = "-2147483648";
itoa(num, tmp_str, base);
mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true);
mjs_return(mjs, ret);
}
static void js_parse_int(struct mjs* mjs) {
mjs_val_t arg = mjs_arg(mjs, 0);
if(!mjs_is_string(arg)) {
mjs_return(mjs, mjs_mk_number(mjs, 0));
return;
}
size_t str_len = 0;
const char* str = mjs_get_string(mjs, &arg, &str_len);
if((str_len == 0) || (str == NULL)) {
mjs_return(mjs, mjs_mk_number(mjs, 0));
return;
}
const char* str;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_STR(&str));
int32_t num = 0;
int32_t sign = 1;
size_t i = 0;
if(str[0] == '-') {
sign = -1;
i = 1;
} else if(str[0] == '+') {
i = 1;
}
for(; i < str_len; i++) {
if(str[i] >= '0' && str[i] <= '9') {
num = num * 10 + (str[i] - '0');
} else {
break;
int32_t base = 10;
if(mjs_nargs(mjs) >= 2) {
mjs_val_t base_arg = mjs_arg(mjs, 1);
if(!mjs_is_number(base_arg)) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Base must be a number");
mjs_return(mjs, MJS_UNDEFINED);
}
base = mjs_get_int(mjs, base_arg);
}
num *= sign;
int32_t num;
if(strint_to_int32(str, NULL, &num, base) != StrintParseNoError) {
num = 0;
}
mjs_return(mjs, mjs_mk_number(mjs, num));
}
static void js_to_upper_case(struct mjs* mjs) {
mjs_val_t arg0 = mjs_arg(mjs, 0);
size_t str_len;
const char* str = NULL;
if(mjs_is_string(arg0)) {
str = mjs_get_string(mjs, &arg0, &str_len);
}
if(!str) {
mjs_return(mjs, MJS_UNDEFINED);
return;
}
char* upperStr = strdup(str);
for(size_t i = 0; i < str_len; i++) {
upperStr[i] = toupper(upperStr[i]);
}
mjs_val_t resultStr = mjs_mk_string(mjs, upperStr, ~0, true);
free(upperStr);
mjs_return(mjs, resultStr);
}
static void js_to_lower_case(struct mjs* mjs) {
mjs_val_t arg0 = mjs_arg(mjs, 0);
size_t str_len;
const char* str = NULL;
if(mjs_is_string(arg0)) {
str = mjs_get_string(mjs, &arg0, &str_len);
}
if(!str) {
mjs_return(mjs, MJS_UNDEFINED);
return;
}
char* lowerStr = strdup(str);
for(size_t i = 0; i < str_len; i++) {
lowerStr[i] = tolower(lowerStr[i]);
}
mjs_val_t resultStr = mjs_mk_string(mjs, lowerStr, ~0, true);
free(lowerStr);
mjs_return(mjs, resultStr);
}
#ifdef JS_DEBUG
static void js_dump_write_callback(void* ctx, const char* format, ...) {
File* file = ctx;
@@ -320,26 +250,23 @@ static int32_t js_thread(void* arg) {
mjs_set(
mjs,
global,
"__filepath",
"__filename",
~0,
mjs_mk_string(
mjs, furi_string_get_cstr(worker->path), furi_string_size(worker->path), true));
mjs_set(
mjs,
global,
"__dirpath",
"__dirname",
~0,
mjs_mk_string(mjs, furi_string_get_cstr(dirpath), furi_string_size(dirpath), true));
furi_string_free(dirpath);
}
mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print));
mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay));
mjs_set(mjs, global, "toString", ~0, MJS_MK_FN(js_global_to_string));
mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address));
mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require));
mjs_set(mjs, global, "parseInt", ~0, MJS_MK_FN(js_parse_int));
mjs_set(mjs, global, "toUpperCase", ~0, MJS_MK_FN(js_to_upper_case));
mjs_set(mjs, global, "toLowerCase", ~0, MJS_MK_FN(js_to_lower_case));
mjs_val_t console_obj = mjs_mk_object(mjs);
mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log));
@@ -269,7 +269,6 @@ static void js_gpio_get(struct mjs* mjs) {
manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0);
manager_data->adc_handle = module->adc_handle;
manager_data->adc_channel = pin_record->channel;
mjs_own(mjs, &manager);
mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data));
mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init));
mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write));
@@ -8,6 +8,7 @@
typedef struct {
uint8_t* buffer;
size_t buffer_size;
size_t default_data_size;
FuriString* header;
FuriSemaphore* input_semaphore;
JsEventLoopContract contract;
@@ -38,7 +39,14 @@ static bool
len_assign(struct mjs* mjs, ByteInput* input, JsViewPropValue value, JsByteKbContext* context) {
UNUSED(mjs);
UNUSED(input);
context->buffer_size = (size_t)(value.number);
size_t new_buffer_size = value.number;
if(new_buffer_size < context->default_data_size) {
// Avoid confusing parameters from user
mjs_prepend_errorf(
mjs, MJS_BAD_ARGS_ERROR, "length must be larger than defaultData length");
return false;
}
context->buffer_size = new_buffer_size;
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
byte_input_set_result_callback(
input,
@@ -57,16 +65,24 @@ static bool default_data_assign(
JsByteKbContext* context) {
UNUSED(mjs);
mjs_val_t array_buf = value.array;
mjs_val_t array_buf = value.term;
if(mjs_is_data_view(array_buf)) {
array_buf = mjs_dataview_get_buf(mjs, array_buf);
}
size_t default_data_len = 0;
char* default_data = mjs_array_buf_get_ptr(mjs, array_buf, &default_data_len);
memcpy(
context->buffer,
(uint8_t*)default_data,
MIN((size_t)context->buffer_size, default_data_len));
char* default_data = mjs_array_buf_get_ptr(mjs, array_buf, &context->default_data_size);
if(context->buffer_size < context->default_data_size) {
// Ensure buffer is large enough for defaultData
context->buffer_size = context->default_data_size;
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
}
memcpy(context->buffer, (uint8_t*)default_data, context->default_data_size);
if(context->buffer_size > context->default_data_size) {
// Reset previous data after defaultData
memset(
context->buffer + context->default_data_size,
0x00,
context->buffer_size - context->default_data_size);
}
byte_input_set_result_callback(
input,
@@ -216,14 +216,14 @@ static bool
expected_type = "array";
break;
}
c_value = (JsViewPropValue){.array = value};
c_value = (JsViewPropValue){.term = value};
} break;
case JsViewPropTypeTypedArr: {
if(!mjs_is_typed_array(value)) {
expected_type = "typed_array";
break;
}
c_value = (JsViewPropValue){.array = value};
c_value = (JsViewPropValue){.term = value};
} break;
case JsViewPropTypeBool: {
if(!mjs_is_boolean(value)) {
@@ -260,26 +260,6 @@ static void js_gui_view_set(struct mjs* mjs) {
mjs_return(mjs, MJS_UNDEFINED);
}
/**
* @brief `View.hasProperty`
*/
static void js_gui_view_has_property(struct mjs* mjs) {
const char* name;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&name));
JsGuiViewData* data = JS_GET_CONTEXT(mjs);
const JsViewDescriptor* descriptor = data->descriptor;
for(size_t i = 0; i < descriptor->prop_cnt; i++) {
JsViewPropDescriptor prop = descriptor->props[i];
if(strcmp(prop.name, name) != 0) continue;
mjs_return(mjs, mjs_mk_boolean(mjs, true));
return;
}
mjs_return(mjs, mjs_mk_boolean(mjs, false));
}
/**
* @brief `View` destructor
*/
@@ -304,7 +284,6 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr
// generic view API
mjs_val_t view_obj = mjs_mk_object(mjs);
mjs_set(mjs, view_obj, "set", ~0, MJS_MK_FN(js_gui_view_set));
mjs_set(mjs, view_obj, "hasProperty", ~0, MJS_MK_FN(js_gui_view_has_property));
// object data
JsGuiViewData* data = malloc(sizeof(JsGuiViewData));
@@ -16,8 +16,8 @@ typedef enum {
typedef union {
const char* string;
int32_t number;
mjs_val_t array;
bool boolean;
mjs_val_t term;
} JsViewPropValue;
/**
@@ -33,9 +33,9 @@ static bool
static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) {
UNUSED(mjs);
submenu_reset(submenu);
size_t len = mjs_array_length(mjs, value.array);
size_t len = mjs_array_length(mjs, value.term);
for(size_t i = 0; i < len; i++) {
mjs_val_t item = mjs_array_get(mjs, value.array, i);
mjs_val_t item = mjs_array_get(mjs, value.term, i);
if(!mjs_is_string(item)) return false;
submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context);
}
@@ -8,6 +8,7 @@
typedef struct {
char* buffer;
size_t buffer_size;
size_t default_text_size;
FuriString* header;
bool default_text_clear;
FuriSemaphore* input_semaphore;
@@ -49,7 +50,14 @@ static bool max_len_assign(
JsViewPropValue value,
JsKbdContext* context) {
UNUSED(mjs);
context->buffer_size = (size_t)(value.number + 1);
size_t new_buffer_size = value.number + 1;
if(new_buffer_size < context->default_text_size) {
// Avoid confusing parameters from user
mjs_prepend_errorf(
mjs, MJS_BAD_ARGS_ERROR, "maxLength must be larger than defaultText length");
return false;
}
context->buffer_size = new_buffer_size;
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
text_input_set_result_callback(
input,
@@ -70,6 +78,13 @@ static bool default_text_assign(
UNUSED(input);
if(value.string) {
context->default_text_size = strlen(value.string) + 1;
if(context->buffer_size < context->default_text_size) {
// Ensure buffer is large enough for defaultData
context->buffer_size = context->default_text_size;
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
}
// Also trim excess previous data with strlcpy()
strlcpy(context->buffer, value.string, context->buffer_size);
text_input_set_result_callback(
input,
@@ -120,6 +135,7 @@ static JsKbdContext* ctx_make(struct mjs* mjs, TextInput* input, mjs_val_t view_
.transformer_context = context,
},
};
text_input_show_illegal_symbols(input, true); // Temporary until prop is available
text_input_set_result_callback(
input,
(TextInputCallback)input_callback,
+280
View File
@@ -0,0 +1,280 @@
#include "../js_modules.h"
#include <furi_hal_i2c.h>
static void ret_bad_args(struct mjs* mjs, const char* error) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
mjs_return(mjs, MJS_UNDEFINED);
}
static bool check_arg_count_range(struct mjs* mjs, size_t min_count, size_t max_count) {
size_t num_args = mjs_nargs(mjs);
if(num_args < min_count || num_args > max_count) {
ret_bad_args(mjs, "Wrong argument count");
return false;
}
return true;
}
static void js_i2c_is_device_ready(struct mjs* mjs) {
if(!check_arg_count_range(mjs, 1, 2)) return;
mjs_val_t addr_arg = mjs_arg(mjs, 0);
if(!mjs_is_number(addr_arg)) {
ret_bad_args(mjs, "Addr must be a number");
return;
}
uint32_t addr = mjs_get_int32(mjs, addr_arg);
uint32_t timeout = 1;
if(mjs_nargs(mjs) > 1) { // Timeout is optional argument
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
if(!mjs_is_number(timeout_arg)) {
ret_bad_args(mjs, "Timeout must be a number");
return;
}
timeout = mjs_get_int32(mjs, timeout_arg);
}
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
bool ready = furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, addr, timeout);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
mjs_return(mjs, mjs_mk_boolean(mjs, ready));
}
static void js_i2c_write(struct mjs* mjs) {
if(!check_arg_count_range(mjs, 2, 3)) return;
mjs_val_t addr_arg = mjs_arg(mjs, 0);
if(!mjs_is_number(addr_arg)) {
ret_bad_args(mjs, "Addr must be a number");
return;
}
uint32_t addr = mjs_get_int32(mjs, addr_arg);
mjs_val_t tx_buf_arg = mjs_arg(mjs, 1);
bool tx_buf_was_allocated = false;
uint8_t* tx_buf = NULL;
size_t tx_len = 0;
if(mjs_is_array(tx_buf_arg)) {
tx_len = mjs_array_length(mjs, tx_buf_arg);
if(tx_len == 0) {
ret_bad_args(mjs, "Data array must not be empty");
return;
}
tx_buf = malloc(tx_len);
tx_buf_was_allocated = true;
for(size_t i = 0; i < tx_len; i++) {
mjs_val_t val = mjs_array_get(mjs, tx_buf_arg, i);
if(!mjs_is_number(val)) {
ret_bad_args(mjs, "Data array must contain only numbers");
free(tx_buf);
return;
}
uint32_t byte_val = mjs_get_int32(mjs, val);
if(byte_val > 0xFF) {
ret_bad_args(mjs, "Data array values must be 0-255");
free(tx_buf);
return;
}
tx_buf[i] = byte_val;
}
} else if(mjs_is_typed_array(tx_buf_arg)) {
mjs_val_t array_buf = tx_buf_arg;
if(mjs_is_data_view(tx_buf_arg)) {
array_buf = mjs_dataview_get_buf(mjs, tx_buf_arg);
}
tx_buf = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &tx_len);
if(tx_len == 0) {
ret_bad_args(mjs, "Data array must not be empty");
return;
}
} else {
ret_bad_args(mjs, "Data must be an array, arraybuf or dataview");
return;
}
uint32_t timeout = 1;
if(mjs_nargs(mjs) > 2) { // Timeout is optional argument
mjs_val_t timeout_arg = mjs_arg(mjs, 2);
if(!mjs_is_number(timeout_arg)) {
ret_bad_args(mjs, "Timeout must be a number");
if(tx_buf_was_allocated) free(tx_buf);
return;
}
timeout = mjs_get_int32(mjs, timeout_arg);
}
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
bool result = furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, tx_buf, tx_len, timeout);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
if(tx_buf_was_allocated) free(tx_buf);
mjs_return(mjs, mjs_mk_boolean(mjs, result));
}
static void js_i2c_read(struct mjs* mjs) {
if(!check_arg_count_range(mjs, 2, 3)) return;
mjs_val_t addr_arg = mjs_arg(mjs, 0);
if(!mjs_is_number(addr_arg)) {
ret_bad_args(mjs, "Addr must be a number");
return;
}
uint32_t addr = mjs_get_int32(mjs, addr_arg);
mjs_val_t rx_len_arg = mjs_arg(mjs, 1);
if(!mjs_is_number(rx_len_arg)) {
ret_bad_args(mjs, "Length must be a number");
return;
}
size_t rx_len = mjs_get_int32(mjs, rx_len_arg);
if(rx_len == 0) {
ret_bad_args(mjs, "Length must not zero");
return;
}
uint8_t* rx_buf = malloc(rx_len);
uint32_t timeout = 1;
if(mjs_nargs(mjs) > 2) { // Timeout is optional argument
mjs_val_t timeout_arg = mjs_arg(mjs, 2);
if(!mjs_is_number(timeout_arg)) {
ret_bad_args(mjs, "Timeout must be a number");
free(rx_buf);
return;
}
timeout = mjs_get_int32(mjs, timeout_arg);
}
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
bool result = furi_hal_i2c_rx(&furi_hal_i2c_handle_external, addr, rx_buf, rx_len, timeout);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
mjs_val_t ret = MJS_UNDEFINED;
if(result) {
ret = mjs_mk_array_buf(mjs, (char*)rx_buf, rx_len);
}
free(rx_buf);
mjs_return(mjs, ret);
}
static void js_i2c_write_read(struct mjs* mjs) {
if(!check_arg_count_range(mjs, 3, 4)) return;
mjs_val_t addr_arg = mjs_arg(mjs, 0);
if(!mjs_is_number(addr_arg)) {
ret_bad_args(mjs, "Addr must be a number");
return;
}
uint32_t addr = mjs_get_int32(mjs, addr_arg);
mjs_val_t tx_buf_arg = mjs_arg(mjs, 1);
bool tx_buf_was_allocated = false;
uint8_t* tx_buf = NULL;
size_t tx_len = 0;
if(mjs_is_array(tx_buf_arg)) {
tx_len = mjs_array_length(mjs, tx_buf_arg);
if(tx_len == 0) {
ret_bad_args(mjs, "Data array must not be empty");
return;
}
tx_buf = malloc(tx_len);
tx_buf_was_allocated = true;
for(size_t i = 0; i < tx_len; i++) {
mjs_val_t val = mjs_array_get(mjs, tx_buf_arg, i);
if(!mjs_is_number(val)) {
ret_bad_args(mjs, "Data array must contain only numbers");
free(tx_buf);
return;
}
uint32_t byte_val = mjs_get_int32(mjs, val);
if(byte_val > 0xFF) {
ret_bad_args(mjs, "Data array values must be 0-255");
free(tx_buf);
return;
}
tx_buf[i] = byte_val;
}
} else if(mjs_is_typed_array(tx_buf_arg)) {
mjs_val_t array_buf = tx_buf_arg;
if(mjs_is_data_view(tx_buf_arg)) {
array_buf = mjs_dataview_get_buf(mjs, tx_buf_arg);
}
tx_buf = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &tx_len);
if(tx_len == 0) {
ret_bad_args(mjs, "Data array must not be empty");
return;
}
} else {
ret_bad_args(mjs, "Data must be an array, arraybuf or dataview");
return;
}
mjs_val_t rx_len_arg = mjs_arg(mjs, 2);
if(!mjs_is_number(rx_len_arg)) {
ret_bad_args(mjs, "Length must be a number");
if(tx_buf_was_allocated) free(tx_buf);
return;
}
size_t rx_len = mjs_get_int32(mjs, rx_len_arg);
if(rx_len == 0) {
ret_bad_args(mjs, "Length must not zero");
if(tx_buf_was_allocated) free(tx_buf);
return;
}
uint8_t* rx_buf = malloc(rx_len);
uint32_t timeout = 1;
if(mjs_nargs(mjs) > 3) { // Timeout is optional argument
mjs_val_t timeout_arg = mjs_arg(mjs, 3);
if(!mjs_is_number(timeout_arg)) {
ret_bad_args(mjs, "Timeout must be a number");
if(tx_buf_was_allocated) free(tx_buf);
free(rx_buf);
return;
}
timeout = mjs_get_int32(mjs, timeout_arg);
}
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
bool result = furi_hal_i2c_trx(
&furi_hal_i2c_handle_external, addr, tx_buf, tx_len, rx_buf, rx_len, timeout);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
mjs_val_t ret = MJS_UNDEFINED;
if(result) {
ret = mjs_mk_array_buf(mjs, (char*)rx_buf, rx_len);
}
if(tx_buf_was_allocated) free(tx_buf);
free(rx_buf);
mjs_return(mjs, ret);
}
static void* js_i2c_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
UNUSED(modules);
mjs_val_t i2c_obj = mjs_mk_object(mjs);
mjs_set(mjs, i2c_obj, "isDeviceReady", ~0, MJS_MK_FN(js_i2c_is_device_ready));
mjs_set(mjs, i2c_obj, "write", ~0, MJS_MK_FN(js_i2c_write));
mjs_set(mjs, i2c_obj, "read", ~0, MJS_MK_FN(js_i2c_read));
mjs_set(mjs, i2c_obj, "writeRead", ~0, MJS_MK_FN(js_i2c_write_read));
*object = i2c_obj;
return (void*)1;
}
static const JsModuleDescriptor js_i2c_desc = {
"i2c",
js_i2c_create,
NULL,
NULL,
};
static const FlipperAppPluginDescriptor i2c_plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_i2c_desc,
};
const FlipperAppPluginDescriptor* js_i2c_ep(void) {
return &i2c_plugin_descriptor;
}
+1 -1
View File
@@ -308,7 +308,7 @@ void js_math_trunc(struct mjs* mjs) {
static void* js_math_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
UNUSED(modules);
mjs_val_t math_obj = mjs_mk_object(mjs);
mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal));
mjs_set(mjs, math_obj, "isEqual", ~0, MJS_MK_FN(js_math_is_equal));
mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs));
mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos));
mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh));
+283
View File
@@ -0,0 +1,283 @@
#include "../js_modules.h"
#include <furi_hal_spi.h>
typedef struct {
bool acquired_bus;
} JsSpiInst;
static JsSpiInst* get_this_ctx(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSpiInst* spi = mjs_get_ptr(mjs, obj_inst);
furi_assert(spi);
return spi;
}
static void ret_bad_args(struct mjs* mjs, const char* error) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
mjs_return(mjs, MJS_UNDEFINED);
}
static bool check_arg_count_range(struct mjs* mjs, size_t min_count, size_t max_count) {
size_t num_args = mjs_nargs(mjs);
if(num_args < min_count || num_args > max_count) {
ret_bad_args(mjs, "Wrong argument count");
return false;
}
return true;
}
static void js_spi_acquire(struct mjs* mjs) {
if(!check_arg_count_range(mjs, 0, 0)) return;
JsSpiInst* spi = get_this_ctx(mjs);
if(!spi->acquired_bus) {
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external);
spi->acquired_bus = true;
}
mjs_return(mjs, MJS_UNDEFINED);
}
static void js_spi_release(struct mjs* mjs) {
if(!check_arg_count_range(mjs, 0, 0)) return;
JsSpiInst* spi = get_this_ctx(mjs);
if(spi->acquired_bus) {
furi_hal_spi_release(&furi_hal_spi_bus_handle_external);
spi->acquired_bus = false;
}
mjs_return(mjs, MJS_UNDEFINED);
}
static bool js_spi_is_acquired(struct mjs* mjs) {
JsSpiInst* spi = get_this_ctx(mjs);
return spi->acquired_bus;
}
static void js_spi_write(struct mjs* mjs) {
if(!check_arg_count_range(mjs, 1, 2)) return;
mjs_val_t tx_buf_arg = mjs_arg(mjs, 0);
bool tx_buf_was_allocated = false;
uint8_t* tx_buf = NULL;
size_t tx_len = 0;
if(mjs_is_array(tx_buf_arg)) {
tx_len = mjs_array_length(mjs, tx_buf_arg);
if(tx_len == 0) {
ret_bad_args(mjs, "Data array must not be empty");
return;
}
tx_buf = malloc(tx_len);
tx_buf_was_allocated = true;
for(size_t i = 0; i < tx_len; i++) {
mjs_val_t val = mjs_array_get(mjs, tx_buf_arg, i);
if(!mjs_is_number(val)) {
ret_bad_args(mjs, "Data array must contain only numbers");
free(tx_buf);
return;
}
uint32_t byte_val = mjs_get_int32(mjs, val);
if(byte_val > 0xFF) {
ret_bad_args(mjs, "Data array values must be 0-255");
free(tx_buf);
return;
}
tx_buf[i] = byte_val;
}
} else if(mjs_is_typed_array(tx_buf_arg)) {
mjs_val_t array_buf = tx_buf_arg;
if(mjs_is_data_view(tx_buf_arg)) {
array_buf = mjs_dataview_get_buf(mjs, tx_buf_arg);
}
tx_buf = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &tx_len);
if(tx_len == 0) {
ret_bad_args(mjs, "Data array must not be empty");
return;
}
} else {
ret_bad_args(mjs, "Data must be an array, arraybuf or dataview");
return;
}
uint32_t timeout = 1;
if(mjs_nargs(mjs) > 1) { // Timeout is optional argument
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
if(!mjs_is_number(timeout_arg)) {
ret_bad_args(mjs, "Timeout must be a number");
if(tx_buf_was_allocated) free(tx_buf);
return;
}
timeout = mjs_get_int32(mjs, timeout_arg);
}
if(!js_spi_is_acquired(mjs)) {
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external);
}
bool result = furi_hal_spi_bus_tx(&furi_hal_spi_bus_handle_external, tx_buf, tx_len, timeout);
if(!js_spi_is_acquired(mjs)) {
furi_hal_spi_release(&furi_hal_spi_bus_handle_external);
}
if(tx_buf_was_allocated) free(tx_buf);
mjs_return(mjs, mjs_mk_boolean(mjs, result));
}
static void js_spi_read(struct mjs* mjs) {
if(!check_arg_count_range(mjs, 1, 2)) return;
mjs_val_t rx_len_arg = mjs_arg(mjs, 0);
if(!mjs_is_number(rx_len_arg)) {
ret_bad_args(mjs, "Length must be a number");
return;
}
size_t rx_len = mjs_get_int32(mjs, rx_len_arg);
if(rx_len == 0) {
ret_bad_args(mjs, "Length must not zero");
return;
}
uint8_t* rx_buf = malloc(rx_len);
uint32_t timeout = 1;
if(mjs_nargs(mjs) > 1) { // Timeout is optional argument
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
if(!mjs_is_number(timeout_arg)) {
ret_bad_args(mjs, "Timeout must be a number");
free(rx_buf);
return;
}
timeout = mjs_get_int32(mjs, timeout_arg);
}
if(!js_spi_is_acquired(mjs)) {
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external);
}
bool result = furi_hal_spi_bus_rx(&furi_hal_spi_bus_handle_external, rx_buf, rx_len, timeout);
if(!js_spi_is_acquired(mjs)) {
furi_hal_spi_release(&furi_hal_spi_bus_handle_external);
}
mjs_val_t ret = MJS_UNDEFINED;
if(result) {
ret = mjs_mk_array_buf(mjs, (char*)rx_buf, rx_len);
}
free(rx_buf);
mjs_return(mjs, ret);
}
static void js_spi_write_read(struct mjs* mjs) {
if(!check_arg_count_range(mjs, 1, 2)) return;
mjs_val_t tx_buf_arg = mjs_arg(mjs, 0);
bool tx_buf_was_allocated = false;
uint8_t* tx_buf = NULL;
size_t data_len = 0;
if(mjs_is_array(tx_buf_arg)) {
data_len = mjs_array_length(mjs, tx_buf_arg);
if(data_len == 0) {
ret_bad_args(mjs, "Data array must not be empty");
return;
}
tx_buf = malloc(data_len);
tx_buf_was_allocated = true;
for(size_t i = 0; i < data_len; i++) {
mjs_val_t val = mjs_array_get(mjs, tx_buf_arg, i);
if(!mjs_is_number(val)) {
ret_bad_args(mjs, "Data array must contain only numbers");
free(tx_buf);
return;
}
uint32_t byte_val = mjs_get_int32(mjs, val);
if(byte_val > 0xFF) {
ret_bad_args(mjs, "Data array values must be 0-255");
free(tx_buf);
return;
}
tx_buf[i] = byte_val;
}
} else if(mjs_is_typed_array(tx_buf_arg)) {
mjs_val_t array_buf = tx_buf_arg;
if(mjs_is_data_view(tx_buf_arg)) {
array_buf = mjs_dataview_get_buf(mjs, tx_buf_arg);
}
tx_buf = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &data_len);
if(data_len == 0) {
ret_bad_args(mjs, "Data array must not be empty");
return;
}
} else {
ret_bad_args(mjs, "Data must be an array, arraybuf or dataview");
return;
}
uint8_t* rx_buf = malloc(data_len); // RX and TX are same length for SPI writeRead.
uint32_t timeout = 1;
if(mjs_nargs(mjs) > 1) { // Timeout is optional argument
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
if(!mjs_is_number(timeout_arg)) {
ret_bad_args(mjs, "Timeout must be a number");
if(tx_buf_was_allocated) free(tx_buf);
free(rx_buf);
return;
}
timeout = mjs_get_int32(mjs, timeout_arg);
}
if(!js_spi_is_acquired(mjs)) {
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external);
}
bool result =
furi_hal_spi_bus_trx(&furi_hal_spi_bus_handle_external, tx_buf, rx_buf, data_len, timeout);
if(!js_spi_is_acquired(mjs)) {
furi_hal_spi_release(&furi_hal_spi_bus_handle_external);
}
mjs_val_t ret = MJS_UNDEFINED;
if(result) {
ret = mjs_mk_array_buf(mjs, (char*)rx_buf, data_len);
}
if(tx_buf_was_allocated) free(tx_buf);
free(rx_buf);
mjs_return(mjs, ret);
}
static void* js_spi_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
UNUSED(modules);
JsSpiInst* spi = (JsSpiInst*)malloc(sizeof(JsSpiInst));
spi->acquired_bus = false;
mjs_val_t spi_obj = mjs_mk_object(mjs);
mjs_set(mjs, spi_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, spi));
mjs_set(mjs, spi_obj, "acquire", ~0, MJS_MK_FN(js_spi_acquire));
mjs_set(mjs, spi_obj, "release", ~0, MJS_MK_FN(js_spi_release));
mjs_set(mjs, spi_obj, "write", ~0, MJS_MK_FN(js_spi_write));
mjs_set(mjs, spi_obj, "read", ~0, MJS_MK_FN(js_spi_read));
mjs_set(mjs, spi_obj, "writeRead", ~0, MJS_MK_FN(js_spi_write_read));
*object = spi_obj;
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external);
return (void*)spi;
}
static void js_spi_destroy(void* inst) {
JsSpiInst* spi = (JsSpiInst*)inst;
if(spi->acquired_bus) {
furi_hal_spi_release(&furi_hal_spi_bus_handle_external);
}
free(spi);
furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_external);
}
static const JsModuleDescriptor js_spi_desc = {
"spi",
js_spi_create,
js_spi_destroy,
NULL,
};
static const FlipperAppPluginDescriptor spi_plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_spi_desc,
};
const FlipperAppPluginDescriptor* js_spi_ep(void) {
return &spi_plugin_descriptor;
}
+1 -1
View File
@@ -40,7 +40,7 @@ export type KeyCode = MainKey | ModifierKey | number;
*
* @param settings USB device settings. Omit to select default parameters
*/
export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string, layoutPath: string }): void;
export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string, layoutPath?: string }): void;
/**
* @brief Tells whether the virtual USB HID device has successfully connected
+23 -25
View File
@@ -11,46 +11,30 @@ declare function delay(ms: number): void;
*/
declare function print(...args: any[]): void;
/**
* @brief Converts a number to a string
* @param value The number to convert to a string
* @param base Integer base (`2`...`16`), default: 16
*/
declare function toString(value: number, base?: number): string;
/**
* @brief Converts a string to a number
* @param text The string to convert to a number
* @param base Integer base (`2`...`16`), default: 10
*/
declare function parseInt(text: string): number;
/**
* @brief Transforms a string to upper case
* @param text The string to transforms to upper case
*/
declare function toUpperCase(text: string): string;
/**
* @brief Transforms a string to lower case
* @param text The string to transforms to lower case
*/
declare function toLowerCase(text: string): string;
declare function parseInt(text: string, base?: number): number;
/**
* @brief Path to the directory containing the current script
*/
declare const __dirpath: string;
declare const __dirname: string;
/**
* @brief Path to the current script file
*/
declare const __filepath: string;
declare const __filename: string;
/**
* @brief Reads a JS value from a file
*
* Reads a file at the specified path, interprets it as a JS value and returns
* the last value pushed on the stack.
* Reads a file at the specified path and runs it as JS, returning the last evaluated value.
*
* The result is cached and this filepath will not re-evaluated on future
* load() calls for this session.
*
* @param path The path to the file
* @param scope An object to use as global scope while running this file
@@ -211,13 +195,27 @@ declare class String {
* @param end The index to end substring at
*/
slice(start: number, end?: number): string;
/**
* @brief Return this string transformed to upper case
*/
toUpperCase(): string;
/**
* @brief Return this string transformed to lower case
*/
toLowerCase(): string;
}
declare class Boolean { }
declare class Function { }
declare class Number { }
declare class Number {
/**
* @brief Converts this number to a string
* @param base Integer base (`2`...`16`), default: 10
*/
toString(base?: number): string;
}
declare class Object { }
-5
View File
@@ -9,11 +9,6 @@ export declare class View<Props extends Properties> {
* @param value Value to assign
*/
set<P extends keyof Props>(property: P, value: Props[P]): void;
/**
* Check if property is available
* @param name Name of the property
*/
hasProperty(name: string): boolean;
}
export declare class ViewFactory<Props extends Properties, V extends View<Props>> {
+31
View File
@@ -0,0 +1,31 @@
/**
* @brief Check if there is an I2C device ready on the bus
* @param address The device address to check
* @param timeout Timeout in milliseconds
*/
export declare function isDeviceReady(address: number, timeout?: number): boolean;
/**
* @brief Write data to I2C device and return success status
* @param address The device address to write to
* @param data The data to write to the device
* @param timeout Timeout in milliseconds
*/
export declare function write(address: number, data: number[] | ArrayBuffer, timeout?: number): boolean;
/**
* @brief Read data from I2C device or return undefined on failure
* @param address The device address to read from
* @param length How many bytes to read
* @param timeout Timeout in milliseconds
*/
export declare function read(address: number, length: number, timeout?: number): ArrayBuffer | undefined;
/**
* @brief Write data then read from I2C device or return undefined on failure
* @param address The device address to talk to
* @param writeData The data to write to the device
* @param readLength How many bytes to read
* @param timeout Timeout in milliseconds
*/
export declare function writeRead(address: number, writeData: number[] | ArrayBuffer, readLength: number, timeout?: number): ArrayBuffer | undefined;
+3
View File
@@ -1,3 +1,4 @@
export function isEqual(a: number, b: number, tolerance: number): boolean;
export function abs(n: number): number;
export function acos(n: number): number;
export function acosh(n: number): number;
@@ -12,6 +13,7 @@ export function clz32(n: number): number;
export function cos(n: number): number;
export function exp(n: number): number;
export function floor(n: number): number;
export function log(n: number): number;
export function max(n: number, m: number): number;
export function min(n: number, m: number): number;
export function pow(n: number, m: number): number;
@@ -21,4 +23,5 @@ export function sin(n: number): number;
export function sqrt(n: number): number;
export function trunc(n: number): number;
declare const PI: number;
declare const E: number;
declare const EPSILON: number;
+30
View File
@@ -0,0 +1,30 @@
/**
* @brief Acquire SPI bus
*/
export declare function acquire(): void;
/**
* @brief Release SPI bus
*/
export declare function release(): void;
/**
* @brief Write data to SPI bus and return success status
* @param data The data to write
* @param timeout Timeout in milliseconds
*/
export declare function write(data: number[] | ArrayBuffer, timeout?: number): boolean;
/**
* @brief Read data from SPI bus or return undefined on failure
* @param length How many bytes to read
* @param timeout Timeout in milliseconds
*/
export declare function read(length: number, timeout?: number): ArrayBuffer | undefined;
/**
* @brief Write and read data on SPI bus or return undefined on failure
* @param data The data to write, its length also indicates how many bytes will be read
* @param timeout Timeout in milliseconds
*/
export declare function writeRead(data: number[] | ArrayBuffer, timeout?: number): ArrayBuffer | undefined;
+3 -2
View File
@@ -1,7 +1,8 @@
/**
@mainpage Overview
@mainpage notitle
## Welcome to Flipper Developer Documentation!
Welcome to the Flipper Developer Documentation!
---
This documentation is intended for developers interested in modifying the Flipper Zero firmware, creating Apps and JavaScript programs, or working on external hardware modules for the device.
+3 -3
View File
@@ -223,7 +223,7 @@ math.floor(45.05); // 45
math.floor(45.95); // 45
```
## is_equal
## isEqual
Return true if the difference between numbers `a` and `b` is less than the specified parameter `e`.
### Parameters
@@ -236,8 +236,8 @@ True if the difference between numbers `a` and `b` is less than the specified pa
### Example
```js
math.is_equal(1.4, 1.6, 0.2); // false
math.is_equal(3.556, 3.555, 0.01); // true
math.isEqual(1.4, 1.6, 0.2); // false
math.isEqual(3.556, 3.555, 0.01); // true
```
## max
+3
View File
@@ -15,6 +15,9 @@
* Example:
* ./fbt --extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3
*/
#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN
#include <furi_hal.h>
#endif
#define TAG "DigitalSequence"
+24
View File
@@ -452,6 +452,12 @@ static int getprop_builtin_string(
} else if(strcmp(name, "slice") == 0) {
*res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_slice);
return 1;
} else if(strcmp(name, "toUpperCase") == 0) {
*res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_to_upper_case);
return 1;
} else if(strcmp(name, "toLowerCase") == 0) {
*res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_to_lower_case);
return 1;
} else if(isnum) {
/*
* string subscript: return a new one-byte string if the index
@@ -469,6 +475,22 @@ static int getprop_builtin_string(
return 0;
}
static int getprop_builtin_number(
struct mjs* mjs,
mjs_val_t val,
const char* name,
size_t name_len,
mjs_val_t* res) {
if(strcmp(name, "toString") == 0) {
*res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_number_to_string);
return 1;
}
(void)val;
(void)name_len;
return 0;
}
static int getprop_builtin_array(
struct mjs* mjs,
mjs_val_t val,
@@ -583,6 +605,8 @@ static int getprop_builtin(struct mjs* mjs, mjs_val_t val, mjs_val_t name, mjs_v
} else if(s != NULL && n == 5 && strncmp(s, "apply", n) == 0) {
*res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_apply_);
handled = 1;
} else if(mjs_is_number(val)) {
handled = getprop_builtin_number(mjs, val, s, n, res);
} else if(mjs_is_array(val)) {
handled = getprop_builtin_array(mjs, val, s, n, res);
} else if(mjs_is_foreign(val)) {
+30
View File
@@ -6,6 +6,8 @@
#include "mjs_core.h"
#include "mjs_internal.h"
#include "mjs_primitive.h"
#include "mjs_string_public.h"
#include "mjs_util.h"
mjs_val_t mjs_mk_null(void) {
return MJS_NULL;
@@ -158,3 +160,31 @@ MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs) {
mjs_return(mjs, ret);
}
MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) {
mjs_val_t ret = MJS_UNDEFINED;
mjs_val_t base_v = MJS_UNDEFINED;
int32_t base = 10;
int32_t num;
/* get number from `this` */
if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_NUMBER, NULL)) {
goto clean;
}
num = mjs_get_int32(mjs, mjs->vals.this_obj);
if(mjs_nargs(mjs) >= 1) {
/* get base from arg 0 */
if(!mjs_check_arg(mjs, 0, "base", MJS_TYPE_NUMBER, &base_v)) {
goto clean;
}
base = mjs_get_int(mjs, base_v);
}
char tmp_str[] = "-2147483648";
itoa(num, tmp_str, base);
ret = mjs_mk_string(mjs, tmp_str, ~0, true);
clean:
mjs_return(mjs, ret);
}
+5
View File
@@ -34,6 +34,11 @@ MJS_PRIVATE void* get_ptr(mjs_val_t v);
*/
MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs);
/*
* Implementation for JS Number.toString()
*/
MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs);
#if defined(__cplusplus)
}
#endif /* __cplusplus */
+35
View File
@@ -286,6 +286,41 @@ MJS_PRIVATE mjs_val_t s_concat(struct mjs* mjs, mjs_val_t a, mjs_val_t b) {
return res;
}
MJS_PRIVATE void mjs_string_to_case(struct mjs* mjs, bool upper) {
mjs_val_t ret = MJS_UNDEFINED;
size_t size;
const char* s = NULL;
/* get string from `this` */
if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_STRING, NULL)) {
goto clean;
}
s = mjs_get_string(mjs, &mjs->vals.this_obj, &size);
if(size == 0) {
ret = mjs_mk_string(mjs, "", 0, 1);
goto clean;
}
char* tmp = malloc(size);
for(size_t i = 0; i < size; i++) {
tmp[i] = upper ? toupper(s[i]) : tolower(s[i]);
}
ret = mjs_mk_string(mjs, tmp, size, 1);
free(tmp);
clean:
mjs_return(mjs, ret);
}
MJS_PRIVATE void mjs_string_to_lower_case(struct mjs* mjs) {
mjs_string_to_case(mjs, false);
}
MJS_PRIVATE void mjs_string_to_upper_case(struct mjs* mjs) {
mjs_string_to_case(mjs, true);
}
MJS_PRIVATE void mjs_string_slice(struct mjs* mjs) {
int nargs = mjs_nargs(mjs);
mjs_val_t ret = mjs_mk_number(mjs, 0);
+2
View File
@@ -33,6 +33,8 @@ MJS_PRIVATE void embed_string(
MJS_PRIVATE void mjs_mkstr(struct mjs* mjs);
MJS_PRIVATE void mjs_string_to_lower_case(struct mjs* mjs);
MJS_PRIVATE void mjs_string_to_upper_case(struct mjs* mjs);
MJS_PRIVATE void mjs_string_slice(struct mjs* mjs);
MJS_PRIVATE void mjs_string_index_of(struct mjs* mjs);
MJS_PRIVATE void mjs_string_char_code_at(struct mjs* mjs);
+16
View File
@@ -543,6 +543,22 @@ void mf_classic_set_key_not_found(
}
}
MfClassicKey
mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type) {
furi_check(data);
furi_check(sector_num < mf_classic_get_total_sectors_num(data->type));
furi_check(key_type == MfClassicKeyTypeA || key_type == MfClassicKeyTypeB);
const MfClassicSectorTrailer* sector_trailer =
mf_classic_get_sector_trailer_by_sector(data, sector_num);
if(key_type == MfClassicKeyTypeA) {
return sector_trailer->key_a;
} else {
return sector_trailer->key_b;
}
}
bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) {
furi_check(data);
@@ -213,6 +213,9 @@ void mf_classic_set_key_not_found(
uint8_t sector_num,
MfClassicKeyType key_type);
MfClassicKey
mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type);
bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num);
void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data);
@@ -8,9 +8,9 @@
// TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches
// TODO: Store target key in CUID dictionary
// TODO: Fix rare nested_target_key 64 bug
// TODO: Dead code for malloc returning NULL?
// TODO: Auth1 static encrypted exists (rare)
// TODO: Use keys found by NFC plugins, cached keys
#define MF_CLASSIC_MAX_BUFF_SIZE (64)
@@ -1663,7 +1663,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc
"Found key candidate %06llx",
bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey)));
dict_attack_ctx->current_key = *key_candidate;
dict_attack_ctx->reuse_key_sector = (target_block / 4);
dict_attack_ctx->reuse_key_sector = target_sector;
dict_attack_ctx->current_key_type = target_key_type;
free(key_candidate);
break;
@@ -1780,7 +1780,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) {
bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_dict_attack) {
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak;
uint8_t nested_target_key = dict_attack_ctx->nested_target_key;
uint16_t nested_target_key = dict_attack_ctx->nested_target_key;
MfClassicKeyType target_key_type;
uint8_t target_sector;
@@ -1818,12 +1818,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
bool initial_dict_attack_iter = false;
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) {
dict_attack_ctx->auth_passed = true;
dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key;
bool backdoor_present = (dict_attack_ctx->backdoor != MfClassicBackdoorNone);
if(!(backdoor_present)) {
for(uint8_t sector = 0; sector < instance->sectors_total; sector++) {
for(uint8_t key_type = 0; key_type < 2; key_type++) {
if(mf_classic_is_key_found(instance->data, sector, key_type)) {
dict_attack_ctx->nested_known_key =
mf_classic_get_key(instance->data, sector, key_type);
dict_attack_ctx->nested_known_key_sector = sector;
dict_attack_ctx->nested_known_key_type = key_type;
break;
@@ -1832,6 +1833,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
}
dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG;
} else {
dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key;
dict_attack_ctx->nested_known_key_sector = 0;
dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA;
dict_attack_ctx->prng_type = MfClassicPrngTypeWeak;
@@ -1879,9 +1881,10 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ?
(instance->sectors_total * 2) :
(instance->sectors_total * 16);
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) {
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackVerify) {
if(!(mf_classic_nested_is_target_key_found(instance, true)) &&
(dict_attack_ctx->nested_nonce.count > 0)) {
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume;
instance->state = MfClassicPollerStateNestedDictAttack;
return command;
} else {
@@ -1896,7 +1899,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
}
}
if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) &&
if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack ||
dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) &&
(dict_attack_ctx->nested_target_key < dict_target_key_max)) {
bool is_last_iter_for_hard_key =
((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7));
@@ -1920,11 +1924,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
NULL;
}
if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) {
// Key reuse
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume;
// Key verify and reuse
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackVerify;
dict_attack_ctx->auth_passed = false;
instance->state = MfClassicPollerStateKeyReuseStartNoOffset;
return command;
} else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) {
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
dict_attack_ctx->auth_passed = true;
}
if(!(dict_attack_ctx->auth_passed)) {
dict_attack_ctx->attempt_count++;
@@ -2004,9 +2011,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
}
uint16_t nonce_collect_key_max;
if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) {
nonce_collect_key_max = dict_attack_ctx->static_encrypted ?
((instance->sectors_total * 4) - 2) :
(instance->sectors_total * 4);
nonce_collect_key_max = instance->sectors_total * 4;
} else {
nonce_collect_key_max = instance->sectors_total * 2;
}
@@ -2047,30 +2052,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
dict_attack_ctx->attempt_count = 0;
}
dict_attack_ctx->auth_passed = true;
if(!(dict_attack_ctx->current_key_checked)) {
dict_attack_ctx->current_key_checked = true;
// Check if the nested target key is a known key
if(mf_classic_nested_is_target_key_found(instance, false)) {
// Continue to next key
if(!(dict_attack_ctx->static_encrypted)) {
dict_attack_ctx->nested_target_key++;
dict_attack_ctx->current_key_checked = false;
}
instance->state = MfClassicPollerStateNestedController;
return command;
}
// If it is not a known key, we'll need to calibrate for static encrypted backdoored tags
if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) &&
(dict_attack_ctx->nested_target_key < nonce_collect_key_max) &&
!(recalibrated)) {
dict_attack_ctx->calibrated = false;
dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate;
instance->state = MfClassicPollerStateNestedController;
return command;
}
}
// If we have tried to collect this nonce too many times, skip
if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) ||
@@ -2096,12 +2077,52 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
dict_attack_ctx->attempt_count = 0;
}
FURI_LOG_D(
TAG,
"Nested target key: %u (max: %u)",
dict_attack_ctx->nested_target_key,
nonce_collect_key_max);
if(!(dict_attack_ctx->current_key_checked)) {
if(dict_attack_ctx->nested_target_key == nonce_collect_key_max) {
// All nonces have been collected
FURI_LOG_D(TAG, "All nonces collected");
instance->state = MfClassicPollerStateNestedController;
return command;
}
dict_attack_ctx->current_key_checked = true;
// Check if the nested target key is a known key
if(mf_classic_nested_is_target_key_found(instance, false)) {
// Continue to next key
if(!(dict_attack_ctx->static_encrypted)) {
dict_attack_ctx->nested_target_key++;
dict_attack_ctx->current_key_checked = false;
}
instance->state = MfClassicPollerStateNestedController;
return command;
}
// If it is not a known key, we'll need to calibrate for static encrypted backdoored tags
if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) &&
(dict_attack_ctx->nested_target_key < nonce_collect_key_max) &&
!(recalibrated)) {
dict_attack_ctx->calibrated = false;
dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate;
instance->state = MfClassicPollerStateNestedController;
return command;
}
}
FURI_LOG_T(TAG, "Collecting a nonce");
// Collect a nonce
dict_attack_ctx->auth_passed = false;
instance->state = MfClassicPollerStateNestedCollectNtEnc;
return command;
}
}
dict_attack_ctx->nested_target_key = 0;
dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished;
instance->state = MfClassicPollerStateSuccess;
return command;
@@ -52,14 +52,15 @@ typedef enum {
* @brief MfClassic poller nested attack phase.
*/
typedef enum {
MfClassicNestedPhaseNone,
MfClassicNestedPhaseAnalyzePRNG,
MfClassicNestedPhaseDictAttack,
MfClassicNestedPhaseDictAttackResume,
MfClassicNestedPhaseCalibrate,
MfClassicNestedPhaseRecalibrate,
MfClassicNestedPhaseCollectNtEnc,
MfClassicNestedPhaseFinished,
MfClassicNestedPhaseNone, /**< No nested attack has taken place yet. */
MfClassicNestedPhaseAnalyzePRNG, /**< Analyze nonces produced by the PRNG to determine if they fit a weak PRNG */
MfClassicNestedPhaseDictAttack, /**< Search keys which match the expected PRNG properties and parity for collected nonces */
MfClassicNestedPhaseDictAttackVerify, /**< Verify candidate keys by authenticating to the sector with the key */
MfClassicNestedPhaseDictAttackResume, /**< Resume nested dictionary attack from the last tested (invalid) key */
MfClassicNestedPhaseCalibrate, /**< Perform necessary calculations to recover the plaintext nonce during later collection phase (weak PRNG tags only) */
MfClassicNestedPhaseRecalibrate, /**< Collect the next plaintext static encrypted nonce for backdoor static encrypted nonce nested attack */
MfClassicNestedPhaseCollectNtEnc, /**< Log nonces collected during nested authentication for key recovery */
MfClassicNestedPhaseFinished, /**< Nested attack has finished */
} MfClassicNestedPhase;
/**
@@ -5,7 +5,7 @@
#include <nfc/helpers/iso14443_crc.h>
#define TAG "MfCLassicPoller"
#define TAG "MfClassicPoller"
MfClassicError mf_classic_process_error(Iso14443_3aError error) {
MfClassicError ret = MfClassicErrorNone;
@@ -508,7 +508,7 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in
if(instance->error == MfUltralightErrorNone) {
if(start_page < instance->pages_total) {
FURI_LOG_D(TAG, "Read page %d success", start_page);
instance->data->page[start_page] = data.page[start_page];
instance->data->page[start_page] = data.page[0];
instance->pages_read++;
instance->data->pages_read = instance->pages_read;
}
+7 -3
View File
@@ -255,6 +255,9 @@ static bool subghz_protocol_keeloq_gen_data(
} else if(strcmp(instance->manufacture_name, "Centurion") == 0) {
decrypt = btn << 28 | (0x1CE) << 16 | instance->generic.cnt;
// Centurion -> no serial in hop, uses fixed value 0x1CE - normal learning
} else if(strcmp(instance->manufacture_name, "Monarch") == 0) {
decrypt = btn << 28 | (0x100) << 16 | instance->generic.cnt;
// Monarch -> no serial in hop, uses fixed value 0x100 - normal learning
} else if(strcmp(instance->manufacture_name, "Dea_Mio") == 0) {
uint8_t first_disc_num = (instance->generic.serial >> 8) & 0xF;
uint8_t result_disc = (0xC + (first_disc_num % 4));
@@ -430,12 +433,13 @@ static bool
(strcmp(instance->manufacture_name, "Dea_Mio") == 0) ||
(strcmp(instance->manufacture_name, "NICE_MHOUSE") == 0)) {
klq_last_custom_btn = 0xF;
} else if((strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0)) {
} else if(
(strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) ||
(strcmp(instance->manufacture_name, "Monarch") == 0) ||
(strcmp(instance->manufacture_name, "NICE_Smilo") == 0)) {
klq_last_custom_btn = 0xB;
} else if((strcmp(instance->manufacture_name, "Novoferm") == 0)) {
klq_last_custom_btn = 0x9;
} else if((strcmp(instance->manufacture_name, "NICE_Smilo") == 0)) {
klq_last_custom_btn = 0xB;
} else if((strcmp(instance->manufacture_name, "EcoStar") == 0)) {
klq_last_custom_btn = 0x6;
}
+1
View File
@@ -67,6 +67,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = {
&ws_protocol_kedsum_th,
&ws_protocol_emose601x,
&ws_protocol_acurite_5n1,
&ws_protocol_vauno_en8822c,
&subghz_protocol_pocsag,
&tpms_protocol_schrader_gg4,
&subghz_protocol_bin_raw,
+1
View File
@@ -68,6 +68,7 @@
#include "acurite_5n1.h"
#include "solight_te44.h"
#include "bresser_3ch.h"
#include "vauno_en8822c.h"
#include "pocsag.h"
#include "schrader_gg4.h"
#include "bin_raw.h"
+258
View File
@@ -0,0 +1,258 @@
#include "vauno_en8822c.h"
#include "furi/core/log.h"
#define TAG "WSProtocolVaunoEN8822C"
/*
* Help
* https://github.com/merbanan/rtl_433/blob/master/src/devices/vauno_en8822c.c
*
* Vauno EN8822C sensor on 433.92MHz.
*
* Largely the same as Esperanza EWS, s3318p.
* @sa esperanza_ews.c s3318p.c
* List of known supported devices:
* - Vauno EN8822C-1
* - FUZHOU ESUN ELECTRONIC outdoor T21 sensor
*
* Frame structure (42 bits):
*
* Byte: 0 1 2 3 4
* Nibble: 1 2 3 4 5 6 7 8 9 10 11
* Type: IIIIIIII B?CCTTTT TTTTTTTT HHHHHHHF FFFBXXXX XX
*
* - I: Random device ID
* - C: Channel (1-3)
* - T: Temperature (Little-endian)
* - H: Humidity (Little-endian)
* - F: Flags (unknown)
* - B: Battery (1=low voltage ~<2.5V)
* - X: Checksum (6 bit nibble sum)
*
* Sample Data:
*
* [00] {42} af 0f a2 7c 01 c0 : 10101111 00001111 10100010 01111100 00000001 11
*
* - Sensor ID = 175 = 0xaf
* - Channel = 0
* - temp = -93 = 0x111110100010
* - TemperatureC = -9.3
* - hum = 62% = 0x0111110
*
* Copyright (C) 2022 Jamie Barron <gumbald@gmail.com>
*
* @m7i-org - because the ether is wavy
*
*/
static const SubGhzBlockConst ws_protocol_vauno_en8822c_const = {
.te_short = 500,
.te_long = 1940,
.te_delta = 150,
.min_count_bit_for_found = 42,
};
struct WSProtocolDecoderVaunoEN8822C {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
WSBlockGeneric generic;
};
struct WSProtocolEncoderVaunoEN8822C {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
WSBlockGeneric generic;
};
const SubGhzProtocolDecoder ws_protocol_vauno_en8822c_decoder = {
.alloc = ws_protocol_decoder_vauno_en8822c_alloc,
.free = ws_protocol_decoder_vauno_en8822c_free,
.feed = ws_protocol_decoder_vauno_en8822c_feed,
.reset = ws_protocol_decoder_vauno_en8822c_reset,
.get_hash_data = NULL,
.get_hash_data_long = ws_protocol_decoder_vauno_en8822c_get_hash_data,
.serialize = ws_protocol_decoder_vauno_en8822c_serialize,
.deserialize = ws_protocol_decoder_vauno_en8822c_deserialize,
.get_string = ws_protocol_decoder_vauno_en8822c_get_string,
.get_string_brief = NULL,
};
const SubGhzProtocolEncoder ws_protocol_vauno_en8822c_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
const SubGhzProtocol ws_protocol_vauno_en8822c = {
.name = WS_PROTOCOL_VAUNO_EN8822C_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.filter = SubGhzProtocolFilter_Weather,
.decoder = &ws_protocol_vauno_en8822c_decoder,
.encoder = &ws_protocol_vauno_en8822c_encoder,
};
typedef enum {
VaunoEN8822CDecoderStepReset = 0,
VaunoEN8822CDecoderStepSaveDuration,
VaunoEN8822CDecoderStepCheckDuration,
} VaunoEN8822CDecoderStep;
void* ws_protocol_decoder_vauno_en8822c_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
WSProtocolDecoderVaunoEN8822C* instance = malloc(sizeof(WSProtocolDecoderVaunoEN8822C));
instance->base.protocol = &ws_protocol_vauno_en8822c;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void ws_protocol_decoder_vauno_en8822c_free(void* context) {
furi_assert(context);
WSProtocolDecoderVaunoEN8822C* instance = context;
free(instance);
}
void ws_protocol_decoder_vauno_en8822c_reset(void* context) {
furi_assert(context);
WSProtocolDecoderVaunoEN8822C* instance = context;
instance->decoder.parser_step = VaunoEN8822CDecoderStepReset;
}
static bool ws_protocol_vauno_en8822c_check(WSProtocolDecoderVaunoEN8822C* instance) {
if(!instance->decoder.decode_data) return false;
// The sum of all nibbles should match the last 6 bits
uint8_t sum = 0;
for(uint8_t i = 6; i <= 38; i += 4) {
sum += ((instance->decoder.decode_data >> i) & 0x0f);
}
return sum != 0 && (sum & 0x3f) == (instance->decoder.decode_data & 0x3f);
}
/**
* Analysis of received data
* @param instance Pointer to a WSBlockGeneric* instance
*/
static void ws_protocol_vauno_en8822c_extract_data(WSBlockGeneric* instance) {
instance->id = (instance->data >> 34) & 0xff;
instance->battery_low = (instance->data >> 33) & 0x01;
instance->channel = ((instance->data >> 30) & 0x03);
int16_t temp = (instance->data >> 18) & 0x0fff;
/* Handle signed data */
if(temp & 0x0800) {
temp |= 0xf000;
}
instance->temp = (float)temp / 10.0;
instance->humidity = (instance->data >> 11) & 0x7f;
instance->btn = WS_NO_BTN;
}
void ws_protocol_decoder_vauno_en8822c_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
WSProtocolDecoderVaunoEN8822C* instance = context;
switch(instance->decoder.parser_step) {
case VaunoEN8822CDecoderStepReset:
if((!level) && DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long * 4) <
ws_protocol_vauno_en8822c_const.te_delta) {
instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
}
break;
case VaunoEN8822CDecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = VaunoEN8822CDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = VaunoEN8822CDecoderStepReset;
}
break;
case VaunoEN8822CDecoderStepCheckDuration:
if(!level) {
if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_vauno_en8822c_const.te_short) <
ws_protocol_vauno_en8822c_const.te_delta) {
if(DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long * 2) <
ws_protocol_vauno_en8822c_const.te_delta) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration;
} else if(
DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long) <
ws_protocol_vauno_en8822c_const.te_delta) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration;
} else if(
DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long * 4) <
ws_protocol_vauno_en8822c_const.te_delta) {
instance->decoder.parser_step = VaunoEN8822CDecoderStepReset;
if(instance->decoder.decode_count_bit ==
ws_protocol_vauno_en8822c_const.min_count_bit_for_found &&
ws_protocol_vauno_en8822c_check(instance)) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
ws_protocol_vauno_en8822c_extract_data(&instance->generic);
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
} else if(instance->decoder.decode_count_bit == 1) {
instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration;
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else
instance->decoder.parser_step = VaunoEN8822CDecoderStepReset;
} else
instance->decoder.parser_step = VaunoEN8822CDecoderStepReset;
} else
instance->decoder.parser_step = VaunoEN8822CDecoderStepReset;
break;
}
}
uint32_t ws_protocol_decoder_vauno_en8822c_get_hash_data(void* context) {
furi_assert(context);
WSProtocolDecoderVaunoEN8822C* instance = context;
return subghz_protocol_blocks_get_hash_data_long(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus ws_protocol_decoder_vauno_en8822c_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
WSProtocolDecoderVaunoEN8822C* instance = context;
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
ws_protocol_decoder_vauno_en8822c_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
WSProtocolDecoderVaunoEN8822C* instance = context;
return ws_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
ws_protocol_vauno_en8822c_const.min_count_bit_for_found);
}
void ws_protocol_decoder_vauno_en8822c_get_string(void* context, FuriString* output) {
furi_assert(context);
WSProtocolDecoderVaunoEN8822C* instance = context;
ws_block_generic_get_string(&instance->generic, output);
}
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include "ws_generic.h"
#include <lib/subghz/blocks/math.h>
#define WS_PROTOCOL_VAUNO_EN8822C_NAME "Vauno-EN8822C"
typedef struct WSProtocolDecoderVaunoEN8822C WSProtocolDecoderVaunoEN8822C;
typedef struct WSProtocolEncoderVaunoEN8822C WSProtocolEncoderVaunoEN8822C;
extern const SubGhzProtocolDecoder ws_protocol_vauno_en8822c_decoder;
extern const SubGhzProtocolEncoder ws_protocol_vauno_en8822c_encoder;
extern const SubGhzProtocol ws_protocol_vauno_en8822c;
/**
* Allocate WSProtocolDecoderVaunoEN8822C.
* @param environment Pointer to a SubGhzEnvironment instance
* @return WSProtocolDecoderVaunoEN8822C* pointer to a WSProtocolDecoderVaunoEN8822C instance
*/
void* ws_protocol_decoder_vauno_en8822c_alloc(SubGhzEnvironment* environment);
/**
* Free WSProtocolDecoderVaunoEN8822C.
* @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance
*/
void ws_protocol_decoder_vauno_en8822c_free(void* context);
/**
* Reset decoder WSProtocolDecoderVaunoEN8822C.
* @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance
*/
void ws_protocol_decoder_vauno_en8822c_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void ws_protocol_decoder_vauno_en8822c_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance
* @return hash Hash sum
*/
uint32_t ws_protocol_decoder_vauno_en8822c_get_hash_data(void* context);
/**
* Serialize data WSProtocolDecoderVaunoEN8822C.
* @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus ws_protocol_decoder_vauno_en8822c_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data WSProtocolDecoderVaunoEN8822C.
* @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
ws_protocol_decoder_vauno_en8822c_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance
* @param output Resulting text
*/
void ws_protocol_decoder_vauno_en8822c_get_string(void* context, FuriString* output);
+6 -2
View File
@@ -45,6 +45,10 @@ def convert_bmx(img: "Image.Image | pathlib.Path") -> bytes:
return data
def copy_file_as_lf(src: "pathlib.Path", dst: "pathlib.Path"):
dst.write_bytes(src.read_bytes().replace(b"\r\n", b"\n"))
def pack_anim(src: pathlib.Path, dst: pathlib.Path):
if not (src / "meta.txt").is_file():
return
@@ -53,7 +57,7 @@ def pack_anim(src: pathlib.Path, dst: pathlib.Path):
if not frame.is_file():
continue
if frame.name == "meta.txt":
shutil.copyfile(frame, dst / frame.name)
copy_file_as_lf(frame, dst / frame.name)
elif frame.name.startswith("frame_"):
if frame.suffix == ".png":
(dst / frame.with_suffix(".bm").name).write_bytes(convert_bm(frame))
@@ -145,7 +149,7 @@ def pack(
if (source / "Anims/manifest.txt").exists():
(packed / "Anims").mkdir(parents=True, exist_ok=True)
shutil.copyfile(
copy_file_as_lf(
source / "Anims/manifest.txt", packed / "Anims/manifest.txt"
)
manifest = (source / "Anims/manifest.txt").read_bytes()
+3 -1
View File
@@ -314,8 +314,9 @@ def _validate_app_imports(target, source, env):
"totp_",
"token_info_",
"memset_s",
# troika
# social_moscow, troika
"mosgortrans_parse_transport_block",
"render_section_header",
)
)
and any(
@@ -324,6 +325,7 @@ def _validate_app_imports(target, source, env):
"advanced_plugin",
"gallagher",
"js_", # js_app and all js_ modules
"social_moscow",
"test_js",
"totp_",
"troika",
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
import pathlib
universals = (
pathlib.Path(__file__).parent
/ "../applications/main/infrared/resources/infrared/assets"
)
for universal in universals.glob("*.ir"):
text = universal.read_text()
lines = text.splitlines()
signal = []
comment = []
signals = []
for line in lines:
if line.startswith("#"):
comment.append(line)
continue
signal.append(line)
if line.startswith(("data: ", "command: ")): # Last line of this signal
signals.append(("\n".join(signal), "\n".join(comment)))
signal.clear()
comment.clear()
found = dict()
for signal, comment in signals:
if signal in found:
if (
universal.stem == "projectors"
and found[signal] == 1
and signal.startswith("name: Power")
):
# Projectors need double press of power to confirm shutdown, so 1 dupe is fine
found[signal] += 1
continue
replace = f"\n{comment}\n{signal}"
pos = text.rfind(replace)
text = text[:pos] + text[pos + len(replace) :]
continue
found[signal] = 1
universal.write_text(text)
Regular → Executable
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import pathlib
import string
+3 -3
View File
@@ -975,7 +975,7 @@ Function,+,dir_walk_read,DirWalkResult,"DirWalk*, FuriString*, FileInfo*"
Function,+,dir_walk_set_filter_cb,void,"DirWalk*, DirWalkFilterCb, void*"
Function,+,dir_walk_set_recurse_filter,void,"DirWalk*, const char**, size_t"
Function,+,dir_walk_set_recursive,void,"DirWalk*, _Bool"
Function,-,div,div_t,"int, int"
Function,+,div,div_t,"int, int"
Function,+,dolphin_deed,void,DolphinDeed
Function,+,dolphin_deed_get_app,DolphinApp,DolphinDeed
Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp
@@ -2587,6 +2587,7 @@ Function,+,mf_classic_get_base_data,Iso14443_3aData*,const MfClassicData*
Function,+,mf_classic_get_blocks_num_in_sector,uint8_t,uint8_t
Function,+,mf_classic_get_device_name,const char*,"const MfClassicData*, NfcDeviceNameType"
Function,+,mf_classic_get_first_block_num_of_sector,uint8_t,uint8_t
Function,+,mf_classic_get_key,MfClassicKey,"const MfClassicData*, uint8_t, MfClassicKeyType"
Function,+,mf_classic_get_read_sectors_and_keys,void,"const MfClassicData*, uint8_t*, uint8_t*"
Function,+,mf_classic_get_sector_by_block,uint8_t,uint8_t
Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"const MfClassicData*, uint8_t"
@@ -3668,8 +3669,6 @@ Function,+,text_box_reset,void,TextBox*
Function,+,text_box_set_focus,void,"TextBox*, TextBoxFocus"
Function,+,text_box_set_font,void,"TextBox*, TextBoxFont"
Function,+,text_box_set_text,void,"TextBox*, const char*"
Function,+,text_input_add_extra_symbol,void,"TextInput*, char"
Function,+,text_input_add_illegal_symbols,void,TextInput*
Function,+,text_input_alloc,TextInput*,
Function,+,text_input_free,void,TextInput*
Function,+,text_input_get_validator_callback,TextInputValidatorCallback,TextInput*
@@ -3680,6 +3679,7 @@ Function,+,text_input_set_header_text,void,"TextInput*, const char*"
Function,+,text_input_set_minimum_length,void,"TextInput*, size_t"
Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool"
Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*"
Function,+,text_input_show_illegal_symbols,void,"TextInput*, _Bool"
Function,-,tgamma,double,double
Function,-,tgammaf,float,float
Function,-,tgammal,long double,long double
1 entry status name type params
975 Function + dir_walk_set_filter_cb void DirWalk*, DirWalkFilterCb, void*
976 Function + dir_walk_set_recurse_filter void DirWalk*, const char**, size_t
977 Function + dir_walk_set_recursive void DirWalk*, _Bool
978 Function - + div div_t int, int
979 Function + dolphin_deed void DolphinDeed
980 Function + dolphin_deed_get_app DolphinApp DolphinDeed
981 Function + dolphin_deed_get_app_limit uint8_t DolphinApp
2587 Function + mf_classic_get_blocks_num_in_sector uint8_t uint8_t
2588 Function + mf_classic_get_device_name const char* const MfClassicData*, NfcDeviceNameType
2589 Function + mf_classic_get_first_block_num_of_sector uint8_t uint8_t
2590 Function + mf_classic_get_key MfClassicKey const MfClassicData*, uint8_t, MfClassicKeyType
2591 Function + mf_classic_get_read_sectors_and_keys void const MfClassicData*, uint8_t*, uint8_t*
2592 Function + mf_classic_get_sector_by_block uint8_t uint8_t
2593 Function + mf_classic_get_sector_trailer_by_sector MfClassicSectorTrailer* const MfClassicData*, uint8_t
3669 Function + text_box_set_focus void TextBox*, TextBoxFocus
3670 Function + text_box_set_font void TextBox*, TextBoxFont
3671 Function + text_box_set_text void TextBox*, const char*
Function + text_input_add_extra_symbol void TextInput*, char
Function + text_input_add_illegal_symbols void TextInput*
3672 Function + text_input_alloc TextInput*
3673 Function + text_input_free void TextInput*
3674 Function + text_input_get_validator_callback TextInputValidatorCallback TextInput*
3679 Function + text_input_set_minimum_length void TextInput*, size_t
3680 Function + text_input_set_result_callback void TextInput*, TextInputCallback, void*, char*, size_t, _Bool
3681 Function + text_input_set_validator void TextInput*, TextInputValidatorCallback, void*
3682 Function + text_input_show_illegal_symbols void TextInput*, _Bool
3683 Function - tgamma double double
3684 Function - tgammaf float float
3685 Function - tgammal long double long double