1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 04:34:43 +04:00

Compare commits

...

75 Commits

Author SHA1 Message Date
MX
5c539d2346 upd changelog 2025-12-11 00:40:37 +03:00
MX
3bfeea9962 fmt [ci skip] 2025-12-11 00:36:11 +03:00
MX
0a7eb30a15 nfc mf classic upgrades
by noproto
2025-12-11 00:24:50 +03:00
MX
caad1ef268 finish subghz fixes 2025-12-10 23:56:22 +03:00
MX
d10a601109 grand finale? 2025-12-10 03:05:16 +03:00
MX
32a182c439 fix some oops
thx WillyJL
2025-12-10 01:09:34 +03:00
MX
1f676cffea text fixes 2025-12-08 20:16:08 +03:00
MX
a28b2477f9 simplify counter logic and apply more fixes
by @Dmitry422
2025-12-08 12:25:00 +03:00
MX
c08cb33a76 move ofex out of range and fix counter editor
scene fixes by @Dmitry422
2025-12-06 22:59:48 +03:00
MX
59ac0f211c upd changelog 2025-12-03 22:53:01 +03:00
MX
c8e756a3c5 enable winter anims 2025-12-03 22:52:09 +03:00
MX
13b79f0246 upd changelog 2025-12-03 22:50:43 +03:00
MX
05925868d2 upd subremote 2025-12-03 19:59:03 +03:00
MX
e2e839fb2c fmt 2025-12-01 07:04:38 +03:00
MX
0b7c9e2eab update changelog [ci skip] 2025-12-01 06:46:13 +03:00
MMX
6f69f39fa1 Merge pull request #933 from Dmitry422/dev
Subghz signal Counter edit option
2025-12-01 06:43:03 +03:00
MMX
ab593a7a8f Merge pull request #931 from koterba/patch-1
Fix typo in README warning about scammers
2025-12-01 06:42:52 +03:00
MX
7409b51da5 upd changelog [ci skip] 2025-12-01 06:41:00 +03:00
Dmitry422
0f3eb9ae12 Subghz counter edit finished 2025-12-01 10:08:23 +07:00
MX
6abd2b0e9f Add date/time input module
ofw pr 4261 by aaronjamt
2025-12-01 06:06:30 +03:00
MX
a7561bee98 Add Saflok MFUL Parser Support
by aaronjamt
2025-12-01 05:40:27 +03:00
MX
3821c9049e Add MFUL counters to Info page
by aaronjamt
2025-12-01 05:36:10 +03:00
MX
5bd0f642dd ir cli fix
by WillyJL
2025-12-01 05:31:26 +03:00
Dmitry422
b8bec12974 from home to work. one step from the end 2025-11-30 21:57:10 +07:00
Dmitry422
a493612444 From work to home 2025-11-28 20:13:56 +07:00
MX
259efadaea upd changelog 2025-11-25 19:09:11 +03:00
MX
ecbeb658e1 Merge remote-tracking branch 'OFW/dev' into dev 2025-11-25 19:08:15 +03:00
RebornedBrain
cd79d4f689 Fix Felica standard loading from nfc file (#4311) 2025-11-25 17:58:10 +04:00
Dmitry422
7fd30911fe Start working on subghz counter editor 2025-11-21 16:58:38 +07:00
koterba
52b76e2e2e Fix typo in README warning about scammers 2025-11-11 18:33:32 +00:00
MX
b0c1931caf upd changelog 2025-11-11 15:46:15 +03:00
MMX
1c07f94cdd Merge pull request #930 from Dmitry422/dev
FZ interface for SubGHz Counter Experimental Mode
2025-11-11 15:43:44 +03:00
Dmitry422
0351818b75 Rework by review finished 2025-11-11 11:52:42 +07:00
Dmitry422
b391cfc71d Rework by review 2025-11-11 08:46:32 +07:00
Dmitry422
da3a2834d4 Recover accidentally deleted 2025-11-11 08:15:02 +07:00
Dmitry422
d320c4a46b Recover accidentally deleted 2025-11-11 08:14:08 +07:00
Dmitry422
d1f8ddc033 i dont know WTF 2025-11-10 23:23:34 +07:00
Dmitry422
d13b43f193 Merge branch 'DarkFlippers:dev' into dev 2025-11-10 23:15:51 +07:00
Dmitry422
01cc4cc3da finita la comedia 2025-11-10 23:13:28 +07:00
Dmitry422
7ee266752e Subghz signal settings counter edit in progress..... 2025-11-09 22:01:36 +07:00
Dmitry422
48b9dd2cc8 Hide additional settings under gebug mode (commented, not active) 2025-11-09 21:01:19 +07:00
MX
0530eda8d1 Merge remote-tracking branch 'OFW/dev' into dev [ci skip] 2025-11-08 17:51:07 +03:00
Dmitry422
9d1cee6d4c Subghz special signail_settings interface end of development 2025-11-08 04:52:36 +07:00
Dmitry422
b7d2ab7a0c Subghz special signail_settings interface 2025-11-08 04:51:23 +07:00
Dmitry422
a1c48c82f8 subghz signal settings gui (read from file and parse) 2025-11-08 01:30:07 +07:00
Mykhailo Shevchuk
7518dc73c2 Returning fix for reading PWD locked MFUL (#4295)
Co-authored-by: hedger <hedger@users.noreply.github.com>
2025-11-06 23:54:50 +04:00
Nathan N
f4c92dcd76 Fix MIFARE Plus SL1 sector overrun issue in state machine (#4288)
Co-authored-by: hedger <hedger@users.noreply.github.com>
2025-11-06 23:34:21 +04:00
Nathan N
c873eb5e36 Fix Ultralight EV1 regression (#4287)
* Fix EV1 regression introduced in e7634d7

* ./fbt format

---------

Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: hedger <hedger@nanode.su>
2025-11-06 23:05:16 +04:00
Zinong Li
a42e30f394 NFC FeliCa Minor Fix: FelicaPollerEventType should only be Incomplete if the tag is FeliCa Lite (#4279)
* poller read is only incomp if in Lite workflow

* another small fix for the "255/28 pages read" bug

---------

Co-authored-by: hedger <hedger@users.noreply.github.com>
2025-11-06 22:45:52 +04:00
Zinong Li
6d93a90a0a NFC lib: Expose nfc_common.h (#4283)
* expose nfc_common.h

* Update api_symbols.csv

---------

Co-authored-by: hedger <hedger@users.noreply.github.com>
2025-11-06 22:37:33 +04:00
Zinong Li
6c83a67173 NFC FeliCa Improvement: Dump All Systems (#4293)
* polling system code done

* select service done

* select system fixes. works fine now

* clean up select sys by idx

* reading, saving, loading done. render wrong

* render first layer works

* render fixed

* fix Lite Render triage

* regression Lite rendering

* sync API with 1.40 release

* clean up and lint

---------

Co-authored-by: hedger <hedger@users.noreply.github.com>
2025-11-06 22:30:02 +04:00
MX
22ee3bdae2 Merge remote-tracking branch 'OFW/dev' into dev 2025-11-06 20:29:16 +03:00
hedger
b54d63037b lib: infrared: fixed doxygen declarations to match implementation (#4305) 2025-11-06 20:40:18 +04:00
MMX
6a5ae6cc0d Infrared Universal remote DBs unit test & move infrared_signal / infrared_brute_force into lib (#4284)
* make infrared db unit tests

* fix the tests, no assets are in asset folder oh no

* fix formate

* ship ir app along with unit_test pkg

* libify ir signal and bruteforce parts

* small cleanup

* api: removed infrared methods (you can link with the lib if needed), unit_tests, infrared: adjusted to link with ir lib; api: added `__aeabi_f2d`

---------

Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: hedger <hedger@nanode.su>
2025-11-06 20:23:59 +04:00
an5t
67b906e6ba github: fail submit_sdk on catalog api error (#4294)
Co-authored-by: hedger <hedger@users.noreply.github.com>
2025-11-06 19:31:17 +04:00
Alexey Zakharov
772c944163 Devboard docs update (#4301)
Wi-Fi devboard pinout added, updated devboard hardware image, fixed the schematics link, and corrected typos.

Co-authored-by: hedger <hedger@users.noreply.github.com>
2025-11-06 17:21:44 +04:00
Dmitry422
7f7d1e1d32 Signal settings gui 2025-11-06 17:42:55 +07:00
Dmitry422
4f5ab0b15b Start working with SubGhz_signal_settings menu 2025-11-05 23:15:08 +07:00
MX
f177c0491d subghz counter freeze mode 2025-11-04 19:28:30 +03:00
MX
c87205fe9a upd changelog and readme 2025-11-04 16:12:19 +03:00
MX
79fc832356 subghz implement countermodes 2025-11-04 15:36:34 +03:00
MX
944c5ffb98 anmotors at4 C button 2025-11-02 22:13:16 +03:00
MX
ed2c40de4b to bool 2025-10-31 12:28:08 +03:00
MX
e392bff808 fmt and upd changelog 2025-10-31 12:12:07 +03:00
MMX
c37c5574ba Merge pull request #928 from Dmitry422/dev
Remove display_back_light bug from "DisplayBacklightEnforceOn"
2025-10-31 12:05:11 +03:00
Dmitry422
5db6a03811 Remove display_back_light bug from "DisplayBacklightEnforceOn" 2025-10-30 16:52:25 +07:00
MX
a553bc2f57 add extra check 2025-10-21 04:22:09 +03:00
MX
3e96806962 honeywell read old files with 62-63bits
change them on the fly to new format during reading, files are not replaced, they will contain old format, you can fix them manually by replacing header to FF FE and bits to 64
2025-10-21 04:12:22 +03:00
MX
cf5761860f upd changelog 2025-10-21 01:48:02 +03:00
MX
01c168e351 subghz il100 smart add manually support 2025-10-21 01:44:30 +03:00
MX
a1c8dfb61b subghz overflow experimental mode 2025-10-21 01:32:02 +03:00
MX
bd02e2f53c honeywell fix uint8 array using uint16 values, fix header, fix bits 2025-10-17 19:19:49 +03:00
MX
94076e6c5c honeywell show whole key and loop states instead 2025-10-17 14:05:25 +03:00
MX
d673fd5573 return honeywellsec with some fixes 2025-10-16 10:15:26 +03:00
MX
9506ccde2e upd changelog 2025-10-13 22:56:32 +03:00
139 changed files with 3216 additions and 257 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -131,6 +131,7 @@ App(
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
fap_libs=["infrared"],
)
App(

View File

@@ -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) {

View 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.

View File

@@ -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",
)

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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,
};

View File

@@ -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

View File

@@ -0,0 +1,2 @@
ADD_SCENE(example_date_time_input, input_date_time, InputDateTime)
ADD_SCENE(example_date_time_input, show_date_time, ShowDateTime)

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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"],
)

View File

@@ -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

View File

@@ -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"

View File

@@ -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)

View File

@@ -11,7 +11,7 @@
*/
#pragma once
#include "infrared_signal.h"
#include <lib/infrared/signal/infrared_signal.h>
/**
* @brief InfraredRemote opaque type declaration.

View File

@@ -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"],

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)

View File

@@ -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,

View File

@@ -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;

View File

@@ -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",

View 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);
}

View File

@@ -35,5 +35,6 @@ App(
"modules/submenu.h",
"modules/widget_elements/widget_element.h",
"modules/empty_screen.h",
"modules/date_time_input.h",
],
)

View 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);
}

View 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

View File

@@ -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");

View File

@@ -77,7 +77,7 @@ struct NotificationApp {
NotificationLedLayer display;
NotificationLedLayer led[NOTIFICATION_LED_COUNT];
uint8_t display_led_lock;
bool display_led_lock;
NotificationSettings settings;

View File

@@ -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(

View File

@@ -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]",
&sector_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;

View File

@@ -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;
}

View File

@@ -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 {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 863 B

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

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