Compare commits
75 Commits
unlshd-083
...
5c539d2346
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c539d2346 | ||
|
|
3bfeea9962 | ||
|
|
0a7eb30a15 | ||
|
|
caad1ef268 | ||
|
|
d10a601109 | ||
|
|
32a182c439 | ||
|
|
1f676cffea | ||
|
|
a28b2477f9 | ||
|
|
c08cb33a76 | ||
|
|
59ac0f211c | ||
|
|
c8e756a3c5 | ||
|
|
13b79f0246 | ||
|
|
05925868d2 | ||
|
|
e2e839fb2c | ||
|
|
0b7c9e2eab | ||
|
|
6f69f39fa1 | ||
|
|
ab593a7a8f | ||
|
|
7409b51da5 | ||
|
|
0f3eb9ae12 | ||
|
|
6abd2b0e9f | ||
|
|
a7561bee98 | ||
|
|
3821c9049e | ||
|
|
5bd0f642dd | ||
|
|
b8bec12974 | ||
|
|
a493612444 | ||
|
|
259efadaea | ||
|
|
ecbeb658e1 | ||
|
|
cd79d4f689 | ||
|
|
7fd30911fe | ||
|
|
52b76e2e2e | ||
|
|
b0c1931caf | ||
|
|
1c07f94cdd | ||
|
|
0351818b75 | ||
|
|
b391cfc71d | ||
|
|
da3a2834d4 | ||
|
|
d320c4a46b | ||
|
|
d1f8ddc033 | ||
|
|
d13b43f193 | ||
|
|
01cc4cc3da | ||
|
|
7ee266752e | ||
|
|
48b9dd2cc8 | ||
|
|
0530eda8d1 | ||
|
|
9d1cee6d4c | ||
|
|
b7d2ab7a0c | ||
|
|
a1c48c82f8 | ||
|
|
7518dc73c2 | ||
|
|
f4c92dcd76 | ||
|
|
c873eb5e36 | ||
|
|
a42e30f394 | ||
|
|
6d93a90a0a | ||
|
|
6c83a67173 | ||
|
|
22ee3bdae2 | ||
|
|
b54d63037b | ||
|
|
6a5ae6cc0d | ||
|
|
67b906e6ba | ||
|
|
772c944163 | ||
|
|
7f7d1e1d32 | ||
|
|
4f5ab0b15b | ||
|
|
f177c0491d | ||
|
|
c87205fe9a | ||
|
|
79fc832356 | ||
|
|
944c5ffb98 | ||
|
|
ed2c40de4b | ||
|
|
e392bff808 | ||
|
|
c37c5574ba | ||
|
|
5db6a03811 | ||
|
|
a553bc2f57 | ||
|
|
3e96806962 | ||
|
|
cf5761860f | ||
|
|
01c168e351 | ||
|
|
a1c8dfb61b | ||
|
|
bd02e2f53c | ||
|
|
94076e6c5c | ||
|
|
d673fd5573 | ||
|
|
9506ccde2e |
68
CHANGELOG.md
@@ -1,51 +1,29 @@
|
||||
## Main changes
|
||||
- Current API: 87.0
|
||||
* SubGHz: Add support for **Came Atomo (TOP44RBN)** remotes (thanks @mishamyte for recordings)
|
||||
* SubGHz: Add **Elplast 18bit** static code protocol (hello Hackcat ^_^)
|
||||
* SubGHz: Try to **decode BFT** (2 buttons remotes only) **on the fly** in regular Read mode (no more KL Unknown and all of that for free?!) (for 4 button remote follow docs [here](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md))
|
||||
* SubGHz: **Tune Linear** (edited by @WillyJL in PR #919 #920) (add better EZCode support) and **Dickert MAHS** protocol decoders
|
||||
* SubGHz: RAW protocol fixes (by @WillyJL)
|
||||
* SubGHz: Add **ZKTeco 430.5 MHz** add manually support
|
||||
* SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9)
|
||||
* SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding
|
||||
* NFC: Returning fix for reading PWD locked MFUL (PR #922 | by @mishamyte)
|
||||
* NFC: Added UL-C keys to the dictionary (PR #923 | by @mishamyte)
|
||||
* NFC: Add MIFARE Classic "Show Keys" UI (by @aaronjamt)
|
||||
* Apps: HID PTT: adding global zoom and google meet shortcuts for MacOS (PR #921 | by @hryamzik)
|
||||
* OFW: NFC FeliCa: Service Directory Traverse + Dump All Unencrypted-Readable Services' Blocks
|
||||
* OFW: **NFC CLI commands**
|
||||
* OFW: LFRFID: **Show ISO-3166 Country Names For Pet Chips**
|
||||
* OFW: **JS views finished**
|
||||
* OFW: BLE: improved pairing security
|
||||
* OFW: FeliCa Emulation: Handle certain Polling commands in firmware
|
||||
* OFW PR 4287: Fix Ultralight EV1 regression (by @noproto)
|
||||
* OFW PR 4271: NFC: **Ultralight C NFC App Key Management, Dictionary Attack** (by @noproto)
|
||||
* OFW PR 4265: NFC: **Fix read crash** with unexpectedly large MFC AUTH(0) response (by @WillyJL)
|
||||
* OFW PR 4251: CLI: **Fix long delay** with quick connect/disconnect (by @WillyJL)
|
||||
* LFRFID: Add additional procotols supported by **EM4305** chipset (by @jamisonderek)
|
||||
- Current API: 87.1
|
||||
* SubGHz: **Counter Edit option with UI** (PR #933 | by @Dmitry422) (with Debug enabled only) (Saved - open file - Signal Settings - Edit Counter)
|
||||
* SubGHz: **UI for Counter Experimental Mode** (PR #930 | by @Dmitry422) (with Debug enabled only) (Saved - open file - Signal Settings - Counter Mode) (see docs below)
|
||||
* SubGHz: **Counter modes for Keeloq, CAME Atomo, Nice Flor S, AlutechAT4N** - [see docs](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzCounterMode.md)
|
||||
* SubGHz: Add AN-Motors AT4 button on arrow keys (0xC)
|
||||
* SubGHz: Add IL-100 Smart support for Add manually
|
||||
* SubGHz: Add **experimental counter overflow mode** (OFEX), replicates how some key duplicators work, DO NOT USE if you don't know what you are doing, it will reset your counter value! (accesible with debug on in radio settings - counter incr.)
|
||||
* SubGHz: **Return Honeywell Sec** with fixes and improvements (by htotoo & LiQuiDz & xMasterX)
|
||||
* NFC: Keys found in key cache are now used in Nested attacks, deleting key cache is no longer required (by @noproto)
|
||||
* NFC: MFKey 4.0, MIFARE Classic Static Encrypted Nested attacks run 10x faster (by @noproto)
|
||||
* NFC: **Add Saflok MFUL Parser Support** (by @aaronjamt)
|
||||
* NFC: **Add MFUL counters to Info page** (by @aaronjamt)
|
||||
* OFW: Fix Felica standard loading from nfc file
|
||||
* Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
|
||||
## Other changes
|
||||
* SubGHz: Fix crash in add manually menu
|
||||
* OFW PR 4293: NFC FeliCa Improvement: Dump All Systems (by @zinongli)
|
||||
* OFW PR 4285: ViewStack: Store View by value to save memory (by @CookiePLMonster)
|
||||
* OFW PR 4290: Storage: Dont send mount event if SD mounted at boot (by @WillyJL)
|
||||
* OFW PR 4283: NFC lib: Expose nfc_common.h (by @zinongli)
|
||||
* OFW: Fix wrbl command tooltip
|
||||
* OFW: VSCode: Reduce file watcher resource usage
|
||||
* OFW: cli: Buzzer command
|
||||
* OFW: Update demo_windows.txt
|
||||
* OFW: Fix PVS warnings
|
||||
* OFW: NFC: Amusement IC Card Parser (FeliCa Lite & Lite-S)
|
||||
* OFW: hid_app mouse clicker: make mouse button selectable
|
||||
* OFW: JS: Expose button event type in gui/widget button callback
|
||||
* OFW: NFC: MFC 1k Banapass Parser
|
||||
* OFW: GUI Bug Fix: Number Input Save Icon
|
||||
* Add possibility to use custom buttons when using the SubGHz remote app (by @MrLego8-9)
|
||||
* Input Settings: Add Vibro Trigger option (by @956MB & @WillyJL)
|
||||
* BT Remote: Add Rename Option (by @aaronjamt & @WillyJL)
|
||||
* Simplify Bad USB BLE profile (by @aaronjamt & @WillyJL)
|
||||
* NFC: Fix incorrect Saflok year formula (by @Eltrick)
|
||||
* JS: Expose button event type in gui/widget button callback (by @WillyJL)
|
||||
* SubGHz: OFEX support for SecPlus v1 and v2, various fixes (by @Dmitry422 & xMasterX)
|
||||
* SubGHz Remote: Add default remote and clear slot features (by @jknlsn)
|
||||
* Fix typo in README warning about scammers (PR #931 | by @koterba)
|
||||
* Bad USB: Colemak keyboard layout (by @Ashe-Sterling)
|
||||
* Display: Remove display_back_light bug from "DisplayBacklightEnforceOn" (PR #928 | by @Dmitry422)
|
||||
* OFW PR 4279: NFC FeliCa Minor Fix: FelicaPollerEventType should only be Incomplete if the tag is FeliCa Lite (by @zinongli)
|
||||
* OFW PR 4261: Add date/time input module (by @aaronjamt)
|
||||
* OFW PR 4312: Infrared: Fix infrared CLI plugin MissingImports (by @WillyJL)
|
||||
* Dolphin: Enable winter anims
|
||||
* Dolphin: Disable halloween anim
|
||||
<br><br>
|
||||
#### Known NFC post-refactor regressions list:
|
||||
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
|
||||
|
||||
@@ -18,7 +18,7 @@ This firmware is a fork of the original (OFW) version of [flipperdevices/flipper
|
||||
>
|
||||
> This project is developed independently and is not affiliated with Flipper Devices.
|
||||
>
|
||||
> Also be aware, DarkFlippers/unleashed-firmware is the only official page of the project, there is no paid, premium or closed source versions and if someone contacts you and say they are from our team and try to offer something like that - they are scammers, block that users ASAP
|
||||
> Also be aware, DarkFlippers/unleashed-firmware is the only official page of the project, there is no paid, premium or closed source versions and if someone contacts you and say they are from our team and try to offer something like that - they are scammers, block that user ASAP
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -81,7 +81,7 @@ Before getting started:
|
||||
> - FAAC SLH, BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis, (right arrow button for other protocols))
|
||||
> - Debug mode counter increase settings (+1 → +5, +10, default: +1)
|
||||
> - Debug PIN output settings for protocol development
|
||||
> - Ignore options - Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)
|
||||
> - Ignore options - Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell Sec, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)
|
||||
> </details>
|
||||
|
||||
> <details>
|
||||
@@ -273,7 +273,8 @@ Enhance your Flipper Zero with apps and plugins created by the community:
|
||||
|
||||
### ![SubGhz Icon Badge] Sub-GHz
|
||||
|
||||
- [How to use Flipper as new remote (Nice FlorS, BFT Mitto, Somfy Telis, Aprimatic, AN-Motors, etc..)](/documentation/SubGHzRemoteProg.md)
|
||||
- [How to use Flipper as rolling code remote (Doorhan, Nice FlorS, BFT Mitto, Somfy Telis, Aprimatic, AN-Motors, etc..)](/documentation/SubGHzRemoteProg.md)
|
||||
- [Experimental rolling code counter modes (avoid desync)](/documentation/SubGHzCounterMode.md)
|
||||
- External Radio: [How to connect CC1101 module](https://github.com/quen0n/flipperzero-ext-cc1101)
|
||||
- Transmission is blocked? [How to extend Sub-GHz frequency range](/documentation/DangerousSettings.md)
|
||||
- [How to add extra Sub-GHz frequencies](/documentation/SubGHzSettings.md)
|
||||
|
||||
@@ -131,6 +131,7 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="get_api",
|
||||
requires=["unit_tests"],
|
||||
fap_libs=["infrared"],
|
||||
)
|
||||
|
||||
App(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <flipper_format.h>
|
||||
#include <infrared.h>
|
||||
#include <common/infrared_common_i.h>
|
||||
#include <lib/infrared/signal/infrared_brute_force.h>
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#define IR_TEST_FILES_DIR EXT_PATH("unit_tests/infrared/")
|
||||
@@ -13,6 +14,7 @@ typedef struct {
|
||||
InfraredEncoderHandler* encoder_handler;
|
||||
FuriString* file_path;
|
||||
FlipperFormat* ff;
|
||||
InfraredBruteForce* brutedb;
|
||||
} InfraredTest;
|
||||
|
||||
static InfraredTest* test;
|
||||
@@ -24,12 +26,14 @@ static void infrared_test_alloc(void) {
|
||||
test->encoder_handler = infrared_alloc_encoder();
|
||||
test->ff = flipper_format_buffered_file_alloc(storage);
|
||||
test->file_path = furi_string_alloc();
|
||||
test->brutedb = infrared_brute_force_alloc();
|
||||
}
|
||||
|
||||
static void infrared_test_free(void) {
|
||||
furi_check(test);
|
||||
infrared_free_decoder(test->decoder_handler);
|
||||
infrared_free_encoder(test->encoder_handler);
|
||||
infrared_brute_force_free(test->brutedb);
|
||||
flipper_format_free(test->ff);
|
||||
furi_string_free(test->file_path);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
@@ -523,6 +527,74 @@ MU_TEST(infrared_test_encoder_decoder_all) {
|
||||
infrared_test_run_encoder_decoder(InfraredProtocolPioneer, 1);
|
||||
}
|
||||
|
||||
MU_TEST(infrared_test_ac_database) {
|
||||
infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/ac.ir"));
|
||||
uint32_t i = 0;
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Off");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Dh");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Cool_hi");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Heat_hi");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Cool_lo");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Heat_lo");
|
||||
|
||||
mu_assert(
|
||||
infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone,
|
||||
"universal ac database is invalid");
|
||||
|
||||
infrared_brute_force_reset(test->brutedb);
|
||||
}
|
||||
|
||||
MU_TEST(infrared_test_audio_database) {
|
||||
infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/audio.ir"));
|
||||
uint32_t i = 0;
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Power");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Mute");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Play");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Pause");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Prev");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Next");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Vol_dn");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Vol_up");
|
||||
|
||||
mu_assert(
|
||||
infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone,
|
||||
"universal audio database is invalid");
|
||||
|
||||
infrared_brute_force_reset(test->brutedb);
|
||||
}
|
||||
|
||||
MU_TEST(infrared_test_projector_database) {
|
||||
infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/projector.ir"));
|
||||
uint32_t i = 0;
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Power");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Mute");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Vol_up");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Vol_dn");
|
||||
|
||||
mu_assert(
|
||||
infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone,
|
||||
"universal projector database is invalid");
|
||||
|
||||
infrared_brute_force_reset(test->brutedb);
|
||||
}
|
||||
|
||||
MU_TEST(infrared_test_tv_database) {
|
||||
infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/tv.ir"));
|
||||
uint32_t i = 0;
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Power");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Mute");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Vol_up");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Ch_next");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Vol_dn");
|
||||
infrared_brute_force_add_record(test->brutedb, i++, "Ch_prev");
|
||||
|
||||
mu_assert(
|
||||
infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone,
|
||||
"universal tv database is invalid");
|
||||
|
||||
infrared_brute_force_reset(test->brutedb);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(infrared_test) {
|
||||
MU_SUITE_CONFIGURE(&infrared_test_alloc, &infrared_test_free);
|
||||
|
||||
@@ -543,6 +615,10 @@ MU_TEST_SUITE(infrared_test) {
|
||||
MU_RUN_TEST(infrared_test_decoder_pioneer);
|
||||
MU_RUN_TEST(infrared_test_decoder_mixed);
|
||||
MU_RUN_TEST(infrared_test_encoder_decoder_all);
|
||||
MU_RUN_TEST(infrared_test_ac_database);
|
||||
MU_RUN_TEST(infrared_test_audio_database);
|
||||
MU_RUN_TEST(infrared_test_projector_database);
|
||||
MU_RUN_TEST(infrared_test_tv_database);
|
||||
}
|
||||
|
||||
int run_minunit_test_infrared(void) {
|
||||
|
||||
13
applications/examples/example_date_time_input/ReadMe.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Date/Time Input {#example_date_time_input}
|
||||
|
||||
Simple view that allows the user to adjust a date and/or time.
|
||||
|
||||
## Source code
|
||||
|
||||
Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_date_time_input).
|
||||
|
||||
## General principle
|
||||
|
||||
Callbacks can be defined for every time a value is edited (useful for application-specific bounds checking or validation) and for when the user is done editing (back button is pressed). The provided DateTime object is used both as the initial value and as the place where the result is stored.
|
||||
|
||||
The fields which the user is allowed to edit can be defined using `date_time_input_set_editable_fields()`. Disabled fields are shown but aren't able to be selected and don't have an outer box. If all fields are disabled, the view is read-only and no cursor will be shown.
|
||||
@@ -0,0 +1,9 @@
|
||||
App(
|
||||
appid="example_date_time_input",
|
||||
name="Example: Date/Time Input",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="example_date_time_input",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "example_date_time_input.h"
|
||||
|
||||
bool example_date_time_input_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
ExampleDateTimeInput* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool example_date_time_input_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleDateTimeInput* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static ExampleDateTimeInput* example_date_time_input_alloc() {
|
||||
ExampleDateTimeInput* app = malloc(sizeof(ExampleDateTimeInput));
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&example_date_time_input_scene_handlers, app);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, example_date_time_input_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, example_date_time_input_back_event_callback);
|
||||
|
||||
app->date_time_input = date_time_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
ExampleDateTimeInputViewIdDateTimeInput,
|
||||
date_time_input_get_view(app->date_time_input));
|
||||
|
||||
app->dialog_ex = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
ExampleDateTimeInputViewIdShowDateTime,
|
||||
dialog_ex_get_view(app->dialog_ex));
|
||||
|
||||
// Fill in current date & time
|
||||
furi_hal_rtc_get_datetime(&app->date_time);
|
||||
app->edit_date = false;
|
||||
app->edit_time = false;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void example_date_time_input_free(ExampleDateTimeInput* app) {
|
||||
furi_assert(app);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ExampleDateTimeInputViewIdShowDateTime);
|
||||
dialog_ex_free(app->dialog_ex);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ExampleDateTimeInputViewIdDateTimeInput);
|
||||
date_time_input_free(app->date_time_input);
|
||||
|
||||
scene_manager_free(app->scene_manager);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
app->gui = NULL;
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t example_date_time_input(void* p) {
|
||||
UNUSED(p);
|
||||
ExampleDateTimeInput* app = example_date_time_input_alloc();
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, ExampleDateTimeInputSceneShowDateTime);
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
example_date_time_input_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/date_time_input.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#include "scenes/example_date_time_input_scene.h"
|
||||
|
||||
typedef struct ExampleDateTimeInputShowDateTime ExampleDateTimeInputShowDateTime;
|
||||
|
||||
typedef enum {
|
||||
ExampleDateTimeInputViewIdShowDateTime,
|
||||
ExampleDateTimeInputViewIdDateTimeInput,
|
||||
} ExampleDateTimeInputViewId;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
SceneManager* scene_manager;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
|
||||
DateTimeInput* date_time_input;
|
||||
DialogEx* dialog_ex;
|
||||
|
||||
DateTime date_time;
|
||||
|
||||
bool edit_date;
|
||||
bool edit_time;
|
||||
} ExampleDateTimeInput;
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "example_date_time_input_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const example_date_time_input_on_enter_handlers[])(void*) = {
|
||||
#include "example_date_time_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const example_date_time_input_on_event_handlers[])(void* context, SceneManagerEvent event) =
|
||||
{
|
||||
#include "example_date_time_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const example_date_time_input_on_exit_handlers[])(void* context) = {
|
||||
#include "example_date_time_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers example_date_time_input_scene_handlers = {
|
||||
.on_enter_handlers = example_date_time_input_on_enter_handlers,
|
||||
.on_event_handlers = example_date_time_input_on_event_handlers,
|
||||
.on_exit_handlers = example_date_time_input_on_exit_handlers,
|
||||
.scene_num = ExampleDateTimeInputSceneNum,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) ExampleDateTimeInputScene##id,
|
||||
typedef enum {
|
||||
#include "example_date_time_input_scene_config.h"
|
||||
ExampleDateTimeInputSceneNum,
|
||||
} ExampleDateTimeInputScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers example_date_time_input_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "example_date_time_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "example_date_time_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "example_date_time_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -0,0 +1,2 @@
|
||||
ADD_SCENE(example_date_time_input, input_date_time, InputDateTime)
|
||||
ADD_SCENE(example_date_time_input, show_date_time, ShowDateTime)
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "../example_date_time_input.h"
|
||||
|
||||
void example_date_time_input_scene_input_date_time_callback(void* context) {
|
||||
ExampleDateTimeInput* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||
}
|
||||
|
||||
void example_date_time_input_scene_input_date_time_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleDateTimeInput* app = context;
|
||||
DateTimeInput* date_time_input = app->date_time_input;
|
||||
|
||||
date_time_input_set_result_callback(
|
||||
date_time_input,
|
||||
NULL,
|
||||
example_date_time_input_scene_input_date_time_callback,
|
||||
context,
|
||||
&app->date_time);
|
||||
|
||||
date_time_input_set_editable_fields(
|
||||
date_time_input,
|
||||
|
||||
app->edit_date,
|
||||
app->edit_date,
|
||||
app->edit_date,
|
||||
|
||||
app->edit_time,
|
||||
app->edit_time,
|
||||
app->edit_time);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleDateTimeInputViewIdDateTimeInput);
|
||||
}
|
||||
|
||||
bool example_date_time_input_scene_input_date_time_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleDateTimeInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) { //Back button pressed
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_date_time_input_scene_input_date_time_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "../example_date_time_input.h"
|
||||
|
||||
static void
|
||||
example_date_time_input_scene_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
ExampleDateTimeInput* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
static void example_date_time_input_scene_update_view(void* context) {
|
||||
ExampleDateTimeInput* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "The date and time are", 64, 0, AlignCenter, AlignTop);
|
||||
|
||||
uint8_t hour = app->date_time.hour;
|
||||
char label_hour[4] = "";
|
||||
if(furi_hal_rtc_get_locale_timeformat() == FuriHalRtcLocaleTimeFormat12h) {
|
||||
if(hour < 12) {
|
||||
snprintf(label_hour, sizeof(label_hour), " AM");
|
||||
} else {
|
||||
snprintf(label_hour, sizeof(label_hour), " PM");
|
||||
}
|
||||
hour %= 12;
|
||||
if(hour == 0) hour = 12;
|
||||
}
|
||||
|
||||
char buffer[29] = {};
|
||||
snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"%04d-%02d-%02d\n%02d:%02d:%02d%s",
|
||||
app->date_time.year,
|
||||
app->date_time.month,
|
||||
app->date_time.day,
|
||||
hour,
|
||||
app->date_time.minute,
|
||||
app->date_time.second,
|
||||
label_hour);
|
||||
dialog_ex_set_text(dialog_ex, buffer, 64, 29, AlignCenter, AlignCenter);
|
||||
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Date");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "Time");
|
||||
dialog_ex_set_center_button_text(dialog_ex, "Both");
|
||||
|
||||
dialog_ex_set_result_callback(
|
||||
dialog_ex, example_date_time_input_scene_confirm_dialog_callback);
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
}
|
||||
|
||||
void example_date_time_input_scene_show_date_time_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleDateTimeInput* app = context;
|
||||
|
||||
example_date_time_input_scene_update_view(app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleDateTimeInputViewIdShowDateTime);
|
||||
}
|
||||
|
||||
bool example_date_time_input_scene_show_date_time_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleDateTimeInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultCenter:
|
||||
app->edit_date = true;
|
||||
app->edit_time = true;
|
||||
scene_manager_next_scene(app->scene_manager, ExampleDateTimeInputSceneInputDateTime);
|
||||
consumed = true;
|
||||
break;
|
||||
case DialogExResultLeft:
|
||||
app->edit_date = true;
|
||||
app->edit_time = false;
|
||||
scene_manager_next_scene(app->scene_manager, ExampleDateTimeInputSceneInputDateTime);
|
||||
consumed = true;
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
app->edit_date = false;
|
||||
app->edit_time = true;
|
||||
scene_manager_next_scene(app->scene_manager, ExampleDateTimeInputSceneInputDateTime);
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_date_time_input_scene_show_date_time_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ App(
|
||||
order=40,
|
||||
sources=["*.c", "!infrared_cli.c"],
|
||||
resources="resources",
|
||||
fap_libs=["assets"],
|
||||
fap_libs=["assets", "infrared"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="Infrared",
|
||||
)
|
||||
@@ -20,9 +20,6 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_ir_ep",
|
||||
requires=["cli"],
|
||||
sources=[
|
||||
"infrared_cli.c",
|
||||
"infrared_brute_force.c",
|
||||
"infrared_signal.c",
|
||||
],
|
||||
sources=["infrared_cli.c"],
|
||||
fap_libs=["infrared"],
|
||||
)
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
* @brief Infrared application - start here.
|
||||
*
|
||||
* @see infrared_app_i.h for the main application data structure and functions.
|
||||
* @see infrared_signal.h for the infrared signal library - loading, storing and transmitting signals.
|
||||
* @see infrared_remote.hl for the infrared remote library - loading, storing and manipulating remotes.
|
||||
* @see infrared_brute_force.h for the infrared brute force - loading and transmitting multiple signals.
|
||||
* @see infrared_remote.h for the infrared remote library - loading, storing and manipulating remotes
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
#include "infrared_app.h"
|
||||
#include "infrared_remote.h"
|
||||
#include "infrared_brute_force.h"
|
||||
#include <lib/infrared/signal/infrared_brute_force.h>
|
||||
#include "infrared_custom_event.h"
|
||||
|
||||
#include "scenes/infrared_scene.h"
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#include <toolbox/pipe.h>
|
||||
#include <m-dict.h>
|
||||
|
||||
#include "infrared_signal.h"
|
||||
#include "infrared_brute_force.h"
|
||||
#include <lib/infrared/signal/infrared_signal.h>
|
||||
#include <lib/infrared/signal/infrared_brute_force.h>
|
||||
|
||||
#define INFRARED_CLI_BUF_SIZE (10U)
|
||||
#define INFRARED_CLI_FILE_NAME_SIZE (256U)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "infrared_signal.h"
|
||||
#include <lib/infrared/signal/infrared_signal.h>
|
||||
|
||||
/**
|
||||
* @brief InfraredRemote opaque type declaration.
|
||||
|
||||
@@ -369,8 +369,19 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="saflok_parser",
|
||||
appid="saflok_mfc_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
cdefines=[("SL_PROTO", "SL_PROTO_MFC")],
|
||||
entry_point="saflok_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/saflok.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="saflok_ul_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
cdefines=[("SL_PROTO", "SL_PROTO_UL")],
|
||||
entry_point="saflok_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
|
||||
@@ -9,6 +9,11 @@ static void nfc_render_mf_ultralight_pages_count(const MfUltralightData* data, F
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_render_mf_ultralight_counters(const MfUltralightData* data, FuriString* str) {
|
||||
for(uint8_t i = 0; i < MF_ULTRALIGHT_COUNTER_NUM; i++)
|
||||
furi_string_cat_printf(str, "\nCounter %u: %lu", i, data->counter[i].counter);
|
||||
}
|
||||
|
||||
void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str) {
|
||||
MfUltralightConfigPages* config;
|
||||
|
||||
@@ -44,6 +49,8 @@ void nfc_render_mf_ultralight_info(
|
||||
nfc_render_iso14443_3a_info(data->iso14443_3a_data, format_type, str);
|
||||
|
||||
nfc_render_mf_ultralight_pages_count(data, str);
|
||||
|
||||
nfc_render_mf_ultralight_counters(data, str);
|
||||
}
|
||||
|
||||
void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str) {
|
||||
|
||||
@@ -113,6 +113,9 @@ typedef struct {
|
||||
uint16_t nested_target_key;
|
||||
uint16_t msb_count;
|
||||
bool enhanced_dict;
|
||||
uint16_t current_key_idx; // Current key index for CUID dictionary mode
|
||||
uint8_t*
|
||||
cuid_key_indices_bitmap; // Bitmap of key indices present in CUID dictionary (256 bits = 32 bytes)
|
||||
} NfcMfClassicDictAttackContext;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <flipper_application.h>
|
||||
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
|
||||
|
||||
#include <nfc_app_i.h>
|
||||
#include <bit_lib.h>
|
||||
|
||||
@@ -18,9 +20,24 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
|
||||
#define TAG "Saflok"
|
||||
|
||||
#define MAGIC_TABLE_SIZE 192
|
||||
#define MAGIC_TABLE_SIZE 192
|
||||
#define SL_PROTO_INVALID (-1)
|
||||
#define SL_PROTO_MFC (0)
|
||||
#define SL_PROTO_UL (1)
|
||||
#define SL_PROTO_TOTAL (2)
|
||||
|
||||
#ifndef SL_PROTO
|
||||
#error Must specify what protocol to use with SL_PROTO define!
|
||||
#endif
|
||||
#if SL_PROTO <= SL_PROTO_INVALID || SL_PROTO >= SL_PROTO_TOTAL
|
||||
#error Invalid SL_PROTO specified!
|
||||
#endif
|
||||
|
||||
#define KEY_LENGTH 6
|
||||
#define UID_LENGTH 4
|
||||
#define CHECK_SECTOR 1
|
||||
@@ -255,25 +272,40 @@ static bool saflok_read(Nfc* nfc, NfcDevice* device) {
|
||||
|
||||
bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
#if SL_PROTO == SL_PROTO_MFC
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
#elif SL_PROTO == SL_PROTO_UL
|
||||
const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight);
|
||||
#endif
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
#if SL_PROTO == SL_PROTO_MFC
|
||||
// Check card type
|
||||
if(data->type != MfClassicType1k) break;
|
||||
|
||||
// Verify key
|
||||
const MfClassicSectorTrailer* sec_tr =
|
||||
mf_classic_get_sector_trailer_by_sector(data, CHECK_SECTOR);
|
||||
|
||||
const uint64_t key_a =
|
||||
bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||
if(key_a != saflok_1k_keys[CHECK_SECTOR].a) break;
|
||||
|
||||
// Decrypt basic access
|
||||
// Init basic access
|
||||
uint8_t basicAccess[BASIC_ACCESS_BYTE_NUM];
|
||||
memcpy(&basicAccess, &data->block[1].data, 16);
|
||||
memcpy(&basicAccess[16], &data->block[2].data[0], 1);
|
||||
#elif SL_PROTO == SL_PROTO_UL
|
||||
// Check card type
|
||||
if(data->type != MfUltralightTypeMfulC) break;
|
||||
// Init basic access
|
||||
uint8_t basicAccess[BASIC_ACCESS_BYTE_NUM];
|
||||
memcpy(&basicAccess[0 * 4], &data->page[34].data, 4);
|
||||
memcpy(&basicAccess[1 * 4], &data->page[35].data, 4);
|
||||
memcpy(&basicAccess[2 * 4], &data->page[36].data, 4);
|
||||
memcpy(&basicAccess[3 * 4], &data->page[37].data, 4);
|
||||
memcpy(&basicAccess[4 * 4], &data->page[38].data[0], 1);
|
||||
#endif
|
||||
|
||||
// Decrypt basic access
|
||||
uint8_t decodedBA[BASIC_ACCESS_BYTE_NUM];
|
||||
DecryptCard(basicAccess, BASIC_ACCESS_BYTE_NUM, decodedBA);
|
||||
|
||||
@@ -290,8 +322,7 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
uint16_t key_record = (key_record_high << 8) | decodedBA[3];
|
||||
|
||||
// Byte 4 & 5: Pass level in reversed binary
|
||||
// This part is commented because the relevance of this info is still unknown
|
||||
// uint16_t pass_level = ((decodedBA[4] & 0xFF) << 8) | decodedBA[5];
|
||||
uint16_t pass_level = ((decodedBA[4] & 0xFF) << 8) | decodedBA[5];
|
||||
// uint8_t pass_levels[12];
|
||||
// int pass_levels_count = 0;
|
||||
|
||||
@@ -390,26 +421,31 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
uint8_t checksum = decodedBA[16];
|
||||
uint8_t checksum_calculated = CalculateCheckSum(decodedBA);
|
||||
bool checksum_valid = (checksum_calculated == checksum);
|
||||
for(int i = 0; i < 17; i++) {
|
||||
for(int i = 0; i < BASIC_ACCESS_BYTE_NUM; i++) {
|
||||
FURI_LOG_D(TAG, "%02X", decodedBA[i]);
|
||||
}
|
||||
FURI_LOG_D(TAG, "CS decrypted: %02X", checksum);
|
||||
FURI_LOG_D(TAG, "CS calculated: %02X", checksum_calculated);
|
||||
|
||||
furi_string_cat_printf(parsed_data, "\e#Saflok Card\n");
|
||||
#if SL_PROTO == SL_PROTO_MFC
|
||||
furi_string_cat_printf(parsed_data, "\e#Saflok MFC 1K Card\n");
|
||||
#elif SL_PROTO == SL_PROTO_UL
|
||||
furi_string_cat_printf(parsed_data, "\e#Saflok UL-C Card\n");
|
||||
#endif
|
||||
furi_string_cat_printf(parsed_data, "Property Number: %u\n", property_id);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Key Level: %u, %s\n",
|
||||
key_levels[key_level].level_num,
|
||||
key_levels[key_level].level_name);
|
||||
furi_string_cat_printf(parsed_data, "LED Exp. Warning: %s\n", led_warning ? "Yes" : "No");
|
||||
furi_string_cat_printf(parsed_data, "Key ID: %02X\n", key_id);
|
||||
furi_string_cat_printf(parsed_data, "Key Record: %04X\n", key_record);
|
||||
furi_string_cat_printf(parsed_data, "Opening key: %s\n", opening_key ? "Yes" : "No");
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Seq. & Combination: %04X\n", sequence_combination_number);
|
||||
furi_string_cat_printf(parsed_data, "Pass Level: %04X\n", pass_level);
|
||||
furi_string_cat_printf(parsed_data, "Opening Key: %s\n", opening_key ? "Yes" : "No");
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Override Deadbolt: %s\n", override_deadbolt ? "Yes" : "No");
|
||||
furi_string_cat_printf(parsed_data, "LED Exp. Warning: %s\n", led_warning ? "Yes" : "No");
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Restricted Weekday: %s\n",
|
||||
@@ -430,19 +466,33 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
expire_day,
|
||||
expire_hour,
|
||||
expire_minute);
|
||||
furi_string_cat_printf(parsed_data, "Property Number: %u\n", property_id);
|
||||
furi_string_cat_printf(parsed_data, "Checksum Valid: %s", checksum_valid ? "Yes" : "No");
|
||||
#if SL_PROTO == SL_PROTO_MFC
|
||||
// MFC returns parsed = true since we have proper verify and read functions
|
||||
parsed = true;
|
||||
#elif SL_PROTO == SL_PROTO_UL
|
||||
// UL returns parsed = checksum_valid since we don't have proper verify and read functions
|
||||
// TODO: change to true after verify and read are implemented
|
||||
parsed = checksum_valid;
|
||||
#endif
|
||||
|
||||
} while(false);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin saflok_plugin = {
|
||||
#if SL_PROTO == SL_PROTO_MFC
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = saflok_verify,
|
||||
.read = saflok_read,
|
||||
.parse = saflok_parse,
|
||||
#elif SL_PROTO == SL_PROTO_UL
|
||||
.protocol = NfcProtocolMfUltralight,
|
||||
.verify = NULL,
|
||||
.read = NULL,
|
||||
.parse = saflok_parse,
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
@@ -456,3 +506,5 @@ static const FlipperAppPluginDescriptor saflok_plugin_descriptor = {
|
||||
const FlipperAppPluginDescriptor* saflok_plugin_ep(void) {
|
||||
return &saflok_plugin_descriptor;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
@@ -2,12 +2,22 @@
|
||||
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <toolbox/stream/buffered_file_stream.h>
|
||||
|
||||
#define TAG "NfcMfClassicDictAttack"
|
||||
#define TAG "NfcMfClassicDictAttack"
|
||||
#define BIT(x, n) ((x) >> (n) & 1)
|
||||
|
||||
// TODO FL-3926: Fix lag when leaving the dictionary attack view after Hardnested
|
||||
// TODO FL-3926: Re-enters backdoor detection between user and system dictionary if no backdoor is found
|
||||
|
||||
// KeysDict structure definition for inline CUID dictionary allocation
|
||||
struct KeysDict {
|
||||
Stream* stream;
|
||||
size_t key_size;
|
||||
size_t key_size_symbols;
|
||||
size_t total_keys;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
DictAttackStateCUIDDictInProgress,
|
||||
DictAttackStateUserDictInProgress,
|
||||
@@ -31,11 +41,22 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context)
|
||||
instance->nfc_dict_context.is_card_present = false;
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost);
|
||||
} else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
|
||||
uint32_t state =
|
||||
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack);
|
||||
bool is_cuid_dict = (state == DictAttackStateCUIDDictInProgress);
|
||||
|
||||
const MfClassicData* mfc_data =
|
||||
nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
|
||||
mfc_event->data->poller_mode.mode = (instance->nfc_dict_context.enhanced_dict) ?
|
||||
MfClassicPollerModeDictAttackEnhanced :
|
||||
MfClassicPollerModeDictAttackStandard;
|
||||
|
||||
// Select mode based on dictionary type
|
||||
if(is_cuid_dict) {
|
||||
mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttackCUID;
|
||||
} else if(instance->nfc_dict_context.enhanced_dict) {
|
||||
mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttackEnhanced;
|
||||
} else {
|
||||
mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttackStandard;
|
||||
}
|
||||
|
||||
mfc_event->data->poller_mode.data = mfc_data;
|
||||
instance->nfc_dict_context.sectors_total =
|
||||
mf_classic_get_total_sectors_num(mfc_data->type);
|
||||
@@ -46,12 +67,57 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context)
|
||||
view_dispatcher_send_custom_event(
|
||||
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
||||
} else if(mfc_event->type == MfClassicPollerEventTypeRequestKey) {
|
||||
uint32_t state =
|
||||
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack);
|
||||
bool is_cuid_dict = (state == DictAttackStateCUIDDictInProgress);
|
||||
|
||||
MfClassicKey key = {};
|
||||
if(keys_dict_get_next_key(
|
||||
instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) {
|
||||
bool key_found = false;
|
||||
|
||||
if(is_cuid_dict) {
|
||||
// CUID dictionary: read 7 bytes (1 byte key_idx + 6 bytes key) and filter by exact key_idx
|
||||
uint16_t target_key_idx = instance->nfc_dict_context.current_key_idx;
|
||||
|
||||
// Check if this key index exists in the bitmap (only valid for 0-255)
|
||||
if(target_key_idx < 256 &&
|
||||
BIT(instance->nfc_dict_context.cuid_key_indices_bitmap[target_key_idx / 8],
|
||||
target_key_idx % 8)) {
|
||||
uint8_t key_with_idx[sizeof(MfClassicKey) + 1];
|
||||
|
||||
while(keys_dict_get_next_key(
|
||||
instance->nfc_dict_context.dict, key_with_idx, sizeof(MfClassicKey) + 1)) {
|
||||
// Extract key_idx from first byte
|
||||
uint8_t key_idx = key_with_idx[0];
|
||||
|
||||
instance->nfc_dict_context.dict_keys_current++;
|
||||
|
||||
// Only use key if it matches the exact current key index
|
||||
if(key_idx == (uint8_t)target_key_idx) {
|
||||
// Copy the actual key (starts at byte 1)
|
||||
memcpy(key.data, &key_with_idx[1], sizeof(MfClassicKey));
|
||||
key_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Standard dictionary: read 12 bytes
|
||||
if(keys_dict_get_next_key(
|
||||
instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) {
|
||||
key_found = true;
|
||||
instance->nfc_dict_context.dict_keys_current++;
|
||||
}
|
||||
}
|
||||
|
||||
if(key_found) {
|
||||
mfc_event->data->key_request_data.key = key;
|
||||
// In CUID mode, set key_type based on key_idx (odd = B, even = A)
|
||||
if(is_cuid_dict) {
|
||||
uint16_t target_key_idx = instance->nfc_dict_context.current_key_idx;
|
||||
mfc_event->data->key_request_data.key_type =
|
||||
(target_key_idx % 2 == 0) ? MfClassicKeyTypeA : MfClassicKeyTypeB;
|
||||
}
|
||||
mfc_event->data->key_request_data.key_provided = true;
|
||||
instance->nfc_dict_context.dict_keys_current++;
|
||||
if(instance->nfc_dict_context.dict_keys_current % 10 == 0) {
|
||||
view_dispatcher_send_custom_event(
|
||||
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
||||
@@ -72,10 +138,27 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context)
|
||||
view_dispatcher_send_custom_event(
|
||||
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
||||
} else if(mfc_event->type == MfClassicPollerEventTypeNextSector) {
|
||||
uint32_t state =
|
||||
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack);
|
||||
bool is_cuid_dict = (state == DictAttackStateCUIDDictInProgress);
|
||||
|
||||
keys_dict_rewind(instance->nfc_dict_context.dict);
|
||||
instance->nfc_dict_context.dict_keys_current = 0;
|
||||
instance->nfc_dict_context.current_sector =
|
||||
mfc_event->data->next_sector_data.current_sector;
|
||||
|
||||
// In CUID mode, increment the key index and calculate sector from it
|
||||
if(is_cuid_dict) {
|
||||
instance->nfc_dict_context.current_key_idx++;
|
||||
// Calculate sector from key_idx (each sector has 2 keys: A and B)
|
||||
instance->nfc_dict_context.current_sector =
|
||||
instance->nfc_dict_context.current_key_idx / 2;
|
||||
// Write back to event data so poller can read it
|
||||
mfc_event->data->next_sector_data.current_sector =
|
||||
instance->nfc_dict_context.current_sector;
|
||||
} else {
|
||||
instance->nfc_dict_context.current_sector =
|
||||
mfc_event->data->next_sector_data.current_sector;
|
||||
}
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
||||
} else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyA) {
|
||||
@@ -153,18 +236,51 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->nfc_dict_context.dict = keys_dict_alloc(
|
||||
furi_string_get_cstr(cuid_dict_path),
|
||||
KeysDictModeOpenExisting,
|
||||
sizeof(MfClassicKey));
|
||||
// Manually create KeysDict and scan once to count + populate bitmap
|
||||
KeysDict* dict = malloc(sizeof(KeysDict));
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
dict->stream = buffered_file_stream_alloc(storage);
|
||||
dict->key_size = sizeof(MfClassicKey) + 1;
|
||||
dict->key_size_symbols = dict->key_size * 2 + 1;
|
||||
dict->total_keys = 0;
|
||||
|
||||
if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) {
|
||||
keys_dict_free(instance->nfc_dict_context.dict);
|
||||
if(!buffered_file_stream_open(
|
||||
dict->stream,
|
||||
furi_string_get_cstr(cuid_dict_path),
|
||||
FSAM_READ_WRITE,
|
||||
FSOM_OPEN_EXISTING)) {
|
||||
buffered_file_stream_close(dict->stream);
|
||||
free(dict);
|
||||
state = DictAttackStateUserDictInProgress;
|
||||
break;
|
||||
}
|
||||
|
||||
// Allocate and populate bitmap of key indices present in CUID dictionary
|
||||
instance->nfc_dict_context.cuid_key_indices_bitmap = malloc(32);
|
||||
memset(instance->nfc_dict_context.cuid_key_indices_bitmap, 0, 32);
|
||||
|
||||
// Scan dictionary once to count keys and populate bitmap
|
||||
uint8_t key_with_idx[dict->key_size];
|
||||
while(keys_dict_get_next_key(dict, key_with_idx, dict->key_size)) {
|
||||
uint8_t key_idx = key_with_idx[0];
|
||||
// Set bit for this key index
|
||||
instance->nfc_dict_context.cuid_key_indices_bitmap[key_idx / 8] |=
|
||||
(1 << (key_idx % 8));
|
||||
dict->total_keys++;
|
||||
}
|
||||
keys_dict_rewind(dict);
|
||||
|
||||
if(dict->total_keys == 0) {
|
||||
keys_dict_free(dict);
|
||||
free(instance->nfc_dict_context.cuid_key_indices_bitmap);
|
||||
instance->nfc_dict_context.cuid_key_indices_bitmap = NULL;
|
||||
state = DictAttackStateUserDictInProgress;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->nfc_dict_context.dict = dict;
|
||||
dict_attack_set_header(instance->dict_attack, "MF Classic CUID Dictionary");
|
||||
instance->nfc_dict_context.current_key_idx = 0; // Initialize key index for CUID mode
|
||||
} while(false);
|
||||
|
||||
furi_string_free(cuid_dict_path);
|
||||
@@ -265,6 +381,10 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent
|
||||
nfc_poller_stop(instance->poller);
|
||||
nfc_poller_free(instance->poller);
|
||||
keys_dict_free(instance->nfc_dict_context.dict);
|
||||
if(instance->nfc_dict_context.cuid_key_indices_bitmap) {
|
||||
free(instance->nfc_dict_context.cuid_key_indices_bitmap);
|
||||
instance->nfc_dict_context.cuid_key_indices_bitmap = NULL;
|
||||
}
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager,
|
||||
NfcSceneMfClassicDictAttack,
|
||||
@@ -309,6 +429,10 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent
|
||||
nfc_poller_stop(instance->poller);
|
||||
nfc_poller_free(instance->poller);
|
||||
keys_dict_free(instance->nfc_dict_context.dict);
|
||||
if(instance->nfc_dict_context.cuid_key_indices_bitmap) {
|
||||
free(instance->nfc_dict_context.cuid_key_indices_bitmap);
|
||||
instance->nfc_dict_context.cuid_key_indices_bitmap = NULL;
|
||||
}
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager,
|
||||
NfcSceneMfClassicDictAttack,
|
||||
@@ -366,6 +490,12 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) {
|
||||
|
||||
keys_dict_free(instance->nfc_dict_context.dict);
|
||||
|
||||
// Free CUID bitmap if allocated
|
||||
if(instance->nfc_dict_context.cuid_key_indices_bitmap) {
|
||||
free(instance->nfc_dict_context.cuid_key_indices_bitmap);
|
||||
instance->nfc_dict_context.cuid_key_indices_bitmap = NULL;
|
||||
}
|
||||
|
||||
instance->nfc_dict_context.current_sector = 0;
|
||||
instance->nfc_dict_context.sectors_total = 0;
|
||||
instance->nfc_dict_context.sectors_read = 0;
|
||||
@@ -381,6 +511,7 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) {
|
||||
instance->nfc_dict_context.nested_target_key = 0;
|
||||
instance->nfc_dict_context.msb_count = 0;
|
||||
instance->nfc_dict_context.enhanced_dict = false;
|
||||
instance->nfc_dict_context.current_key_idx = 0;
|
||||
|
||||
// Clean up temporary files used for nested dictionary attack
|
||||
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) {
|
||||
|
||||
@@ -16,6 +16,7 @@ typedef enum {
|
||||
SubGhzCustomEventSceneReceiverInfoTxStop,
|
||||
SubGhzCustomEventSceneReceiverInfoSave,
|
||||
SubGhzCustomEventSceneSaveName,
|
||||
SubGhzCustomEventSceneSignalSettings,
|
||||
SubGhzCustomEventSceneSaveSuccess,
|
||||
SubGhzCustomEventSceneShowErrorBack,
|
||||
SubGhzCustomEventSceneShowErrorOk,
|
||||
@@ -91,6 +92,7 @@ typedef enum {
|
||||
SetTypeSommer_FM238_868,
|
||||
SetTypeStilmatic,
|
||||
SetTypeIronLogic,
|
||||
SetTypeIronLogicSmart,
|
||||
SetTypeDeaMio433,
|
||||
SetTypeDTMNeo433,
|
||||
SetTypeGibidi433,
|
||||
|
||||
@@ -398,6 +398,16 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty
|
||||
.keeloq.cnt = 0x05,
|
||||
.keeloq.manuf = "IronLogic"};
|
||||
break;
|
||||
case SetTypeIronLogicSmart:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFF0,
|
||||
.keeloq.btn = 0x04,
|
||||
.keeloq.cnt = 0x05,
|
||||
.keeloq.manuf = "IL-100(Smart)"};
|
||||
break;
|
||||
case SetTypeStilmatic:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
|
||||
@@ -25,3 +25,4 @@ ADD_SCENE(subghz, decode_raw, DecodeRAW)
|
||||
ADD_SCENE(subghz, delete_raw, DeleteRAW)
|
||||
ADD_SCENE(subghz, need_saving, NeedSaving)
|
||||
ADD_SCENE(subghz, rpc, Rpc)
|
||||
ADD_SCENE(subghz, signal_settings, SignalSettings)
|
||||
|
||||
@@ -26,7 +26,7 @@ const char* const debug_pin_text[DEBUG_P_COUNT] = {
|
||||
"17(1W)",
|
||||
};
|
||||
|
||||
#define DEBUG_COUNTER_COUNT 16
|
||||
#define DEBUG_COUNTER_COUNT 17
|
||||
const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = {
|
||||
"+1",
|
||||
"+2",
|
||||
@@ -36,6 +36,7 @@ const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = {
|
||||
"+10",
|
||||
"+50",
|
||||
"OVFL",
|
||||
"OFEX",
|
||||
"No",
|
||||
"-1",
|
||||
"-2",
|
||||
@@ -54,6 +55,7 @@ const int32_t debug_counter_val[DEBUG_COUNTER_COUNT] = {
|
||||
10,
|
||||
50,
|
||||
65535,
|
||||
-2147483647,
|
||||
0,
|
||||
-1,
|
||||
-2,
|
||||
|
||||
@@ -4,6 +4,7 @@ enum SubmenuIndex {
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexEdit,
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexSignalSettings
|
||||
};
|
||||
|
||||
void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -34,6 +35,15 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Signal Settings",
|
||||
SubmenuIndexSignalSettings,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
};
|
||||
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu,
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSavedMenu));
|
||||
@@ -60,6 +70,11 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEdit);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexSignalSettings) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexSignalSettings);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettings);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -39,6 +39,7 @@ static const char* submenu_names[SetTypeMAX] = {
|
||||
[SetTypeSommer_FM238_868] = "KL: Sommer fm2 868Mhz",
|
||||
[SetTypeStilmatic] = "KL: Stilmatic 433MHz",
|
||||
[SetTypeIronLogic] = "KL: IronLogic 433MHz",
|
||||
[SetTypeIronLogicSmart] = "KL: IronLogic SM 433MHz",
|
||||
[SetTypeDeaMio433] = "KL: DEA Mio 433MHz",
|
||||
[SetTypeDTMNeo433] = "KL: DTM Neo 433MHz",
|
||||
[SetTypeGibidi433] = "KL: Gibidi 433MHz",
|
||||
|
||||
421
applications/main/subghz/scenes/subghz_scene_signal_settings.c
Normal file
@@ -0,0 +1,421 @@
|
||||
#include "../subghz_i.h"
|
||||
#include "subghz/types.h"
|
||||
#include "../helpers/subghz_custom_event.h"
|
||||
#include <lib/toolbox/value_index.h>
|
||||
#include <machine/endian.h>
|
||||
#include <toolbox/strint.h>
|
||||
|
||||
#define TAG "SubGhzSceneSignalSettings"
|
||||
|
||||
static uint32_t counter_mode = 0xff;
|
||||
static uint32_t loaded_counter32 = 0x0;
|
||||
static uint32_t counter32 = 0x0;
|
||||
static uint16_t counter16 = 0x0;
|
||||
static uint8_t byte_count = 0;
|
||||
static uint8_t* byte_ptr = NULL;
|
||||
static uint8_t hex_char_lenght = 0;
|
||||
static FuriString* byte_input_text;
|
||||
|
||||
#define COUNTER_MODE_COUNT 7
|
||||
static const char* const counter_mode_text[COUNTER_MODE_COUNT] = {
|
||||
"System",
|
||||
"Mode 1",
|
||||
"Mode 2",
|
||||
"Mode 3",
|
||||
"Mode 4",
|
||||
"Mode 5",
|
||||
"Mode 6",
|
||||
};
|
||||
|
||||
static const int32_t counter_mode_value[COUNTER_MODE_COUNT] = {
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
uint8_t mode_count;
|
||||
} Protocols;
|
||||
|
||||
// List of protocols names and appropriate CounterMode counts
|
||||
static Protocols protocols[] = {
|
||||
{"Nice FloR-S", 3},
|
||||
{"CAME Atomo", 4},
|
||||
{"Alutech AT-4N", 3},
|
||||
{"KeeLoq", 7},
|
||||
};
|
||||
|
||||
#define PROTOCOLS_COUNT (sizeof(protocols) / sizeof(Protocols));
|
||||
|
||||
// our special case function based on strint_to_uint32 from strint.c
|
||||
StrintParseError strint_to_uint32_base16(const char* str, uint32_t* out, uint8_t* lenght) {
|
||||
// skip whitespace
|
||||
while(((*str >= '\t') && (*str <= '\r')) || *str == ' ') {
|
||||
str++;
|
||||
}
|
||||
|
||||
// read digits
|
||||
uint32_t limit = UINT32_MAX;
|
||||
uint32_t mul_limit = limit / 16;
|
||||
uint32_t result = 0;
|
||||
int read_total = 0;
|
||||
|
||||
while(*str != 0) {
|
||||
int digit_value;
|
||||
if(*str >= '0' && *str <= '9') {
|
||||
digit_value = *str - '0';
|
||||
} else if(*str >= 'A' && *str <= 'Z') {
|
||||
digit_value = *str - 'A' + 10;
|
||||
} else if(*str >= 'a' && *str <= 'z') {
|
||||
digit_value = *str - 'a' + 10;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if(digit_value >= 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(result > mul_limit) return StrintParseOverflowError;
|
||||
result *= 16;
|
||||
if(result > limit - digit_value) return StrintParseOverflowError; //-V658
|
||||
result += digit_value;
|
||||
|
||||
read_total++;
|
||||
str++;
|
||||
}
|
||||
|
||||
if(read_total == 0) {
|
||||
result = 0;
|
||||
*lenght = 0;
|
||||
return StrintParseAbsentError;
|
||||
}
|
||||
|
||||
if(out) *out = result;
|
||||
if(lenght) *lenght = read_total;
|
||||
return StrintParseNoError;
|
||||
}
|
||||
|
||||
void subghz_scene_signal_settings_counter_mode_changed(VariableItem* item) {
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, counter_mode_text[index]);
|
||||
counter_mode = counter_mode_value[index];
|
||||
}
|
||||
|
||||
void subghz_scene_signal_settings_byte_input_callback(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventByteInputDone);
|
||||
}
|
||||
|
||||
void subghz_scene_signal_settings_variable_item_list_enter_callback(void* context, uint32_t index) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
// when we click OK on "Edit counter" item
|
||||
if(index == 1) {
|
||||
furi_string_cat_printf(byte_input_text, "%i", hex_char_lenght * 4);
|
||||
furi_string_cat_str(byte_input_text, "-bit counter in HEX");
|
||||
|
||||
// Setup byte_input view
|
||||
ByteInput* byte_input = subghz->byte_input;
|
||||
byte_input_set_header_text(byte_input, furi_string_get_cstr(byte_input_text));
|
||||
|
||||
byte_input_set_result_callback(
|
||||
byte_input,
|
||||
subghz_scene_signal_settings_byte_input_callback,
|
||||
NULL,
|
||||
subghz,
|
||||
byte_ptr,
|
||||
byte_count);
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput);
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_scene_signal_settings_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
// ### Counter mode section ###
|
||||
|
||||
// When we open saved file we do some check and fill up subghz->file_path.
|
||||
// So now we use it to check is there CounterMode in file or not
|
||||
const char* file_path = furi_string_get_cstr(subghz->file_path);
|
||||
|
||||
furi_assert(subghz);
|
||||
furi_assert(file_path);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
||||
FuriString* tmp_text = furi_string_alloc_set_str("");
|
||||
|
||||
uint32_t tmp_counter_mode = 0;
|
||||
counter_mode = 0xff;
|
||||
uint8_t mode_count = 1;
|
||||
|
||||
// Open file and check is it contains allowed protocols and CounterMode variable - if not then CcounterMode will stay 0xff
|
||||
// if file contain allowed protocol but not contain CounterMode value then setup default CounterMode value = 0 and available CounterMode count for this protocol
|
||||
// if file contain CounterMode value then load it
|
||||
if(!flipper_format_file_open_existing(fff_data_file, file_path)) {
|
||||
FURI_LOG_E(TAG, "Error open file %s", file_path);
|
||||
} else {
|
||||
flipper_format_read_string(fff_data_file, "Protocol", tmp_text);
|
||||
// compare available protocols names, load CounterMode value from file and setup variable_item_list values_count
|
||||
for(uint8_t i = 0; i < PROTOCOLS_COUNT i++) {
|
||||
if(!strcmp(furi_string_get_cstr(tmp_text), protocols[i].name)) {
|
||||
mode_count = protocols[i].mode_count;
|
||||
if(flipper_format_read_uint32(fff_data_file, "CounterMode", &tmp_counter_mode, 1)) {
|
||||
counter_mode = (uint8_t)tmp_counter_mode;
|
||||
} else {
|
||||
counter_mode = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FURI_LOG_D(TAG, "Current CounterMode value %li", counter_mode);
|
||||
|
||||
flipper_format_file_close(fff_data_file);
|
||||
flipper_format_free(fff_data_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
// ### Counter edit section ###
|
||||
FuriString* textCnt = furi_string_alloc_set_str("");
|
||||
byte_input_text = furi_string_alloc_set_str("Enter ");
|
||||
furi_string_reset(tmp_text);
|
||||
|
||||
bool counter_not_available = true;
|
||||
SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx);
|
||||
|
||||
// deserialaze and decode loaded sugbhz file and take information string from decoder
|
||||
if(subghz_protocol_decoder_base_deserialize(decoder, subghz_txrx_get_fff_data(subghz->txrx)) ==
|
||||
SubGhzProtocolStatusOk) {
|
||||
subghz_protocol_decoder_base_get_string(decoder, tmp_text);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Cant deserialize this subghz file");
|
||||
}
|
||||
|
||||
// In protocols output we allways have HEX format for "Cnt:" output (text formating like ...Cnt:%05lX\r\n")
|
||||
// we take 8 simbols starting from "Cnt:........"
|
||||
// at first we search "Cnt:????" that mean for this protocol counter cannot be decoded
|
||||
|
||||
int8_t place = furi_string_search_str(tmp_text, "Cnt:??", 0);
|
||||
if(place > 0) {
|
||||
counter_mode = 0xff;
|
||||
FURI_LOG_D(
|
||||
TAG, "Founded Cnt:???? - Counter mode and edit not available for this protocol");
|
||||
} else {
|
||||
place = furi_string_search_str(tmp_text, "Cnt:", 0);
|
||||
if(place > 0) {
|
||||
// defence from memory leaks. Check can we take 8 symbols after 'Cnt:' ?
|
||||
// if from current place to end of stirngs more than 8 symbols - ok, if not - just take symbols from current place to end of string.
|
||||
// +4 - its 'Cnt:' lenght
|
||||
uint8_t n_symbols_taken = 8;
|
||||
if(sizeof(tmp_text) - (place + 4) < 8) {
|
||||
n_symbols_taken = sizeof(tmp_text) - (place + 4);
|
||||
}
|
||||
furi_string_set_n(textCnt, tmp_text, place + 4, n_symbols_taken);
|
||||
furi_string_trim(textCnt);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Taked 8 bytes hex value starting after 'Cnt:' - %s",
|
||||
furi_string_get_cstr(textCnt));
|
||||
|
||||
// trim and convert 8 simbols string to uint32 by base 16 (hex);
|
||||
// later we use loaded_counter in subghz_scene_signal_settings_on_event to check is there 0 or not - special case
|
||||
|
||||
if(strint_to_uint32_base16(
|
||||
furi_string_get_cstr(textCnt), &loaded_counter32, &hex_char_lenght) ==
|
||||
StrintParseNoError) {
|
||||
counter_not_available = false;
|
||||
|
||||
// calculate and roundup number of hex bytes do display counter in byte_input (every 2 hex simbols = 1 byte for view)
|
||||
// later must be used in byte_input to restrict number of available byte to edit
|
||||
// cnt_byte_count = (hex_char_lenght + 1) / 2;
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Result of conversion from String to uint_32 DEC %li, HEX %lX, HEX lenght %i symbols",
|
||||
loaded_counter32,
|
||||
loaded_counter32,
|
||||
hex_char_lenght);
|
||||
|
||||
// Check is there byte_count more than 2 hex bytes long (16 bit) or not (32bit)
|
||||
// To show hex value we must correct revert bytes for ByteInput view
|
||||
if(hex_char_lenght > 4) {
|
||||
counter32 = loaded_counter32;
|
||||
furi_string_printf(tmp_text, "%lX", counter32);
|
||||
counter32 = __bswap32(counter32);
|
||||
byte_ptr = (uint8_t*)&counter32;
|
||||
byte_count = 4;
|
||||
} else {
|
||||
counter16 = loaded_counter32;
|
||||
furi_string_printf(tmp_text, "%X", counter16);
|
||||
counter16 = __bswap16(counter16);
|
||||
byte_ptr = (uint8_t*)&counter16;
|
||||
byte_count = 2;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Cant convert text counter value");
|
||||
};
|
||||
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Counter editor not available for this protocol");
|
||||
}
|
||||
}
|
||||
|
||||
furi_assert(byte_ptr);
|
||||
furi_assert(byte_count > 0);
|
||||
|
||||
//Create and Enable/Disable variable_item_list depent from current values
|
||||
VariableItemList* variable_item_list = subghz->variable_item_list;
|
||||
int32_t value_index;
|
||||
VariableItem* item;
|
||||
|
||||
// variable_item_list_set_selected_item(subghz->variable_item_list, 0);
|
||||
// variable_item_list_reset(subghz->variable_item_list);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
variable_item_list,
|
||||
subghz_scene_signal_settings_variable_item_list_enter_callback,
|
||||
subghz);
|
||||
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Counter Mode",
|
||||
mode_count,
|
||||
subghz_scene_signal_settings_counter_mode_changed,
|
||||
subghz);
|
||||
value_index = value_index_int32(counter_mode, counter_mode_value, mode_count);
|
||||
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, counter_mode_text[value_index]);
|
||||
variable_item_set_locked(item, (counter_mode == 0xff), "Not available\nfor this\nprotocol !");
|
||||
|
||||
item = variable_item_list_add(variable_item_list, "Edit Counter", 1, NULL, subghz);
|
||||
variable_item_set_current_value_index(item, 0);
|
||||
variable_item_set_current_value_text(item, furi_string_get_cstr(tmp_text));
|
||||
variable_item_set_locked(item, (counter_not_available), "Not available\nfor this\nprotocol !");
|
||||
|
||||
furi_string_free(tmp_text);
|
||||
furi_string_free(textCnt);
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
|
||||
}
|
||||
|
||||
bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
int32_t tmp_counter = 0;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubGhzCustomEventByteInputDone) {
|
||||
switch(byte_count) {
|
||||
case 2:
|
||||
// when signal has Cnt:00 we can step only to 0000+FFFF = FFFF, but we need 0000 for next step
|
||||
// for this case we must use +1 additional step to increace Cnt from FFFF to 0000.
|
||||
|
||||
// save current user definded counter increase value (mult)
|
||||
tmp_counter = furi_hal_subghz_get_rolling_counter_mult();
|
||||
|
||||
// increase signal counter to max value - at result it must be 0000 in most cases
|
||||
// but can be FFFF in case Cnt:0000 (for this we have +1 additional step below)
|
||||
furi_hal_subghz_set_rolling_counter_mult(0xFFFF);
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
|
||||
// if file Cnt:00 then do +1 additional step
|
||||
if(loaded_counter32 == 0) {
|
||||
furi_hal_subghz_set_rolling_counter_mult(1);
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
}
|
||||
|
||||
// at this point we must have signal Cnt:00
|
||||
// convert back after byte_input and do one send with our new mult (counter16) - at end we must have signal Cnt = counter16
|
||||
counter16 = __bswap16(counter16);
|
||||
|
||||
furi_hal_subghz_set_rolling_counter_mult(counter16);
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
|
||||
// restore user definded counter increase value (mult)
|
||||
furi_hal_subghz_set_rolling_counter_mult(tmp_counter);
|
||||
|
||||
break;
|
||||
case 4:
|
||||
// the same for 32 bit Counter
|
||||
tmp_counter = furi_hal_subghz_get_rolling_counter_mult();
|
||||
|
||||
furi_hal_subghz_set_rolling_counter_mult(0xFFFFFFF);
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
|
||||
if(loaded_counter32 == 0) {
|
||||
furi_hal_subghz_set_rolling_counter_mult(1);
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
}
|
||||
|
||||
counter32 = __bswap32(counter32);
|
||||
|
||||
furi_hal_subghz_set_rolling_counter_mult((counter32 & 0xFFFFFFF));
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
|
||||
furi_hal_subghz_set_rolling_counter_mult(tmp_counter);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_signal_settings_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
const char* file_path = furi_string_get_cstr(subghz->file_path);
|
||||
|
||||
furi_assert(subghz);
|
||||
furi_assert(file_path);
|
||||
|
||||
// if ConterMode was changed from 0xff then we must update or write new value to file
|
||||
if(counter_mode != 0xff) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
||||
|
||||
// check is the file available for update/insert CounterMode value
|
||||
if(flipper_format_file_open_existing(fff_data_file, file_path)) {
|
||||
if(flipper_format_insert_or_update_uint32(
|
||||
fff_data_file, "CounterMode", &counter_mode, 1)) {
|
||||
FURI_LOG_D(
|
||||
TAG, "Successfully updated/inserted CounterMode value %li", counter_mode);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Error update/insert CounterMode value");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Error open file %s for writing", file_path);
|
||||
}
|
||||
|
||||
flipper_format_file_close(fff_data_file);
|
||||
flipper_format_free(fff_data_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
// Clear views
|
||||
variable_item_list_set_selected_item(subghz->variable_item_list, 0);
|
||||
variable_item_list_reset(subghz->variable_item_list);
|
||||
byte_input_set_result_callback(subghz->byte_input, NULL, NULL, NULL, NULL, 0);
|
||||
byte_input_set_header_text(subghz->byte_input, "");
|
||||
furi_string_free(byte_input_text);
|
||||
}
|
||||
@@ -35,5 +35,6 @@ App(
|
||||
"modules/submenu.h",
|
||||
"modules/widget_elements/widget_element.h",
|
||||
"modules/empty_screen.h",
|
||||
"modules/date_time_input.h",
|
||||
],
|
||||
)
|
||||
|
||||
479
applications/services/gui/modules/date_time_input.c
Normal file
@@ -0,0 +1,479 @@
|
||||
#include "date_time_input.h"
|
||||
#include "furi_hal_rtc.h"
|
||||
#include <furi.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define get_state(m, r, c, f) \
|
||||
((m)->editable.f ? ((m)->row == (r) && (m)->column == (c) ? \
|
||||
((m)->editing ? EditStateActiveEditing : EditStateActive) : \
|
||||
EditStateNone) : \
|
||||
EditStateDisabled)
|
||||
#define ROW_0_Y (9)
|
||||
#define ROW_0_H (20)
|
||||
|
||||
#define ROW_1_Y (40)
|
||||
#define ROW_1_H (20)
|
||||
|
||||
#define ROW_COUNT 2
|
||||
#define COLUMN_COUNT 3
|
||||
|
||||
struct DateTimeInput {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
DateTime* datetime;
|
||||
|
||||
uint8_t row;
|
||||
uint8_t column;
|
||||
bool editing;
|
||||
|
||||
struct {
|
||||
bool year;
|
||||
bool month;
|
||||
bool day;
|
||||
bool hour;
|
||||
bool minute;
|
||||
bool second;
|
||||
} editable;
|
||||
|
||||
DateTimeChangedCallback changed_callback;
|
||||
DateTimeDoneCallback done_callback;
|
||||
void* callback_context;
|
||||
} DateTimeInputModel;
|
||||
|
||||
typedef enum {
|
||||
EditStateNone,
|
||||
EditStateActive,
|
||||
EditStateActiveEditing,
|
||||
EditStateDisabled
|
||||
} EditState;
|
||||
|
||||
static inline void date_time_input_cleanup_date(DateTime* dt) {
|
||||
uint8_t day_per_month =
|
||||
datetime_get_days_per_month(datetime_is_leap_year(dt->year), dt->month);
|
||||
if(dt->day > day_per_month) {
|
||||
dt->day = day_per_month;
|
||||
}
|
||||
}
|
||||
static inline void date_time_input_draw_block(
|
||||
Canvas* canvas,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
size_t w,
|
||||
size_t h,
|
||||
Font font,
|
||||
EditState state,
|
||||
const char* text) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(text);
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
if(state != EditStateDisabled) {
|
||||
if(state != EditStateNone) {
|
||||
if(state == EditStateActiveEditing) {
|
||||
canvas_draw_icon(canvas, x + w / 2 - 2, y - 1 - 3, &I_SmallArrowUp_3x5);
|
||||
canvas_draw_icon(canvas, x + w / 2 - 2, y + h + 1, &I_SmallArrowDown_3x5);
|
||||
}
|
||||
canvas_draw_rbox(canvas, x, y, w, h, 1);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else {
|
||||
canvas_draw_rframe(canvas, x, y, w, h, 1);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, font);
|
||||
canvas_draw_str_aligned(canvas, x + w / 2, y + h / 2, AlignCenter, AlignCenter, text);
|
||||
if(state != EditStateNone) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void date_time_input_draw_text(
|
||||
Canvas* canvas,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
size_t w,
|
||||
size_t h,
|
||||
Font font,
|
||||
EditState state,
|
||||
const char* text) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(text);
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
if(state != EditStateDisabled && state != EditStateNone) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, font);
|
||||
canvas_draw_str_aligned(canvas, x + w / 2, y + h / 2, AlignCenter, AlignCenter, text);
|
||||
if(state != EditStateNone) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
}
|
||||
|
||||
static void date_time_input_draw_hour_24hr_callback(Canvas* canvas, DateTimeInputModel* model) {
|
||||
char buffer[4];
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 0, ROW_1_Y - 2, " H H M M S S");
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%02u", model->datetime->hour);
|
||||
date_time_input_draw_block(
|
||||
canvas, 30, ROW_1_Y, 28, ROW_1_H, FontBigNumbers, get_state(model, 1, 0, hour), buffer);
|
||||
canvas_draw_box(canvas, 60, ROW_1_Y + ROW_1_H - 7, 2, 2);
|
||||
canvas_draw_box(canvas, 60, ROW_1_Y + ROW_1_H - 7 - 6, 2, 2);
|
||||
}
|
||||
|
||||
static void date_time_input_draw_hour_12hr_callback(Canvas* canvas, DateTimeInputModel* model) {
|
||||
char buffer[4];
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 0, ROW_1_Y - 2, " H H M M S S");
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
uint8_t hour = model->datetime->hour % 12;
|
||||
// Show 12:00 instead of 00:00 for 12-hour time
|
||||
if(hour == 0) hour = 12;
|
||||
|
||||
// Placeholder spaces to make room for AM/PM since FontBigNumbers can't draw letters
|
||||
date_time_input_draw_block(
|
||||
canvas, 8, ROW_1_Y, 50, ROW_1_H, FontBigNumbers, get_state(model, 1, 0, hour), buffer);
|
||||
canvas_draw_box(canvas, 60, ROW_1_Y + ROW_1_H - 7, 2, 2);
|
||||
canvas_draw_box(canvas, 60, ROW_1_Y + ROW_1_H - 7 - 6, 2, 2);
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%02u", hour);
|
||||
date_time_input_draw_text(
|
||||
canvas, 8, ROW_1_Y, 30, ROW_1_H, FontBigNumbers, get_state(model, 1, 0, hour), buffer);
|
||||
|
||||
// The AM and PM text shift by 1 pixel so compensate to make them line up
|
||||
if(model->datetime->hour < 12) {
|
||||
date_time_input_draw_text(
|
||||
canvas, 30, ROW_1_Y + 3, 30, ROW_1_H, FontPrimary, get_state(model, 1, 0, hour), "AM");
|
||||
} else {
|
||||
date_time_input_draw_text(
|
||||
canvas, 31, ROW_1_Y + 3, 30, ROW_1_H, FontPrimary, get_state(model, 1, 0, hour), "PM");
|
||||
}
|
||||
}
|
||||
|
||||
static void date_time_input_draw_time_callback(Canvas* canvas, DateTimeInputModel* model) {
|
||||
furi_check(model->datetime);
|
||||
|
||||
char buffer[4];
|
||||
|
||||
// Draw hour depending on RTC time format
|
||||
if(furi_hal_rtc_get_locale_timeformat() == FuriHalRtcLocaleTimeFormat24h) {
|
||||
date_time_input_draw_hour_24hr_callback(canvas, model);
|
||||
} else {
|
||||
date_time_input_draw_hour_12hr_callback(canvas, model);
|
||||
}
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%02u", model->datetime->minute);
|
||||
date_time_input_draw_block(
|
||||
canvas, 64, ROW_1_Y, 28, ROW_1_H, FontBigNumbers, get_state(model, 1, 1, minute), buffer);
|
||||
canvas_draw_box(canvas, 94, ROW_1_Y + ROW_1_H - 7, 2, 2);
|
||||
canvas_draw_box(canvas, 94, ROW_1_Y + ROW_1_H - 7 - 6, 2, 2);
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%02u", model->datetime->second);
|
||||
date_time_input_draw_block(
|
||||
canvas, 98, ROW_1_Y, 28, ROW_1_H, FontBigNumbers, get_state(model, 1, 2, second), buffer);
|
||||
}
|
||||
|
||||
static void date_time_input_draw_date_callback(Canvas* canvas, DateTimeInputModel* model) {
|
||||
furi_check(model->datetime);
|
||||
|
||||
char buffer[6];
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 0, ROW_0_Y - 2, " Y Y Y Y M M D D");
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
snprintf(buffer, sizeof(buffer), "%04u", model->datetime->year);
|
||||
date_time_input_draw_block(
|
||||
canvas, 2, ROW_0_Y, 56, ROW_0_H, FontBigNumbers, get_state(model, 0, 0, year), buffer);
|
||||
snprintf(buffer, sizeof(buffer), "%02u", model->datetime->month);
|
||||
date_time_input_draw_block(
|
||||
canvas, 64, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 1, month), buffer);
|
||||
canvas_draw_box(canvas, 64 - 5, ROW_0_Y + (ROW_0_H / 2), 4, 2);
|
||||
snprintf(buffer, sizeof(buffer), "%02u", model->datetime->day);
|
||||
date_time_input_draw_block(
|
||||
canvas, 98, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 2, day), buffer);
|
||||
canvas_draw_box(canvas, 98 - 5, ROW_0_Y + (ROW_0_H / 2), 4, 2);
|
||||
}
|
||||
|
||||
static void date_time_input_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
DateTimeInputModel* model = _model;
|
||||
canvas_clear(canvas);
|
||||
date_time_input_draw_time_callback(canvas, model);
|
||||
date_time_input_draw_date_callback(canvas, model);
|
||||
}
|
||||
|
||||
static inline bool is_allowed_to_edit(DateTimeInputModel* model) {
|
||||
return (model->row == 0 && ((model->column == 0 && model->editable.year) |
|
||||
(model->column == 1 && model->editable.month) |
|
||||
(model->column == 2 && model->editable.day))) ||
|
||||
((model->row == 1) && ((model->column == 0 && model->editable.hour) |
|
||||
(model->column == 1 && model->editable.minute) |
|
||||
(model->column == 2 && model->editable.second)));
|
||||
}
|
||||
|
||||
static bool date_time_input_navigation_callback(InputEvent* event, DateTimeInputModel* model) {
|
||||
if(event->key == InputKeyUp) {
|
||||
if(model->row > 0) model->row--;
|
||||
if(!is_allowed_to_edit(model)) model->row++;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
if(model->row < ROW_COUNT - 1) model->row++;
|
||||
if(!is_allowed_to_edit(model)) model->row--;
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->editing = !model->editing;
|
||||
} else if(event->key == InputKeyRight) {
|
||||
if(model->column < COLUMN_COUNT - 1) model->column++;
|
||||
while(model->column < COLUMN_COUNT - 1 && !is_allowed_to_edit(model))
|
||||
model->column++;
|
||||
while(model->column > 0 && !is_allowed_to_edit(model))
|
||||
model->column--;
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
if(model->column > 0) model->column--;
|
||||
while(model->column > 0 && !is_allowed_to_edit(model))
|
||||
model->column--;
|
||||
while(model->column < COLUMN_COUNT - 1 && !is_allowed_to_edit(model))
|
||||
model->column++;
|
||||
} else if(event->key == InputKeyBack && model->editing) {
|
||||
model->editing = false;
|
||||
} else if(event->key == InputKeyBack && model->done_callback) {
|
||||
model->done_callback(model->callback_context);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool date_time_input_time_callback(InputEvent* event, DateTimeInputModel* model) {
|
||||
furi_check(model->datetime);
|
||||
|
||||
if(event->key == InputKeyUp) {
|
||||
if(model->column == 0) {
|
||||
model->datetime->hour++;
|
||||
model->datetime->hour = model->datetime->hour % 24;
|
||||
} else if(model->column == 1) {
|
||||
model->datetime->minute++;
|
||||
model->datetime->minute = model->datetime->minute % 60;
|
||||
} else if(model->column == 2) {
|
||||
model->datetime->second++;
|
||||
model->datetime->second = model->datetime->second % 60;
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
} else if(event->key == InputKeyDown) {
|
||||
if(model->column == 0) {
|
||||
if(model->datetime->hour > 0) {
|
||||
model->datetime->hour--;
|
||||
} else {
|
||||
model->datetime->hour = 23;
|
||||
}
|
||||
model->datetime->hour = model->datetime->hour % 24;
|
||||
} else if(model->column == 1) {
|
||||
if(model->datetime->minute > 0) {
|
||||
model->datetime->minute--;
|
||||
} else {
|
||||
model->datetime->minute = 59;
|
||||
}
|
||||
model->datetime->minute = model->datetime->minute % 60;
|
||||
} else if(model->column == 2) {
|
||||
if(model->datetime->second > 0) {
|
||||
model->datetime->second--;
|
||||
} else {
|
||||
model->datetime->second = 59;
|
||||
}
|
||||
model->datetime->second = model->datetime->second % 60;
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
} else {
|
||||
return date_time_input_navigation_callback(event, model);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool date_time_input_date_callback(InputEvent* event, DateTimeInputModel* model) {
|
||||
furi_check(model->datetime);
|
||||
|
||||
if(event->key == InputKeyUp) {
|
||||
if(model->column == 0) {
|
||||
if(model->datetime->year < 2099) {
|
||||
model->datetime->year++;
|
||||
}
|
||||
} else if(model->column == 1) {
|
||||
if(model->datetime->month < 12) {
|
||||
model->datetime->month++;
|
||||
}
|
||||
} else if(model->column == 2) {
|
||||
if(model->datetime->day < 31) model->datetime->day++;
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
} else if(event->key == InputKeyDown) {
|
||||
if(model->column == 0) {
|
||||
if(model->datetime->year > 1980) {
|
||||
model->datetime->year--;
|
||||
}
|
||||
} else if(model->column == 1) {
|
||||
if(model->datetime->month > 1) {
|
||||
model->datetime->month--;
|
||||
}
|
||||
} else if(model->column == 2) {
|
||||
if(model->datetime->day > 1) {
|
||||
model->datetime->day--;
|
||||
}
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
} else {
|
||||
return date_time_input_navigation_callback(event, model);
|
||||
}
|
||||
|
||||
date_time_input_cleanup_date(model->datetime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool date_time_input_view_input_callback(InputEvent* event, void* context) {
|
||||
DateTimeInput* instance = context;
|
||||
bool consumed = false;
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
DateTimeInputModel * model,
|
||||
{
|
||||
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
|
||||
if(model->editing) {
|
||||
if(model->row == 0) {
|
||||
consumed = date_time_input_date_callback(event, model);
|
||||
} else if(model->row == 1) {
|
||||
consumed = date_time_input_time_callback(event, model);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
if(model->changed_callback) {
|
||||
model->changed_callback(model->callback_context);
|
||||
}
|
||||
} else {
|
||||
consumed = date_time_input_navigation_callback(event, model);
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/** Reset all input-related data in model
|
||||
*
|
||||
* @param model The model
|
||||
*/
|
||||
static void date_time_input_reset_model_input_data(DateTimeInputModel* model) {
|
||||
model->row = 0;
|
||||
model->column = 0;
|
||||
|
||||
model->datetime = NULL;
|
||||
|
||||
model->editable.year = true;
|
||||
model->editable.month = true;
|
||||
model->editable.day = true;
|
||||
model->editable.hour = true;
|
||||
model->editable.minute = true;
|
||||
model->editable.second = true;
|
||||
}
|
||||
|
||||
DateTimeInput* date_time_input_alloc(void) {
|
||||
DateTimeInput* date_time_input = malloc(sizeof(DateTimeInput));
|
||||
date_time_input->view = view_alloc();
|
||||
view_allocate_model(date_time_input->view, ViewModelTypeLocking, sizeof(DateTimeInputModel));
|
||||
view_set_context(date_time_input->view, date_time_input);
|
||||
view_set_draw_callback(date_time_input->view, date_time_input_view_draw_callback);
|
||||
view_set_input_callback(date_time_input->view, date_time_input_view_input_callback);
|
||||
|
||||
with_view_model(
|
||||
date_time_input->view,
|
||||
DateTimeInputModel * model,
|
||||
{
|
||||
model->changed_callback = NULL;
|
||||
model->callback_context = NULL;
|
||||
date_time_input_reset_model_input_data(model);
|
||||
},
|
||||
true);
|
||||
|
||||
return date_time_input;
|
||||
}
|
||||
|
||||
void date_time_input_free(DateTimeInput* date_time_input) {
|
||||
furi_check(date_time_input);
|
||||
view_free(date_time_input->view);
|
||||
free(date_time_input);
|
||||
}
|
||||
|
||||
View* date_time_input_get_view(DateTimeInput* date_time_input) {
|
||||
furi_check(date_time_input);
|
||||
return date_time_input->view;
|
||||
}
|
||||
|
||||
void date_time_input_set_result_callback(
|
||||
DateTimeInput* date_time_input,
|
||||
DateTimeChangedCallback changed_callback,
|
||||
DateTimeDoneCallback done_callback,
|
||||
void* callback_context,
|
||||
DateTime* current_datetime) {
|
||||
furi_check(date_time_input);
|
||||
|
||||
with_view_model(
|
||||
date_time_input->view,
|
||||
DateTimeInputModel * model,
|
||||
{
|
||||
date_time_input_reset_model_input_data(model);
|
||||
model->changed_callback = changed_callback;
|
||||
model->done_callback = done_callback;
|
||||
model->callback_context = callback_context;
|
||||
model->datetime = current_datetime;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void date_time_input_set_editable_fields(
|
||||
DateTimeInput* date_time_input,
|
||||
bool year,
|
||||
bool month,
|
||||
bool day,
|
||||
bool hour,
|
||||
bool minute,
|
||||
bool second) {
|
||||
furi_check(date_time_input);
|
||||
|
||||
with_view_model(
|
||||
date_time_input->view,
|
||||
DateTimeInputModel * model,
|
||||
{
|
||||
model->editable.year = year;
|
||||
model->editable.month = month;
|
||||
model->editable.day = day;
|
||||
model->editable.hour = hour;
|
||||
model->editable.minute = minute;
|
||||
model->editable.second = second;
|
||||
|
||||
// Select first editable field
|
||||
model->row = 0;
|
||||
model->column = 0;
|
||||
while(!is_allowed_to_edit(model)) {
|
||||
// Cycle to next column and wrap around at end
|
||||
model->column = (model->column + 1) % COLUMN_COUNT;
|
||||
// If the column is 0, we wrapped, so go to next row
|
||||
if(model->column == 0) model->row++;
|
||||
// If we passed the last row, give up
|
||||
if(model->row >= ROW_COUNT) break;
|
||||
};
|
||||
},
|
||||
true);
|
||||
}
|
||||
82
applications/services/gui/modules/date_time_input.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @file date_time_input.h
|
||||
* GUI: DateTimeInput view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include <datetime.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Date/time input anonymous structure */
|
||||
typedef struct DateTimeInput DateTimeInput;
|
||||
|
||||
/** callback that is executed on value change */
|
||||
typedef void (*DateTimeChangedCallback)(void* context);
|
||||
|
||||
/** callback that is executed on back button press */
|
||||
typedef void (*DateTimeDoneCallback)(void* context);
|
||||
|
||||
/** Allocate and initialize date/time input
|
||||
*
|
||||
* This screen used to input a date and time
|
||||
*
|
||||
* @return DateTimeInput instance
|
||||
*/
|
||||
DateTimeInput* date_time_input_alloc(void);
|
||||
|
||||
/** Deinitialize and free date/time input
|
||||
*
|
||||
* @param date_time_input Date/time input instance
|
||||
*/
|
||||
void date_time_input_free(DateTimeInput* date_time_input);
|
||||
|
||||
/** Get date/time input view
|
||||
*
|
||||
* @param date_time_input Date/time input instance
|
||||
*
|
||||
* @return View instance that can be used for embedding
|
||||
*/
|
||||
View* date_time_input_get_view(DateTimeInput* date_time_input);
|
||||
|
||||
/** Set date/time input result callback
|
||||
*
|
||||
* @param date_time_input date/time input instance
|
||||
* @param changed_callback changed callback fn
|
||||
* @param done_callback finished callback fn
|
||||
* @param callback_context callback context
|
||||
* @param datetime date/time value
|
||||
*/
|
||||
void date_time_input_set_result_callback(
|
||||
DateTimeInput* date_time_input,
|
||||
DateTimeChangedCallback changed_callback,
|
||||
DateTimeDoneCallback done_callback,
|
||||
void* callback_context,
|
||||
DateTime* datetime);
|
||||
|
||||
/** Set date/time fields which can be edited
|
||||
*
|
||||
* @param date_time_input date/time input instance
|
||||
* @param year whether to allow editing the year
|
||||
* @param month whether to allow editing the month
|
||||
* @param day whether to allow editing the day
|
||||
* @param hour whether to allow editing the hour
|
||||
* @param minute whether to allow editing the minute
|
||||
* @param second whether to allow editing the second
|
||||
*/
|
||||
void date_time_input_set_editable_fields(
|
||||
DateTimeInput* date_time_input,
|
||||
bool year,
|
||||
bool month,
|
||||
bool day,
|
||||
bool hour,
|
||||
bool minute,
|
||||
bool second);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -487,10 +487,9 @@ static void notification_process_notification_message(
|
||||
}
|
||||
break;
|
||||
case NotificationMessageTypeLedDisplayBacklightEnforceOn:
|
||||
furi_check(app->display_led_lock < UINT8_MAX);
|
||||
app->display_led_lock++;
|
||||
// --- NIGHT SHIFT ---
|
||||
if(app->display_led_lock == 1) {
|
||||
if(!app->display_led_lock) {
|
||||
app->display_led_lock = true;
|
||||
notification_apply_internal_led_layer(
|
||||
&app->display,
|
||||
notification_message->data.led.value * display_brightness_setting *
|
||||
@@ -498,14 +497,12 @@ static void notification_process_notification_message(
|
||||
}
|
||||
break;
|
||||
case NotificationMessageTypeLedDisplayBacklightEnforceAuto:
|
||||
if(app->display_led_lock > 0) {
|
||||
app->display_led_lock--;
|
||||
if(app->display_led_lock == 0) {
|
||||
notification_apply_internal_led_layer(
|
||||
&app->display,
|
||||
notification_message->data.led.value * display_brightness_setting *
|
||||
app->current_night_shift * 1.0f);
|
||||
}
|
||||
if(app->display_led_lock) {
|
||||
app->display_led_lock = false;
|
||||
notification_apply_internal_led_layer(
|
||||
&app->display,
|
||||
notification_message->data.led.value * display_brightness_setting *
|
||||
app->current_night_shift * 1.0f);
|
||||
// --- NIGHT SHIFT END ---
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Incorrect BacklightEnforce use");
|
||||
|
||||
@@ -77,7 +77,7 @@ struct NotificationApp {
|
||||
|
||||
NotificationLedLayer display;
|
||||
NotificationLedLayer led[NOTIFICATION_LED_COUNT];
|
||||
uint8_t display_led_lock;
|
||||
bool display_led_lock;
|
||||
|
||||
NotificationSettings settings;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ App(
|
||||
fap_icon_assets="images",
|
||||
fap_weburl="https://github.com/noproto/FlipperMfkey",
|
||||
fap_description="MIFARE Classic key recovery tool",
|
||||
fap_version="3.1",
|
||||
fap_version="4.0",
|
||||
)
|
||||
|
||||
App(
|
||||
|
||||
@@ -251,10 +251,14 @@ bool load_nested_nonces(
|
||||
MfClassicNonce res = {0};
|
||||
res.attack = static_encrypted;
|
||||
|
||||
int sector_num = 0;
|
||||
char key_type = 'A';
|
||||
int parsed = sscanf(
|
||||
line,
|
||||
"Sec %*d key %*c cuid %" PRIx32 " nt0 %" PRIx32 " ks0 %" PRIx32
|
||||
"Sec %d key %c cuid %" PRIx32 " nt0 %" PRIx32 " ks0 %" PRIx32
|
||||
" par0 %4[01] nt1 %" PRIx32 " ks1 %" PRIx32 " par1 %4[01]",
|
||||
§or_num,
|
||||
&key_type,
|
||||
&res.uid,
|
||||
&res.nt0,
|
||||
&res.ks1_1_enc,
|
||||
@@ -263,11 +267,14 @@ bool load_nested_nonces(
|
||||
&res.ks1_2_enc,
|
||||
res.par_2_str);
|
||||
|
||||
if(parsed >= 4) { // At least one nonce is present
|
||||
// Calculate key_idx from sector and key type (for static encrypted: key_idx = sector * 2 + key_offset)
|
||||
res.key_idx = (uint8_t)(sector_num * 2 + (key_type == 'B' ? 1 : 0));
|
||||
|
||||
if(parsed >= 6) { // At least one nonce is present (sector, key, uid, nt0, ks0, par0)
|
||||
res.par_1 = binaryStringToInt(res.par_1_str);
|
||||
res.uid_xor_nt0 = res.uid ^ res.nt0;
|
||||
|
||||
if(parsed == 7) { // Both nonces are present
|
||||
if(parsed == 9) { // Both nonces are present
|
||||
res.attack = static_nested;
|
||||
res.par_2 = binaryStringToInt(res.par_2_str);
|
||||
res.uid_xor_nt1 = res.uid ^ res.nt1;
|
||||
|
||||
@@ -67,8 +67,8 @@ static uint8_t MSB_LIMIT = 16;
|
||||
static inline void flush_key_buffer(ProgramState* program_state) {
|
||||
if(program_state->key_buffer && program_state->key_buffer_count > 0 &&
|
||||
program_state->cuid_dict) {
|
||||
// Pre-allocate exact size needed: 12 hex chars + 1 newline per key
|
||||
size_t total_size = program_state->key_buffer_count * 13;
|
||||
// Pre-allocate exact size needed: 2 hex chars (key_idx) + 12 hex chars (key) + 1 newline per key
|
||||
size_t total_size = program_state->key_buffer_count * 15;
|
||||
//FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count);
|
||||
//FURI_LOG_I(TAG, "Total size: %d bytes", total_size);
|
||||
char* batch_buffer = malloc(total_size + 1); // +1 for null terminator
|
||||
@@ -77,6 +77,11 @@ static inline void flush_key_buffer(ProgramState* program_state) {
|
||||
const char hex_chars[] = "0123456789ABCDEF";
|
||||
|
||||
for(size_t i = 0; i < program_state->key_buffer_count; i++) {
|
||||
// Write key_idx as 2 hex chars
|
||||
uint8_t key_idx = program_state->key_idx_buffer[i];
|
||||
*ptr++ = hex_chars[key_idx >> 4];
|
||||
*ptr++ = hex_chars[key_idx & 0x0F];
|
||||
|
||||
// Convert key to hex string directly into buffer
|
||||
for(size_t j = 0; j < sizeof(MfClassicKey); j++) {
|
||||
uint8_t byte = program_state->key_buffer[i].data[j];
|
||||
@@ -144,6 +149,7 @@ static inline int
|
||||
|
||||
// Use key buffer - buffer is guaranteed to be available for static_encrypted
|
||||
program_state->key_buffer[program_state->key_buffer_count] = n->key;
|
||||
program_state->key_idx_buffer[program_state->key_buffer_count] = n->key_idx;
|
||||
program_state->key_buffer_count++;
|
||||
|
||||
// Flush buffer when full
|
||||
@@ -659,17 +665,20 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_
|
||||
// Allocate key buffer for static encrypted nonces
|
||||
if(n->attack == static_encrypted) {
|
||||
size_t available_ram = memmgr_heap_get_max_free_block();
|
||||
// Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string
|
||||
// Plus original 6 bytes in buffer = 19 bytes total per key
|
||||
// Each key becomes 2 hex chars (key_idx) + 12 hex chars (key) + 1 newline = 15 bytes in the batch string
|
||||
// Plus original 6 bytes (key) + 1 byte (key_idx) in buffer = 22 bytes total per key
|
||||
// Add extra safety margin for string overhead and other allocations
|
||||
const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD;
|
||||
const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation
|
||||
const size_t bytes_per_key =
|
||||
sizeof(MfClassicKey) + sizeof(uint8_t) + 15; // buffer + string representation
|
||||
if(available_ram > safety_threshold) {
|
||||
program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key;
|
||||
program_state->key_buffer =
|
||||
malloc(program_state->key_buffer_size * sizeof(MfClassicKey));
|
||||
program_state->key_idx_buffer =
|
||||
malloc(program_state->key_buffer_size * sizeof(uint8_t));
|
||||
program_state->key_buffer_count = 0;
|
||||
if(!program_state->key_buffer) {
|
||||
if(!program_state->key_buffer || !program_state->key_idx_buffer) {
|
||||
// Free the allocated blocks before returning
|
||||
for(int i = 0; i < num_blocks; i++) {
|
||||
free(block_pointers[i]);
|
||||
@@ -691,6 +700,7 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_
|
||||
}
|
||||
} else {
|
||||
program_state->key_buffer = NULL;
|
||||
program_state->key_idx_buffer = NULL;
|
||||
program_state->key_buffer_size = 0;
|
||||
program_state->key_buffer_count = 0;
|
||||
}
|
||||
@@ -736,7 +746,9 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_
|
||||
if(n->attack == static_encrypted && program_state->key_buffer) {
|
||||
flush_key_buffer(program_state);
|
||||
free(program_state->key_buffer);
|
||||
free(program_state->key_idx_buffer);
|
||||
program_state->key_buffer = NULL;
|
||||
program_state->key_idx_buffer = NULL;
|
||||
program_state->key_buffer_size = 0;
|
||||
program_state->key_buffer_count = 0;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ typedef struct {
|
||||
FuriThread* mfkeythread;
|
||||
KeysDict* cuid_dict;
|
||||
MfClassicKey* key_buffer;
|
||||
uint8_t* key_idx_buffer;
|
||||
size_t key_buffer_size;
|
||||
size_t key_buffer_count;
|
||||
} ProgramState;
|
||||
@@ -72,6 +73,7 @@ typedef struct {
|
||||
uint32_t nt1; // tag challenge second
|
||||
uint32_t uid_xor_nt0; // uid ^ nt0
|
||||
uint32_t uid_xor_nt1; // uid ^ nt1
|
||||
uint8_t key_idx; // key index (for static encrypted nonces)
|
||||
union {
|
||||
// Mfkey32
|
||||
struct {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png
vendored
Executable file
|
After Width: | Height: | Size: 858 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png
vendored
Executable file
|
After Width: | Height: | Size: 855 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png
vendored
Executable file
|
After Width: | Height: | Size: 872 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png
vendored
Executable file
|
After Width: | Height: | Size: 861 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png
vendored
Executable file
|
After Width: | Height: | Size: 853 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png
vendored
Executable file
|
After Width: | Height: | Size: 851 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png
vendored
Executable file
|
After Width: | Height: | Size: 852 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png
vendored
Executable file
|
After Width: | Height: | Size: 856 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png
vendored
Executable file
|
After Width: | Height: | Size: 850 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png
vendored
Executable file
|
After Width: | Height: | Size: 851 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png
vendored
Executable file
|
After Width: | Height: | Size: 860 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png
vendored
Executable file
|
After Width: | Height: | Size: 857 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png
vendored
Executable file
|
After Width: | Height: | Size: 863 B |
23
assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
Filetype: Flipper Animation
|
||||
Version: 1
|
||||
|
||||
Width: 128
|
||||
Height: 64
|
||||
Passive frames: 10
|
||||
Active frames: 18
|
||||
Frames order: 0 1 2 1 0 1 2 1 0 1 2 3 4 5 6 5 4 7 2 8 9 10 11 10 9 10 11 12
|
||||
Active cycles: 1
|
||||
Frame rate: 2
|
||||
Duration: 3600
|
||||
Active cooldown: 7
|
||||
|
||||
Bubble slots: 1
|
||||
|
||||
Slot: 0
|
||||
X: 11
|
||||
Y: 19
|
||||
Text: HAPPY\nHOLIDAYS!
|
||||
AlignH: Right
|
||||
AlignV: Center
|
||||
StartFrame: 22
|
||||
EndFrame: 27
|
||||
BIN
assets/dolphin/external/L1_New_year_128x64/frame_0.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/dolphin/external/L1_New_year_128x64/frame_1.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/dolphin/external/L1_New_year_128x64/frame_2.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/dolphin/external/L1_New_year_128x64/frame_3.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
@@ -7,7 +7,7 @@ Passive frames: 4
|
||||
Active frames: 0
|
||||
Frames order: 0 1 2 3
|
||||
Active cycles: 0
|
||||
Frame rate: 3
|
||||
Frame rate: 2
|
||||
Duration: 3600
|
||||
Active cooldown: 0
|
||||
|
||||
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png
vendored
Executable file
|
After Width: | Height: | Size: 820 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png
vendored
Executable file
|
After Width: | Height: | Size: 881 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png
vendored
Executable file
|
After Width: | Height: | Size: 788 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png
vendored
Executable file
|
After Width: | Height: | Size: 816 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png
vendored
Executable file
|
After Width: | Height: | Size: 864 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png
vendored
Executable file
|
After Width: | Height: | Size: 798 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png
vendored
Executable file
|
After Width: | Height: | Size: 813 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png
vendored
Executable file
|
After Width: | Height: | Size: 879 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png
vendored
Executable file
|
After Width: | Height: | Size: 855 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png
vendored
Executable file
|
After Width: | Height: | Size: 772 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png
vendored
Executable file
|
After Width: | Height: | Size: 817 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png
vendored
Executable file
|
After Width: | Height: | Size: 867 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png
vendored
Executable file
|
After Width: | Height: | Size: 866 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png
vendored
Executable file
|
After Width: | Height: | Size: 809 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png
vendored
Executable file
|
After Width: | Height: | Size: 795 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png
vendored
Executable file
|
After Width: | Height: | Size: 870 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png
vendored
Executable file
|
After Width: | Height: | Size: 852 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png
vendored
Executable file
|
After Width: | Height: | Size: 805 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png
vendored
Executable file
|
After Width: | Height: | Size: 858 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png
vendored
Executable file
|
After Width: | Height: | Size: 830 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png
vendored
Executable file
|
After Width: | Height: | Size: 828 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png
vendored
Executable file
|
After Width: | Height: | Size: 585 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png
vendored
Executable file
|
After Width: | Height: | Size: 431 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png
vendored
Executable file
|
After Width: | Height: | Size: 812 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png
vendored
Executable file
|
After Width: | Height: | Size: 281 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png
vendored
Executable file
|
After Width: | Height: | Size: 270 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png
vendored
Executable file
|
After Width: | Height: | Size: 236 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png
vendored
Executable file
|
After Width: | Height: | Size: 485 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png
vendored
Executable file
|
After Width: | Height: | Size: 771 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png
vendored
Executable file
|
After Width: | Height: | Size: 887 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png
vendored
Executable file
|
After Width: | Height: | Size: 809 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png
vendored
Executable file
|
After Width: | Height: | Size: 890 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png
vendored
Executable file
|
After Width: | Height: | Size: 819 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png
vendored
Executable file
|
After Width: | Height: | Size: 799 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png
vendored
Executable file
|
After Width: | Height: | Size: 817 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png
vendored
Executable file
|
After Width: | Height: | Size: 875 B |