Compare commits

..

2 Commits

Author SHA1 Message Date
d4rks1d33 94dcc82483 Official Flipper Mobile APP is now working
Build Dev Firmware / build (push) Successful in 16m58s
2026-06-12 22:36:08 -03:00
d4rks1d33 9b7499be36 Fix kia v3/v4/v5
Build Dev Firmware / build (push) Successful in 17m16s
2026-06-12 20:59:48 -03:00
54 changed files with 212 additions and 4093 deletions
@@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
@@ -1,13 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: www.buymeacoffee.com/zweiss
@@ -1,6 +0,0 @@
dist/*
.vscode
.clang-format
.editorconfig
.env
.ufbt
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Zachary Weiss
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1,65 +0,0 @@
## TODO
Known bugs:
- [X] File format issues when Track 2 data exists but Track 1 is left empty; doesn't seem to be setting the Track 2 field with anything (doesn't overwrite existing data). However, `flipper_format_read_string()` doesn't seem to return `false`. Is the bug in my code, or with `flipper_format`?
- [X] Review how it's done in [unirfremix (Sub-GHz Remote)](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/applications/main/unirfremix/unirfremix_app.c), as IIRC that can handle empty keys, despite using the `flipper_format` lib for parsing.
- [X] Attempting to play a track that doesn't have data results in a crash (as one might expect). Need to lock out users from selecting empty tracks in the config menu or do better error handling (*Doesn't crash now, but still should probably prevent users from being able to select*)
- [ ] Custom text input scene with expanded characterset (Add Manually) has odd behavior when navigating the keys near the numpad
Emulation:
- [X] Validate arha's bitmap changes, transition over to it fully
- [X] Test piezo TX (prelim tests promising)
- [ ] General code cleanup
- [X] Reverse track precompute & replay
- [ ] Parameter tuning, find best defaults, troubleshoot improperly parsed TX
- [ ] Pursue skunkworks TX improvement ideas listed below
- [ ] Remove or reimplement interpacket
- [ ] Verify `furi_delay_us` aliasing to `64us`
Scenes:
- [X] Finish emulation config scene (reverse track functionality; possibly expand settings list to include prefix/between/suffix options)
- [ ] "Edit" scene (generalize `input_value`)
- [ ] "Rename" scene (generalize `input_name`)
File management:
- [ ] Update Add Manually flow to reflect new file format (currently only sets Track 2)
- [ ] Validation of card track data?
- [ ] Parsing loaded files into human-readable fields? (would we need to specify card type to decode correctly?)
## Skunkworks ideas
Internal TX improvements:
- [ ] Attempt downstream modulation techniques in addition to upstream, like the LF RFID worker does when writing.
- [ ] Implement using the timer system, rather than direct-writing to pins
- [X] Use the NFC (HF RFID) coil instead of or in addition to the LF coil (likely unfruitful from initial tests; we can enable/disable the oscillating field, but even with transparent mode to the ST25R3916, it seems we don't get low-enough-level control to pull it high/low correctly)
- [ ] Add "subcarriers" to each half-bit transmitted (wiggle the pin high and low rapidly)
- [ ] Piezo subcarrier tests
- [ ] LF subcarrier tests
- [X] Retry NFC oscillating field?
External RX options:
1. [TTL / PS/2 mag reader connected to UART](https://www.alibaba.com/product-detail/Mini-portable-12-3-tracks-usb_60679900708.html) (bulky, harder to source, but likely easiest to read over GPIO, and means one can read all tracks)
2. Square audio jack mag reader (this may be DOA; seems like newer versions of the Square modules have some form of preprocessing that also modifies the signal, perhaps in an effort to discourage folks using their hardware independent of their software. Thanks arha for your work investigating this)
3. Some [read-head](https://www.alibaba.com/product-detail/POS-1-2-3-triple-track_60677205741.html) directly connected to GPIO, ADC'd, and parsed all on the Flipper. Likely the most compact and cheapest module option, but also would require some signal-processing cleverness.
4. USB HID input over pre-existing USB C port infeasible; seems the FZ cannot act as an HID host (MCU is the STM32WB55RGV6TR).
5. Custom USB HID host hat based on the [MAX3421E](https://www.analog.com/en/products/max3421e.html) (USB Host Controller w/ SPI), like the [Arduino USB Host Shield](https://docs.arduino.cc/retired/shields/arduino-usb-host-shield). Would be a large but worthwhile project in its own right, and would let one connect any USB HID reader they desire (or other HID devices for other projects). Suggestion credit to arha.
6. Implement a software/firmware USB host solution over GPIO like [esp32_usb_soft_host (for ESP32)](https://github.com/sdima1357/esp32_usb_soft_host) or [V-USB (for AVR)](https://www.obdev.at/products/vusb/index.html). Suggestion credit to arha. Also a massive undertaking, but valuable in and of itself.
## arha todo & notes
Attempting to exploit flipper hardware to some extent
- [X] Preprocess all MSR data into bitwise arrays, including manchester encoding.
- [ ] Feed bits from timers
- [ ] Sync to the lfrfid timer and experiment representing a field flip with a few cycles of a high frequency carrier, like the 125khz lfrfid one. Perhaps mag readers' frontends will lowpass such signals, and keep only the low frequency component, in an attempt to drown out nearby noise?
- [X] Can the CC1101 radio be used in any way? Driving it from GD0 can achieve 50us, or about 10khz. Probably more with sync/packet mode. **Currently under testing**. The signal is extra noisy with a very wide bandwidth, but, in theory, it can work
- [ ] Can the 5V pin act as a coil driver? I've read reports it can drive 0.4A, other reports it can drive 2A. It boils down to bq25896 being fast enough. Ref: bq25896_enable_otg, which will probably need bypassing kernel libs and calling furi_hal_i2c_tx/furi_hal_i2c_tx whatever calls from Cube libs.
- [ ] Investigate transparent mode on 3916
- [ ] Can the piezo be used at its resonant frequency? I've seen LF signals being emulated with [nothing but headphones](https://github.com/smre/DCF77/blob/master/DCF77.py#L124) running a subharmonic; the wheel brake on some carts seems to react to audiofreq signals (or the RF emission from driving a speaker)
## Hummus's Fork
I made this fork initially to add reading capability using UART magnetic card readers.
Things that changed in this fork:
- Added a basic card reading ability
- Added a function to parse a new MagDevice from a Card String (%Track1?;Track2;Track3?;)
- Swapped the pins between A6 to A7 on the card that I'm using, might add this to configuration scene later on
- Adapted some of the APIs to the most recent firmware changes.
@@ -1,29 +0,0 @@
# magspoof_flipper
WIP of MagSpoof for the Flipper Zero. Basic TX of saved files confirmed working against an MSR90 with an external H-bridge module mirroring Samy Kamkar's design. Sample files are included in `resources`.
RFID coil output weaker; able to be picked up/detected by more compact mag readers such as Square, but yet to have success with it being decoded/parsed properly. Additional investigation was made into alternate internal TX options (CC1101, ST25R3916, piezo); tentatively, RFID coil + speaker (`LF + P` config setting) results in the strongest internal TX tested to date but still weaker than a dedicated external module or an actual card swipe (and sounds like a dial-up modem from hell). For information on the state of internal TX &/or misc TODOs, known bugs, etc, confer `NOTES.md`.
**Disclaimer**: use responsibly, and at your own risk. ***I neither condone nor am sympathetic to malicious uses of my code.*** Please only use this with magstripe cards and mag readers you own — this is solely meant as a proof of concept for educational purposes. Similarly, if using internal TX: while in my testing, I've seen no reason to believe this could damage the RFID (or other) hardware, this is inherently driving the coil in ways it was not designed or intended for; I take no responsibility for fried/bricked Flippers.
## GPIO TX Module
For those desiring better TX than the internal RFID coil can offer, one can build the module below, consisting of an H-bridge, a capacitor, and a coil. Custom GPIO pin selection is a planned future feature.
<img src="https://user-images.githubusercontent.com/20050953/215654078-1f4b370e-21b3-4324-b63c-3bbbc643120e.png" alt="Wiring diagram" title="Wiring diagram" style="height:320px">
----
## Credits
This project interpolates work from [Samy Kamkar](https://github.com/samyk/)'s [original MagSpoof project](https://github.com/samyk/magspoof), [Alexey D. (dunaevai135)](https://github.com/dunaevai135/) & [Alexandr Yaroshevich](https://github.com/AYaro)'s [Flipper hackathon project](https://github.com/dunaevai135/flipperzero-firmware/tree/dev/applications/magspoof), and the [Flipper team](https://github.com/flipperdevices)'s [LF RFID](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/main/lfrfid) and [SubGhz](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/main/subghz) apps.
Many thanks to everyone who has helped in addition to those above, most notably:
- [arha](https://github.com/arha) for bitmapping work, skunkworks testing, and innumerable suggestions/ideas/feedback (now a collaborator!)
- [Zalán Kórósi (Z4urce)](https://github.com/Z4urce) for an earlier app icon
- [Salvatore Sanfilippo (antirez)](https://github.com/antirez) for bitmapping suggestions and general C wisdom
- [skotopes](https://github.com/skotopes) for RFID consultation
- [Tiernan (NVX)](https://github.com/nvx) + dlz for NFC consultation
- davethepirate for EE insight and acting as a sounding board
- [cool4uma](https://github.com/cool4uma) for their work on custom text_input scenes
- [hummusec](https://github.com/hummusec) for testing of UART RX
- [xMasterX](https://github.com/xMasterX) and [WillyJL](https://github.com/Willy-JL) for keeping the app updated across API changes while I was away!
- Everyone else I've had the pleasure of chatting with!
@@ -1,25 +0,0 @@
App(
appid="magspoof",
name="MagSpoof",
apptype=FlipperAppType.EXTERNAL,
entry_point="mag_app",
cdefines=["APP_MAG"],
requires=[
"gui",
"storage",
"notification",
"dialogs",
],
provides=[],
stack_size=6 * 1024,
order=64,
fap_icon="icons/mag_10px.png",
fap_category="GPIO",
fap_icon_assets="icons",
fap_icon_assets_symbol="mag",
fap_version=(0, 10), # major, minor
fap_description="Enables wireless transmission of magstripe data",
fap_author="Zachary Weiss",
fap_weburl="https://github.com/zacharyweiss/magspoof_flipper",
fap_file_assets="resources",
)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

@@ -1,494 +0,0 @@
#include "mag_helpers.h"
#define TAG "MagHelpers"
#define ZERO_PREFIX 25 // n zeros prefix
#define ZERO_BETWEEN 53 // n zeros between tracks
#define ZERO_SUFFIX 25 // n zeros suffix
#define REPEAT_DELAY_MS 50
// bits per char on a given track
const uint8_t bitlen[] = {7, 5, 5};
// char offset by track
const int sublen[] = {32, 48, 48};
uint8_t last_value = 2;
void play_halfbit(bool value, MagState* state) {
switch(state->tx) {
case MagTxStateRFID:
furi_hal_gpio_write(&gpio_rfid_carrier_out, value);
/*furi_hal_gpio_write(RFID_PIN_OUT, !value);
furi_hal_gpio_write(RFID_PIN_OUT, value);
furi_hal_gpio_write(RFID_PIN_OUT, !value);
furi_hal_gpio_write(RFID_PIN_OUT, value);*/
break;
case MagTxStateGPIO:
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_input), value);
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_output), !value);
break;
case MagTxStatePiezo:
furi_hal_gpio_write(&gpio_speaker, value);
/*furi_hal_gpio_write(&gpio_speaker, !value);
furi_hal_gpio_write(&gpio_speaker, value);
furi_hal_gpio_write(&gpio_speaker, !value);
furi_hal_gpio_write(&gpio_speaker, value);*/
break;
case MagTxStateLF_P:
furi_hal_gpio_write(&gpio_rfid_carrier_out, value);
furi_hal_gpio_write(&gpio_speaker, value);
/* // Weaker but cleaner signal
if(value) {
furi_hal_gpio_write(RFID_PIN_OUT, value);
furi_hal_gpio_write(&gpio_speaker, value);
furi_delay_us(10);
furi_hal_gpio_write(RFID_PIN_OUT, !value);
furi_hal_gpio_write(&gpio_speaker, !value);
} else {
furi_delay_us(10);
}*/
/*furi_hal_gpio_write(RFID_PIN_OUT, value);
furi_hal_gpio_write(&gpio_speaker, value);
furi_hal_gpio_write(RFID_PIN_OUT, !value);
furi_hal_gpio_write(&gpio_speaker, !value);
furi_hal_gpio_write(RFID_PIN_OUT, value);
furi_hal_gpio_write(&gpio_speaker, value);*/
break;
case MagTxStateNFC:
// turn on for duration of half-bit? or "blip" the field on / off?
// getting nothing from the mag reader either way
//(value) ? furi_hal_nfc_ll_txrx_on() : furi_hal_nfc_ll_txrx_off();
if(last_value == 2 || value != (bool)last_value) {
//furi_hal_nfc_ll_txrx_on();
furi_delay_us(64);
//furi_hal_nfc_ll_txrx_off();
}
break;
case MagTxCC1101_434:
case MagTxCC1101_868:
if(last_value == 2 || value != (bool)last_value) {
furi_hal_gpio_write(&gpio_cc1101_g0, true);
furi_delay_us(64);
furi_hal_gpio_write(&gpio_cc1101_g0, false);
}
break;
default:
break;
}
last_value = value;
}
void play_track(uint8_t* bits_manchester, uint16_t n_bits, MagState* state, bool reverse) {
for(uint16_t i = 0; i < n_bits; i++) {
uint16_t j = (reverse) ? (n_bits - i - 1) : i;
uint8_t byte = j / 8;
uint8_t bitmask = 1 << (7 - (j % 8));
/* Bits are stored in their arrays like on a card (LSB first). This is not how usually bits are stored in a
* byte, with the MSB first. the var bitmask creates the pattern to iterate through each bit, LSB first, like so
* 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x80... masking bits one by one from the current byte
*
* I've chosen this LSB approach since bits and bytes are hard enough to visualize with the 5/8 and 7/8 encoding
* MSR uses. It's a biiit more complicated to process, but visualizing it with printf or a debugger is
* infinitely easier
*
* Encoding the following pairs of 5 bits as 5/8: A1234 B1234 C1234 D1234
* using this LSB format looks like: A1234B12 34C1234D 12340000
* using the MSB format, looks like: 21B4321A D4321C43 00004321
* this means reading each byte backwards when printing/debugging, and the jumping 16 bits ahead, reading 8 more
* bits backward, jumping 16 more bits ahead.
*
* I find this much more convenient for debugging, with the tiny incovenience of reading the bits in reverse
* order. Thus, the reason for the bitmask above
*/
bool bit = !!(bits_manchester[byte] & bitmask);
// TODO: reimplement timing delays. Replace fixed furi_hal_cortex_delay_us to wait instead to a specific value
// for DWT->CYCCNT. Note timer is aliased to 64us as per
// #define FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND (SystemCoreClock / 1000000) | furi_hal_cortex.c
play_halfbit(bit, state);
furi_delay_us(state->us_clock);
// if (i % 2 == 1) furi_delay_us(state->us_interpacket);
}
}
void tx_init_rfid() {
// initialize RFID system for TX
furi_hal_ibutton_pin_configure();
// furi_hal_ibutton_start_drive();
furi_hal_ibutton_pin_write(false);
// Initializing at GpioSpeedLow seems sufficient for our needs; no improvements seen by increasing speed state
// this doesn't seem to make a difference, leaving it in
furi_hal_gpio_init(&gpio_rfid_data_in, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_rfid_data_in, false);
// false->ground RFID antenna; true->don't ground
// skotopes (RFID dev) say normally you'd want RFID_PULL in high for signal forming, while modulating RFID_OUT
// dunaevai135 had it low in their old code. Leaving low, as it doesn't seem to make a difference on my janky antenna
furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_nfc_irq_rfid_pull, false);
furi_hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_delay_ms(300);
}
void tx_deinit_rfid() {
// reset RFID system
furi_hal_gpio_write(&gpio_rfid_carrier_out, 0);
furi_hal_rfid_pins_reset();
}
void tx_init_rf(int hz) {
// presets and frequency will need some experimenting
furi_hal_subghz_reset();
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_set_frequency_and_path(hz);
furi_hal_subghz_tx();
furi_hal_gpio_write(&gpio_cc1101_g0, false);
}
void tx_init_piezo() {
// TODO: some special mutex acquire procedure? c.f. furi_hal_speaker.c
furi_hal_gpio_init(&gpio_speaker, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
}
void tx_deinit_piezo() {
// TODO: some special mutex release procedure?
furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
bool tx_init(MagState* state) {
// Initialize configured TX method
switch(state->tx) {
case MagTxStateRFID:
tx_init_rfid();
break;
case MagTxStateGPIO:
// gpio_item_configure_all_pins(GpioModeOutputPushPull);
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_input),
GpioModeOutputPushPull,
GpioPullNo,
GpioSpeedLow);
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_output),
GpioModeOutputPushPull,
GpioPullNo,
GpioSpeedLow);
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_enable),
GpioModeOutputPushPull,
GpioPullNo,
GpioSpeedLow);
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_enable), 1);
// had some issues with ~300; bumped higher temporarily
furi_delay_ms(500);
break;
case MagTxStatePiezo:
tx_init_piezo();
break;
case MagTxStateLF_P:
tx_init_piezo();
tx_init_rfid();
break;
case MagTxStateNFC:
//furi_hal_nfc_exit_sleep();
break;
case MagTxCC1101_434:
tx_init_rf(434000000);
break;
case MagTxCC1101_868:
tx_init_rf(868000000);
break;
default:
return false;
}
return true;
}
bool tx_deinit(MagState* state) {
// Reset configured TX method
switch(state->tx) {
case MagTxStateRFID:
tx_deinit_rfid();
break;
case MagTxStateGPIO:
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_input), 0);
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_output), 0);
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_enable), 0);
// set back to analog output mode? - YES
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_input), GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_output), GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_enable), GpioModeAnalog, GpioPullNo, GpioSpeedLow);
//gpio_item_configure_all_pins(GpioModeAnalog);
break;
case MagTxStatePiezo:
tx_deinit_piezo();
break;
case MagTxStateLF_P:
tx_deinit_piezo();
tx_deinit_rfid();
break;
case MagTxStateNFC:
//furi_hal_nfc_ll_txrx_off();
//furi_hal_nfc_start_sleep();
break;
case MagTxCC1101_434:
case MagTxCC1101_868:
furi_hal_gpio_write(&gpio_cc1101_g0, false);
furi_hal_subghz_reset();
furi_hal_subghz_idle();
break;
default:
return false;
}
return true;
}
void mag_spoof(Mag* mag) {
MagState* state = &mag->state;
// TODO: cleanup this section. Possibly move precompute + tx_init to emulate_on_enter?
FuriString* ft1 = mag->mag_dev->dev_data.track[0].str;
FuriString* ft2 = mag->mag_dev->dev_data.track[1].str;
FuriString* ft3 = mag->mag_dev->dev_data.track[2].str;
char *data1, *data2, *data3;
data1 = malloc(furi_string_size(ft1) + 1);
data2 = malloc(furi_string_size(ft2) + 1);
data3 = malloc(furi_string_size(ft3) + 1);
strncpy(data1, furi_string_get_cstr(ft1), furi_string_size(ft1));
strncpy(data2, furi_string_get_cstr(ft2), furi_string_size(ft2));
strncpy(data3, furi_string_get_cstr(ft3), furi_string_size(ft3));
if(furi_log_get_level() >= FuriLogLevelDebug) {
debug_mag_string(data1, bitlen[0], sublen[0]);
debug_mag_string(data2, bitlen[1], sublen[1]);
debug_mag_string(data3, bitlen[2], sublen[2]);
}
uint8_t bits_t1_raw[64] = {0x00}; // 68 chars max track 1 + 1 char crc * 7 approx =~ 483 bits
uint8_t bits_t1_manchester[128] = {0x00}; // twice the above
uint16_t bits_t1_count = mag_encode(
data1, (uint8_t*)bits_t1_manchester, (uint8_t*)bits_t1_raw, bitlen[0], sublen[0]);
uint8_t bits_t2_raw[64] = {0x00}; // 68 chars max track 1 + 1 char crc * 7 approx =~ 483 bits
uint8_t bits_t2_manchester[128] = {0x00}; // twice the above
uint16_t bits_t2_count = mag_encode(
data2, (uint8_t*)bits_t2_manchester, (uint8_t*)bits_t2_raw, bitlen[1], sublen[1]);
uint8_t bits_t3_raw[64] = {0x00};
uint8_t bits_t3_manchester[128] = {0x00};
uint16_t bits_t3_count = mag_encode(
data3, (uint8_t*)bits_t3_manchester, (uint8_t*)bits_t3_raw, bitlen[2], sublen[2]);
if(furi_log_get_level() >= FuriLogLevelDebug) {
printf(
"Manchester bitcount: T1: %d, T2: %d, T3: %d\r\n",
bits_t1_count,
bits_t2_count,
bits_t3_count);
printf("T1 raw: ");
for(int i = 0; i < bits_t1_count / 16; i++) printf("%02x ", bits_t1_raw[i]);
printf("\r\nT1 manchester: ");
for(int i = 0; i < bits_t1_count / 8; i++) printf("%02x ", bits_t1_manchester[i]);
printf("\r\nT2 raw: ");
for(int i = 0; i < bits_t2_count / 16; i++) printf("%02x ", bits_t2_raw[i]);
printf("\r\nT2 manchester: ");
for(int i = 0; i < bits_t2_count / 8; i++) printf("%02x ", bits_t2_manchester[i]);
printf("\r\nT3 raw: ");
for(int i = 0; i < bits_t3_count / 16; i++) printf("%02x ", bits_t3_raw[i]);
printf("\r\nT3 manchester: ");
for(int i = 0; i < bits_t3_count / 8; i++) printf("%02x ", bits_t3_manchester[i]);
printf("\r\nBitwise emulation done\r\n\r\n");
}
last_value = 2;
bool bit = false;
if(!tx_init(state)) return;
uint8_t i = 0;
do {
FURI_CRITICAL_ENTER();
for(uint16_t i = 0; i < (ZERO_PREFIX * 2); i++) {
// is this right?
if(!!(i % 2)) bit ^= 1;
play_halfbit(bit, state);
furi_delay_us(state->us_clock);
}
if((state->track == MagTrackStateOneAndTwo) || (state->track == MagTrackStateOne))
play_track((uint8_t*)bits_t1_manchester, bits_t1_count, state, false);
if((state->track == MagTrackStateOneAndTwo))
for(uint16_t i = 0; i < (ZERO_BETWEEN * 2); i++) {
if(!!(i % 2)) bit ^= 1;
play_halfbit(bit, state);
furi_delay_us(state->us_clock);
}
if((state->track == MagTrackStateOneAndTwo) || (state->track == MagTrackStateTwo))
play_track(
(uint8_t*)bits_t2_manchester,
bits_t2_count,
state,
(state->reverse == MagReverseStateOn));
if((state->track == MagTrackStateThree))
play_track((uint8_t*)bits_t3_manchester, bits_t3_count, state, false);
for(uint16_t i = 0; i < (ZERO_SUFFIX * 2); i++) {
if(!!(i % 2)) bit ^= 1;
play_halfbit(bit, state);
furi_delay_us(state->us_clock);
}
FURI_CRITICAL_EXIT();
i++;
FURI_LOG_D(
TAG, "TX %u (n_repeats: %u, repeat_mode: %u)", i, state->n_repeats, state->repeat_mode);
furi_delay_ms(REPEAT_DELAY_MS);
} while((i < state->n_repeats) && state->repeat_mode);
free(data1);
free(data2);
free(data3);
tx_deinit(state);
}
uint16_t add_bit(bool value, uint8_t* out, uint16_t count) {
uint8_t bit = count % 8;
uint8_t byte = count / 8;
if(value) {
out[byte] |= 0x01;
}
if(bit < 7) out[byte] <<= 1;
return count + 1;
}
uint16_t add_bit_manchester(bool value, uint8_t* out, uint16_t count) {
static bool toggle = 0;
toggle ^= 0x01;
count = add_bit(toggle, out, count);
if(value) toggle ^= 0x01;
count = add_bit(toggle, out, count);
return count;
}
uint16_t mag_encode(
char* data,
uint8_t* out_manchester,
uint8_t* out_raw,
uint8_t track_bits,
uint8_t track_ascii_offset) {
/*
* track_bits - the number of raw (data) bits on the track. on ISO cards, that's 7 for track 1, or 5 for 2/3 - this is samy's bitlen
* - this count includes the parity bit
* track_ascii_offset - how much the ascii values are offset. track 1 makes space (ascii 32) become data 0x00,
* - tracks 2/3 make ascii "0" become data 0x00 - this is samy's sublen
*
*/
uint16_t raw_bits_count = 0;
uint16_t output_count = 0;
int tmp, crc, lrc = 0;
/* // why are we adding zeros to the encoded string if we're also doing it while playing?
for(int i = 0; i < ZERO_PREFIX; i++) {
output_count = add_bit_manchester(0, out_manchester, output_count);
raw_bits_count = add_bit(0, out_raw, raw_bits_count);
}*/
for(int i = 0; *(data + i) != 0; i++) {
crc = 1;
tmp = *(data + i) - track_ascii_offset;
for(int j = 0; j < track_bits - 1; j++) {
crc ^= tmp & 1;
lrc ^= (tmp & 1) << j;
raw_bits_count = add_bit(tmp & 0x01, out_raw, raw_bits_count);
output_count = add_bit_manchester(tmp & 0x01, out_manchester, output_count);
tmp >>= 1;
}
raw_bits_count = add_bit(crc, out_raw, raw_bits_count);
output_count = add_bit_manchester(crc, out_manchester, output_count);
}
// LRC byte
tmp = lrc;
crc = 1;
for(int j = 0; j < track_bits - 1; j++) {
crc ^= tmp & 0x01;
raw_bits_count = add_bit(tmp & 0x01, out_raw, raw_bits_count);
output_count = add_bit_manchester(tmp & 0x01, out_manchester, output_count);
tmp >>= 1;
}
raw_bits_count = add_bit(crc, out_raw, raw_bits_count);
output_count = add_bit_manchester(crc, out_manchester, output_count);
return output_count;
}
void debug_mag_string(char* data, uint8_t track_bits, uint8_t track_ascii_offset) {
uint8_t bits_raw[64] = {0}; // 68 chars max track 1 + 1 char crc * 7 approx =~ 483 bits
uint8_t bits_manchester[128] = {0}; // twice the above
int numbits = 0;
printf("Encoding [%s] with %d bits\r\n", data, track_bits);
numbits = mag_encode(
data, (uint8_t*)bits_manchester, (uint8_t*)bits_raw, track_bits, track_ascii_offset);
printf("Got %d bits\r\n", numbits);
printf("Raw byte stream: ");
for(int i = 0; i < numbits / 8 / 2; i++) {
printf("%02x", bits_raw[i]);
if(i % 4 == 3) printf(" ");
}
printf("\r\n");
printf("Bits ");
int space_counter = 0;
for(int i = 0; i < numbits / 2; i++) {
/*if(i < ZERO_PREFIX) {
printf("X");
continue;
} else if(i == ZERO_PREFIX) {
printf(" ");
space_counter = 0;
}*/
printf("%01x", (bits_raw[i / 8] & (1 << (7 - (i % 8)))) != 0);
if((space_counter) % track_bits == track_bits - 1) printf(" ");
space_counter++;
}
printf("\r\n");
printf("Manchester encoded, byte stream: ");
for(int i = 0; i < numbits / 8; i++) {
printf("%02x", bits_manchester[i]);
if(i % 4 == 3) printf(" ");
}
printf("\r\n\r\n");
}
@@ -1,25 +0,0 @@
#include "../mag_i.h"
#include <stdio.h>
#include <string.h>
void play_halfbit(bool value, MagState* state);
void play_track(uint8_t* bits_manchester, uint16_t n_bits, MagState* state, bool reverse);
void tx_init_rf(int hz);
void tx_init_rfid();
void tx_init_piezo();
bool tx_init(MagState* state);
void tx_deinit_piezo();
void tx_deinit_rfid();
bool tx_deinit(MagState* state);
uint16_t add_bit(bool value, uint8_t* out, uint16_t count);
uint16_t add_bit_manchester(bool value, uint8_t* out, uint16_t count);
uint16_t mag_encode(
char* data,
uint8_t* out_manchester,
uint8_t* out_raw,
uint8_t track_bits,
uint8_t track_ascii_offset);
void debug_mag_string(char* data, uint8_t track_bits, uint8_t track_ascii_offset);
void mag_spoof(Mag* mag);
@@ -1,583 +0,0 @@
#include "mag_text_input.h"
#include <gui/elements.h>
#include <assets_icons.h>
#include <furi.h>
struct Mag_TextInput {
View* view;
FuriTimer* timer;
};
typedef struct {
const char text;
const uint8_t x;
const uint8_t y;
} Mag_TextInputKey;
typedef struct {
const char* header;
char* text_buffer;
size_t text_buffer_size;
bool clear_default_text;
Mag_TextInputCallback callback;
void* callback_context;
uint8_t selected_row;
uint8_t selected_column;
// Mag_TextInputValidatorCallback validator_callback;
// void* validator_callback_context;
// FuriString* validator_text;
// bool validator_message_visible;
} Mag_TextInputModel;
static const uint8_t keyboard_origin_x = 1;
static const uint8_t keyboard_origin_y = 29;
static const uint8_t keyboard_row_count = 3;
#define ENTER_KEY '\r'
#define BACKSPACE_KEY '\b'
static const Mag_TextInputKey keyboard_keys_row_1[] = {
{'q', 1, 8},
{'w', 9, 8},
{'e', 17, 8},
{'r', 25, 8},
{'t', 33, 8},
{'y', 41, 8},
{'u', 49, 8},
{'i', 57, 8},
{'o', 65, 8},
{'p', 73, 8},
{'0', 81, 8},
{'1', 89, 8},
{'2', 97, 8},
{'3', 105, 8},
{'%', 113, 8},
{'^', 120, 8},
};
static const Mag_TextInputKey keyboard_keys_row_2[] = {
{'a', 1, 20},
{'s', 9, 20},
{'d', 18, 20},
{'f', 25, 20},
{'g', 33, 20},
{'h', 41, 20},
{'j', 49, 20},
{'k', 57, 20},
{'l', 65, 20},
{BACKSPACE_KEY, 72, 12},
{'4', 89, 20},
{'5', 97, 20},
{'6', 105, 20},
{'/', 113, 20},
{'?', 120, 20},
};
static const Mag_TextInputKey keyboard_keys_row_3[] = {
{'z', 1, 32},
{'x', 9, 32},
{'c', 18, 32},
{'v', 25, 32},
{'b', 33, 32},
{'n', 41, 32},
{'m', 49, 32},
{'_', 57, 32},
{ENTER_KEY, 64, 23},
{'7', 89, 32},
{'8', 97, 32},
{'9', 105, 32},
{';', 113, 32},
{'=', 120, 32},
};
static uint8_t get_row_size(uint8_t row_index) {
uint8_t row_size = 0;
switch(row_index + 1) {
case 1:
row_size = sizeof(keyboard_keys_row_1) / sizeof(Mag_TextInputKey);
break;
case 2:
row_size = sizeof(keyboard_keys_row_2) / sizeof(Mag_TextInputKey);
break;
case 3:
row_size = sizeof(keyboard_keys_row_3) / sizeof(Mag_TextInputKey);
break;
}
return row_size;
}
static const Mag_TextInputKey* get_row(uint8_t row_index) {
const Mag_TextInputKey* row = NULL;
switch(row_index + 1) {
case 1:
row = keyboard_keys_row_1;
break;
case 2:
row = keyboard_keys_row_2;
break;
case 3:
row = keyboard_keys_row_3;
break;
}
return row;
}
static char get_selected_char(Mag_TextInputModel* model) {
return get_row(model->selected_row)[model->selected_column].text;
}
static bool char_is_lowercase(char letter) {
return (letter >= 0x61 && letter <= 0x7A);
}
static char char_to_uppercase(const char letter) {
if(letter == '_') {
return 0x20;
} else if(isalpha(letter)) {
return (letter - 0x20);
} else {
return letter;
}
}
static void mag_text_input_backspace_cb(Mag_TextInputModel* model) {
uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer);
if(text_length > 0) {
model->text_buffer[text_length - 1] = 0;
}
}
static void mag_text_input_view_draw_callback(Canvas* canvas, void* _model) {
Mag_TextInputModel* model = _model;
// uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
uint8_t needed_string_width = canvas_width(canvas) - 8;
uint8_t start_pos = 4;
const char* text = model->text_buffer;
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(canvas, 2, 8, model->header);
elements_slightly_rounded_frame(canvas, 1, 12, 126, 15);
if(canvas_string_width(canvas, text) > needed_string_width) {
canvas_draw_str(canvas, start_pos, 22, "...");
start_pos += 6;
needed_string_width -= 8;
}
while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) {
text++;
}
if(model->clear_default_text) {
elements_slightly_rounded_box(
canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 22, "|");
canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 22, "|");
}
canvas_draw_str(canvas, start_pos, 22, text);
canvas_set_font(canvas, FontKeyboard);
for(uint8_t row = 0; row <= keyboard_row_count; row++) {
const uint8_t column_count = get_row_size(row);
const Mag_TextInputKey* keys = get_row(row);
for(size_t column = 0; column < column_count; column++) {
if(keys[column].text == ENTER_KEY) {
canvas_set_color(canvas, ColorBlack);
if(model->selected_row == row && model->selected_column == column) {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySaveSelected_24x11);
} else {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySave_24x11);
}
} else if(keys[column].text == BACKSPACE_KEY) {
canvas_set_color(canvas, ColorBlack);
if(model->selected_row == row && model->selected_column == column) {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeyBackspaceSelected_16x9);
} else {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeyBackspace_16x9);
}
} else {
if(model->selected_row == row && model->selected_column == column) {
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(
canvas,
keyboard_origin_x + keys[column].x - 1,
keyboard_origin_y + keys[column].y - 8,
7,
10);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_set_color(canvas, ColorBlack);
}
if(model->clear_default_text || (char_is_lowercase(keys[column].text))) {
canvas_draw_glyph(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
char_to_uppercase(keys[column].text));
//keys[column].text);
} else {
canvas_draw_glyph(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
keys[column].text);
}
}
}
}
/*if(model->validator_message_visible) {
canvas_set_font(canvas, FontSecondary);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 8, 10, 110, 48);
canvas_set_color(canvas, ColorBlack);
canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42);
canvas_draw_rframe(canvas, 8, 8, 112, 50, 3);
canvas_draw_rframe(canvas, 9, 9, 110, 48, 2);
elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text));
canvas_set_font(canvas, FontKeyboard);
}*/
}
static void mag_text_input_handle_up(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
UNUSED(mag_text_input);
if(model->selected_row > 0) {
model->selected_row--;
if(model->selected_column > get_row_size(model->selected_row) - 6) {
model->selected_column = model->selected_column + 1;
}
}
}
static void mag_text_input_handle_down(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
UNUSED(mag_text_input);
if(model->selected_row < keyboard_row_count - 1) {
model->selected_row++;
if(model->selected_column > get_row_size(model->selected_row) - 4) {
model->selected_column = model->selected_column - 1;
}
}
}
static void mag_text_input_handle_left(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
UNUSED(mag_text_input);
if(model->selected_column > 0) {
model->selected_column--;
} else {
model->selected_column = get_row_size(model->selected_row) - 1;
}
}
static void mag_text_input_handle_right(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
UNUSED(mag_text_input);
if(model->selected_column < get_row_size(model->selected_row) - 1) {
model->selected_column++;
} else {
model->selected_column = 0;
}
}
static void
mag_text_input_handle_ok(Mag_TextInput* mag_text_input, Mag_TextInputModel* model, bool shift) {
UNUSED(mag_text_input);
char selected = get_selected_char(model);
uint8_t text_length = strlen(model->text_buffer);
if(shift) {
selected = char_to_uppercase(selected);
}
if(selected == ENTER_KEY) {
/*if(model->validator_callback &&
(!model->validator_callback(
model->text_buffer, model->validator_text, model->validator_callback_context))) {
model->validator_message_visible = true;
furi_timer_start(mag_text_input->timer, furi_kernel_get_tick_frequency() * 4);
} else*/
if(model->callback != 0 && text_length > 0) {
model->callback(model->callback_context);
}
} else if(selected == BACKSPACE_KEY) {
mag_text_input_backspace_cb(model);
} else {
if(model->clear_default_text) {
text_length = 0;
}
if(text_length < (model->text_buffer_size - 1)) {
if(char_is_lowercase(selected)) {
selected = char_to_uppercase(selected);
}
model->text_buffer[text_length] = selected;
model->text_buffer[text_length + 1] = 0;
}
}
model->clear_default_text = false;
}
static bool mag_text_input_view_input_callback(InputEvent* event, void* context) {
Mag_TextInput* mag_text_input = context;
furi_assert(mag_text_input);
bool consumed = false;
// Acquire model
Mag_TextInputModel* model = view_get_model(mag_text_input->view);
/* if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) &&
model->validator_message_visible) {
model->validator_message_visible = false;
consumed = true;
} else*/
if(event->type == InputTypeShort) {
consumed = true;
switch(event->key) {
case InputKeyUp:
mag_text_input_handle_up(mag_text_input, model);
break;
case InputKeyDown:
mag_text_input_handle_down(mag_text_input, model);
break;
case InputKeyLeft:
mag_text_input_handle_left(mag_text_input, model);
break;
case InputKeyRight:
mag_text_input_handle_right(mag_text_input, model);
break;
case InputKeyOk:
mag_text_input_handle_ok(mag_text_input, model, false);
break;
default:
consumed = false;
break;
}
} else if(event->type == InputTypeLong) {
consumed = true;
switch(event->key) {
case InputKeyUp:
mag_text_input_handle_up(mag_text_input, model);
break;
case InputKeyDown:
mag_text_input_handle_down(mag_text_input, model);
break;
case InputKeyLeft:
mag_text_input_handle_left(mag_text_input, model);
break;
case InputKeyRight:
mag_text_input_handle_right(mag_text_input, model);
break;
case InputKeyOk:
mag_text_input_handle_ok(mag_text_input, model, true);
break;
case InputKeyBack:
mag_text_input_backspace_cb(model);
break;
default:
consumed = false;
break;
}
} else if(event->type == InputTypeRepeat) {
consumed = true;
switch(event->key) {
case InputKeyUp:
mag_text_input_handle_up(mag_text_input, model);
break;
case InputKeyDown:
mag_text_input_handle_down(mag_text_input, model);
break;
case InputKeyLeft:
mag_text_input_handle_left(mag_text_input, model);
break;
case InputKeyRight:
mag_text_input_handle_right(mag_text_input, model);
break;
case InputKeyBack:
mag_text_input_backspace_cb(model);
break;
default:
consumed = false;
break;
}
}
// Commit model
view_commit_model(mag_text_input->view, consumed);
return consumed;
}
void mag_text_input_timer_callback(void* context) {
furi_assert(context);
Mag_TextInput* mag_text_input = context;
UNUSED(mag_text_input);
/*with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{ model->validator_message_visible = false; },
true);*/
}
Mag_TextInput* mag_text_input_alloc() {
Mag_TextInput* mag_text_input = malloc(sizeof(Mag_TextInput));
mag_text_input->view = view_alloc();
view_set_context(mag_text_input->view, mag_text_input);
view_allocate_model(mag_text_input->view, ViewModelTypeLocking, sizeof(Mag_TextInputModel));
view_set_draw_callback(mag_text_input->view, mag_text_input_view_draw_callback);
view_set_input_callback(mag_text_input->view, mag_text_input_view_input_callback);
mag_text_input->timer =
furi_timer_alloc(mag_text_input_timer_callback, FuriTimerTypeOnce, mag_text_input);
/*with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{ model->validator_text = furi_string_alloc(); },
false);*/
mag_text_input_reset(mag_text_input);
return mag_text_input;
}
void mag_text_input_free(Mag_TextInput* mag_text_input) {
furi_assert(mag_text_input);
/*with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{ furi_string_free(model->validator_text); },
false);*/
// Send stop command
furi_timer_stop(mag_text_input->timer);
// Release allocated memory
furi_timer_free(mag_text_input->timer);
view_free(mag_text_input->view);
free(mag_text_input);
}
void mag_text_input_reset(Mag_TextInput* mag_text_input) {
furi_assert(mag_text_input);
with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{
model->text_buffer_size = 0;
model->header = "";
model->selected_row = 0;
model->selected_column = 0;
model->clear_default_text = false;
model->text_buffer = NULL;
model->text_buffer_size = 0;
model->callback = NULL;
model->callback_context = NULL;
/*model->validator_callback = NULL;
model->validator_callback_context = NULL;
furi_string_reset(model->validator_text);
model->validator_message_visible = false;*/
},
true);
}
View* mag_text_input_get_view(Mag_TextInput* mag_text_input) {
furi_assert(mag_text_input);
return mag_text_input->view;
}
void mag_text_input_set_result_callback(
Mag_TextInput* mag_text_input,
Mag_TextInputCallback callback,
void* callback_context,
char* text_buffer,
size_t text_buffer_size,
bool clear_default_text) {
with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{
model->callback = callback;
model->callback_context = callback_context;
model->text_buffer = text_buffer;
model->text_buffer_size = text_buffer_size;
model->clear_default_text = clear_default_text;
if(text_buffer && text_buffer[0] != '\0') {
// Set focus on Save
model->selected_row = 2;
model->selected_column = 8;
}
},
true);
}
/* void mag_text_input_set_validator(
Mag_TextInput* mag_text_input,
Mag_TextInputValidatorCallback callback,
void* callback_context) {
with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{
model->validator_callback = callback;
model->validator_callback_context = callback_context;
},
true);
}
Mag_TextInputValidatorCallback
mag_text_input_get_validator_callback(Mag_TextInput* mag_text_input) {
Mag_TextInputValidatorCallback validator_callback = NULL;
with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{ validator_callback = model->validator_callback; },
false);
return validator_callback;
}
void* mag_text_input_get_validator_callback_context(Mag_TextInput* mag_text_input) {
void* validator_callback_context = NULL;
with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{ validator_callback_context = model->validator_callback_context; },
false);
return validator_callback_context;
}*/
void mag_text_input_set_header_text(Mag_TextInput* mag_text_input, const char* text) {
with_view_model(
mag_text_input->view, Mag_TextInputModel * model, { model->header = text; }, true);
}
@@ -1,82 +0,0 @@
#pragma once
#include <gui/view.h>
// #include "mag_validators.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Text input anonymous structure */
typedef struct Mag_TextInput Mag_TextInput;
typedef void (*Mag_TextInputCallback)(void* context);
// typedef bool (*Mag_TextInputValidatorCallback)(const char* text, FuriString* error, void* context);
/** Allocate and initialize text input
*
* This text input is used to enter string
*
* @return Mag_TextInput instance
*/
Mag_TextInput* mag_text_input_alloc();
/** Deinitialize and free text input
*
* @param mag_text_input Mag_TextInput instance
*/
void mag_text_input_free(Mag_TextInput* mag_text_input);
/** Clean text input view Note: this function does not free memory
*
* @param mag_text_input Text input instance
*/
void mag_text_input_reset(Mag_TextInput* mag_text_input);
/** Get text input view
*
* @param mag_text_input Mag_TextInput instance
*
* @return View instance that can be used for embedding
*/
View* mag_text_input_get_view(Mag_TextInput* mag_text_input);
/** Set text input result callback
*
* @param mag_text_input Mag_TextInput instance
* @param callback callback fn
* @param callback_context callback context
* @param text_buffer pointer to YOUR text buffer, that we going
* to modify
* @param text_buffer_size YOUR text buffer size in bytes. Max string
* length will be text_buffer_size-1.
* @param clear_default_text clear text from text_buffer on first OK
* event
*/
void mag_text_input_set_result_callback(
Mag_TextInput* mag_text_input,
Mag_TextInputCallback callback,
void* callback_context,
char* text_buffer,
size_t text_buffer_size,
bool clear_default_text);
/* void mag_text_input_set_validator(
Mag_TextInput* mag_text_input,
Mag_TextInputValidatorCallback callback,
void* callback_context);
Mag_TextInputValidatorCallback
mag_text_input_get_validator_callback(Mag_TextInput* mag_text_input);
void* mag_text_input_get_validator_callback_context(Mag_TextInput* mag_text_input); */
/** Set text input header text
*
* @param mag_text_input Mag_TextInput instance
* @param text text to be shown
*/
void mag_text_input_set_header_text(Mag_TextInput* mag_text_input, const char* text);
#ifdef __cplusplus
}
#endif
@@ -1,67 +0,0 @@
#pragma once
#define MAG_VERSION_APP FAP_VERSION
#define MAG_DEVELOPER "Zachary Weiss"
#define MAG_GITHUB "github.com/zacharyweiss/magspoof_flipper"
typedef enum {
MagReverseStateOff,
MagReverseStateOn,
} MagReverseState;
typedef enum {
MagTrackStateOneAndTwo,
MagTrackStateOne,
MagTrackStateTwo,
MagTrackStateThree,
} MagTrackState;
typedef enum {
MagTxStateRFID,
MagTxStateGPIO,
MagTxStatePiezo,
MagTxStateLF_P, // combo of RFID and Piezo
MagTxStateNFC,
MagTxCC1101_434,
MagTxCC1101_868,
} MagTxState;
typedef enum {
MagPinA7,
MagPinA6,
MagPinA4,
MagPinB3,
MagPinB2,
MagPinC3,
MagPinC1,
MagPinC0,
} MagPin;
#define MAG_STATE_DEFAULT_REVERSE MagReverseStateOff
#define MAG_STATE_DEFAULT_TRACK MagTrackStateOneAndTwo
#define MAG_STATE_DEFAULT_TX MagTxStateGPIO
#define MAG_STATE_DEFAULT_US_CLOCK 240
#define MAG_STATE_DEFAULT_US_INTERPACKET 10
#define MAG_STATE_DEFAULT_PIN_INPUT MagPinA7
#define MAG_STATE_DEFAULT_PIN_OUTPUT MagPinA6
#define MAG_STATE_DEFAULT_PIN_ENABLE MagPinA4
#define MAG_STATE_DEFAULT_ALLOW_UART false
#define MAG_STATE_DEFAULT_N_REPEATS 3
#define MAG_STATE_DEFAULT_REPEAT_MODE true
typedef enum {
MagViewSubmenu,
MagViewDialogEx,
MagViewPopup,
MagViewLoading,
MagViewWidget,
MagViewVariableItemList,
MagViewTextInput,
MagViewTextMagInput,
} MagView;
typedef enum {
UART_TerminalEventRefreshConsoleOutput = 0,
UART_TerminalEventStartConsole,
UART_TerminalEventStartKeyboard,
} UART_TerminalCustomEvent;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

-268
View File
@@ -1,268 +0,0 @@
#include "mag_i.h"
#define TAG "Mag"
static bool mag_debug_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
Mag* mag = context;
return scene_manager_handle_custom_event(mag->scene_manager, event);
}
static bool mag_debug_back_event_callback(void* context) {
furi_assert(context);
Mag* mag = context;
return scene_manager_handle_back_event(mag->scene_manager);
}
static Mag* mag_alloc() {
Mag* mag = malloc(sizeof(Mag));
mag->storage = furi_record_open(RECORD_STORAGE);
mag->dialogs = furi_record_open(RECORD_DIALOGS);
mag->file_name = furi_string_alloc();
mag->file_path = furi_string_alloc_set(MAG_APP_FOLDER);
mag->args = furi_string_alloc();
mag->view_dispatcher = view_dispatcher_alloc();
mag->scene_manager = scene_manager_alloc(&mag_scene_handlers, mag);
view_dispatcher_set_event_callback_context(mag->view_dispatcher, mag);
view_dispatcher_set_custom_event_callback(
mag->view_dispatcher, mag_debug_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
mag->view_dispatcher, mag_debug_back_event_callback);
mag->mag_dev = mag_device_alloc();
mag_state_load(&mag->state);
// Open GUI record
mag->gui = furi_record_open(RECORD_GUI);
// Open Notification record
mag->notifications = furi_record_open(RECORD_NOTIFICATION);
// Submenu
mag->submenu = submenu_alloc();
view_dispatcher_add_view(mag->view_dispatcher, MagViewSubmenu, submenu_get_view(mag->submenu));
// Popup
mag->popup = popup_alloc();
view_dispatcher_add_view(mag->view_dispatcher, MagViewPopup, popup_get_view(mag->popup));
// Loading
mag->loading = loading_alloc();
view_dispatcher_add_view(mag->view_dispatcher, MagViewLoading, loading_get_view(mag->loading));
// Widget
mag->widget = widget_alloc();
view_dispatcher_add_view(mag->view_dispatcher, MagViewWidget, widget_get_view(mag->widget));
// Variable Item List
mag->variable_item_list = variable_item_list_alloc();
view_dispatcher_add_view(
mag->view_dispatcher,
MagViewVariableItemList,
variable_item_list_get_view(mag->variable_item_list));
// Text Input
mag->text_input = text_input_alloc();
view_dispatcher_add_view(
mag->view_dispatcher, MagViewTextInput, text_input_get_view(mag->text_input));
// Custom Mag Text Input
mag->mag_text_input = mag_text_input_alloc();
view_dispatcher_add_view(
mag->view_dispatcher, MagViewTextMagInput, mag_text_input_get_view(mag->mag_text_input));
// Disable expansion protocol to avoid interference with UART Handle
mag->expansion = furi_record_open(RECORD_EXPANSION);
expansion_disable(mag->expansion);
// Move UART here? conditional upon setting?
return mag;
}
static void mag_free(Mag* mag) {
furi_assert(mag);
furi_string_free(mag->file_name);
furi_string_free(mag->file_path);
furi_string_free(mag->args);
// Mag device
mag_device_free(mag->mag_dev);
mag->mag_dev = NULL;
// Submenu
view_dispatcher_remove_view(mag->view_dispatcher, MagViewSubmenu);
submenu_free(mag->submenu);
// Popup
view_dispatcher_remove_view(mag->view_dispatcher, MagViewPopup);
popup_free(mag->popup);
// Loading
view_dispatcher_remove_view(mag->view_dispatcher, MagViewLoading);
loading_free(mag->loading);
// Widget
view_dispatcher_remove_view(mag->view_dispatcher, MagViewWidget);
widget_free(mag->widget);
// Variable Item List
view_dispatcher_remove_view(mag->view_dispatcher, MagViewVariableItemList);
variable_item_list_free(mag->variable_item_list);
// TextInput
view_dispatcher_remove_view(mag->view_dispatcher, MagViewTextInput);
text_input_free(mag->text_input);
view_dispatcher_remove_view(mag->view_dispatcher, MagViewTextMagInput);
mag_text_input_free(mag->mag_text_input);
// View Dispatcher
view_dispatcher_free(mag->view_dispatcher);
// Scene Manager
scene_manager_free(mag->scene_manager);
// GUI
furi_record_close(RECORD_GUI);
mag->gui = NULL;
// Notifications
furi_record_close(RECORD_NOTIFICATION);
mag->notifications = NULL;
// Return previous state of expansion
expansion_enable(mag->expansion);
furi_record_close(RECORD_EXPANSION);
furi_record_close(RECORD_STORAGE);
furi_record_close(RECORD_DIALOGS);
free(mag);
}
// entry point for app
int32_t mag_app(void* p) {
const char* args = p;
Mag* mag = mag_alloc();
if(args && strlen(args)) {
furi_string_set(mag->args, args);
}
mag_make_app_folder(mag);
mag_migrate_and_copy_files(mag);
// Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering
uint8_t attempts = 0;
bool otg_was_enabled = furi_hal_power_is_otg_enabled();
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
furi_hal_power_enable_otg();
furi_delay_ms(10);
}
view_dispatcher_attach_to_gui(mag->view_dispatcher, mag->gui, ViewDispatcherTypeFullscreen);
if(furi_string_empty(mag->args)) {
scene_manager_next_scene(mag->scene_manager, MagSceneStart);
} else {
mag_device_load_data(mag->mag_dev, mag->args, true);
MagTrackState auto_track = mag_device_autoselect_track_state(mag->mag_dev);
if(auto_track) {
mag->state.track = auto_track;
}
scene_manager_next_scene(mag->scene_manager, MagSceneEmulate);
}
view_dispatcher_run(mag->view_dispatcher);
// Disable 5v power
if(furi_hal_power_is_otg_enabled() && !otg_was_enabled) {
furi_hal_power_disable_otg();
}
mag_free(mag);
return 0;
}
void mag_make_app_folder(Mag* mag) {
furi_assert(mag);
if(!storage_simply_mkdir(mag->storage, MAG_APP_FOLDER)) {
dialog_message_show_storage_error(mag->dialogs, "Cannot create\napp folder");
}
}
void mag_migrate_and_copy_files(Mag* mag) {
furi_assert(mag);
Storage* storage = mag->storage;
storage_common_migrate(storage, EXT_PATH("magspoof"), STORAGE_APP_DATA_PATH_PREFIX);
storage_common_migrate(storage, EXT_PATH("mag"), STORAGE_APP_DATA_PATH_PREFIX);
if(!storage_common_exists(storage, APP_DATA_PATH(MAG_EXAMPLE_FILE_1))) {
storage_common_copy(
storage, APP_ASSETS_PATH(MAG_EXAMPLE_FILE_1), APP_DATA_PATH(MAG_EXAMPLE_FILE_1));
}
if(!storage_common_exists(storage, APP_DATA_PATH(MAG_EXAMPLE_FILE_2))) {
storage_common_copy(
storage, APP_ASSETS_PATH(MAG_EXAMPLE_FILE_2), APP_DATA_PATH(MAG_EXAMPLE_FILE_2));
}
if(!storage_common_exists(storage, APP_DATA_PATH(MAG_EXAMPLE_FILE_3))) {
storage_common_copy(
storage, APP_ASSETS_PATH(MAG_EXAMPLE_FILE_3), APP_DATA_PATH(MAG_EXAMPLE_FILE_3));
}
}
void mag_text_store_set(Mag* mag, const char* text, ...) {
furi_assert(mag);
va_list args;
va_start(args, text);
vsnprintf(mag->text_store, MAG_TEXT_STORE_SIZE, text, args);
va_end(args);
}
void mag_text_store_clear(Mag* mag) {
furi_assert(mag);
memset(mag->text_store, 0, sizeof(mag->text_store));
}
void mag_popup_timeout_callback(void* context) {
Mag* mag = context;
view_dispatcher_send_custom_event(mag->view_dispatcher, MagEventPopupClosed);
}
void mag_widget_callback(GuiButtonType result, InputType type, void* context) {
Mag* mag = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(mag->view_dispatcher, result);
}
}
void mag_text_input_callback(void* context) {
Mag* mag = context;
view_dispatcher_send_custom_event(mag->view_dispatcher, MagEventNext);
}
void mag_show_loading_popup(void* context, bool show) {
Mag* mag = context;
if(show) {
// Raise timer priority so that animations can play
furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewLoading);
} else {
// Restore default timer priority
furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
}
}
@@ -1,333 +0,0 @@
#include "mag_device.h"
#include <toolbox/path.h>
#include <flipper_format/flipper_format.h>
#define TAG "MagDevice"
static const char* mag_file_header = "Flipper Mag device";
static const uint32_t mag_file_version = 1;
MagDevice* mag_device_alloc() {
MagDevice* mag_dev = malloc(sizeof(MagDevice));
mag_dev->dev_data.track[0].str = furi_string_alloc();
mag_dev->dev_data.track[1].str = furi_string_alloc();
mag_dev->dev_data.track[2].str = furi_string_alloc();
mag_dev->storage = furi_record_open(RECORD_STORAGE);
mag_dev->dialogs = furi_record_open(RECORD_DIALOGS);
mag_dev->load_path = furi_string_alloc();
return mag_dev;
}
void mag_device_data_clear(MagDeviceData* dev_data) {
furi_string_reset(dev_data->track[0].str);
furi_string_reset(dev_data->track[1].str);
furi_string_reset(dev_data->track[2].str);
}
void mag_device_clear(MagDevice* mag_dev) {
furi_assert(mag_dev);
mag_device_data_clear(&mag_dev->dev_data);
memset(&mag_dev->dev_data, 0, sizeof(mag_dev->dev_data));
furi_string_reset(mag_dev->load_path);
}
void mag_device_free(MagDevice* mag_dev) {
furi_assert(mag_dev);
mag_device_clear(mag_dev);
furi_record_close(RECORD_STORAGE);
furi_record_close(RECORD_DIALOGS);
furi_string_free(mag_dev->load_path);
//furi_string_free(mag_dev->dev_data.track[0].str);
//furi_string_free(mag_dev->dev_data.track[1].str);
//furi_string_free(mag_dev->dev_data.track[2].str);
free(mag_dev);
}
void mag_device_set_name(MagDevice* mag_dev, const char* name) {
furi_assert(mag_dev);
strlcpy(mag_dev->dev_name, name, MAG_DEV_NAME_MAX_LEN);
}
static bool mag_device_save_file(
MagDevice* mag_dev,
const char* dev_name,
const char* folder,
const char* extension,
bool use_load_path) {
furi_assert(mag_dev);
bool saved = false;
FlipperFormat* file = flipper_format_file_alloc(mag_dev->storage);
FuriString* temp_str;
temp_str = furi_string_alloc();
do {
if(use_load_path && !furi_string_empty(mag_dev->load_path)) {
// Get dir name
path_extract_dirname(furi_string_get_cstr(mag_dev->load_path), temp_str);
// Create mag directory if necessary
if(!storage_simply_mkdir((mag_dev->storage), furi_string_get_cstr(temp_str))) break;
// Make path to file to be saved
furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension);
} else {
// Create mag directory if necessary
if(!storage_simply_mkdir((mag_dev->storage), MAG_APP_FOLDER)) break;
// First remove mag device file if it was saved
furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
}
// Open file
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
// Write header
if(!flipper_format_write_header_cstr(file, mag_file_header, mag_file_version)) break;
// Write comment
if(!flipper_format_write_comment_cstr(file, "Mag device track data")) break;
// Write data
for(uint8_t i = 0; i < MAG_DEV_TRACKS; i++) {
furi_string_printf(temp_str, "Track %d", i + 1);
if(!flipper_format_write_string_cstr(
file,
furi_string_get_cstr(temp_str),
furi_string_get_cstr(mag_dev->dev_data.track[i].str)))
break;
}
saved = true;
} while(0);
if(!saved) {
dialog_message_show_storage_error(mag_dev->dialogs, "Cannot save\nfile");
}
furi_string_free(temp_str);
flipper_format_free(file);
return saved;
}
bool mag_device_save(MagDevice* mag_dev, const char* dev_name) {
// wrapping function in the event we have multiple formats
return mag_device_save_file(mag_dev, dev_name, MAG_APP_FOLDER, MAG_APP_EXTENSION, true);
}
bool mag_device_load_data(MagDevice* mag_dev, FuriString* path, bool show_dialog) {
bool parsed = false;
FuriString* filename;
filename = furi_string_alloc();
path_extract_filename(path, filename, true);
strncpy(mag_dev->dev_name, furi_string_get_cstr(filename), MAG_DEV_NAME_MAX_LEN);
FlipperFormat* file = flipper_format_file_alloc(mag_dev->storage);
FuriString* temp_str;
temp_str = furi_string_alloc();
bool deprecated_version = false;
bool data_read = true;
if(mag_dev->loading_cb) {
mag_dev->loading_cb(mag_dev->loading_cb_ctx, true);
}
do {
if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break;
// Read and verify header, check file version
uint32_t version;
if(!flipper_format_read_header(file, temp_str, &version)) break;
if(furi_string_cmp_str(temp_str, mag_file_header) || (version != mag_file_version)) {
deprecated_version = true;
break;
}
// Parse data
for(uint8_t i = 0; i < MAG_DEV_TRACKS; i++) {
furi_string_printf(temp_str, "Track %d", i + 1);
if(!flipper_format_read_string(
file, furi_string_get_cstr(temp_str), mag_dev->dev_data.track[i].str)) {
FURI_LOG_D(TAG, "Could not read track %d data", i + 1);
// TODO: smarter load handling now that it is acceptible for some tracks to be empty
data_read = false;
}
}
parsed = true;
} while(false);
if((!parsed) && (show_dialog)) {
if(deprecated_version) {
dialog_message_show_storage_error(mag_dev->dialogs, "File format\ndeprecated");
} else if(!data_read) {
dialog_message_show_storage_error(mag_dev->dialogs, "Cannot read\ndata");
} else {
dialog_message_show_storage_error(mag_dev->dialogs, "Cannot parse\nfile");
}
}
furi_string_free(temp_str);
furi_string_free(filename);
flipper_format_free(file);
return parsed;
}
bool mag_file_select(MagDevice* mag_dev) {
furi_assert(mag_dev);
// Input events and views are managed by file_browser
FuriString* mag_app_folder;
mag_app_folder = furi_string_alloc_set(MAG_APP_FOLDER);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, MAG_APP_EXTENSION, &I_mag_file_10px);
browser_options.base_path = MAG_APP_FOLDER;
bool res = dialog_file_browser_show(
mag_dev->dialogs, mag_dev->load_path, mag_app_folder, &browser_options);
furi_string_free(mag_app_folder);
if(res) {
res = mag_device_load_data(mag_dev, mag_dev->load_path, true);
if(res) {
mag_device_set_name(mag_dev, mag_dev->dev_name);
}
}
return res;
}
bool mag_device_delete(MagDevice* mag_dev, bool use_load_path) {
furi_assert(mag_dev);
bool deleted = false;
FuriString* file_path;
file_path = furi_string_alloc();
do {
// Delete original file
if(use_load_path && !furi_string_empty(mag_dev->load_path)) {
furi_string_set(file_path, mag_dev->load_path);
} else {
furi_string_printf(
file_path, "%s/%s%s", MAG_APP_FOLDER, mag_dev->dev_name, MAG_APP_EXTENSION);
}
if(!storage_simply_remove(mag_dev->storage, furi_string_get_cstr(file_path))) break;
deleted = true;
} while(false);
if(!deleted) {
dialog_message_show_storage_error(mag_dev->dialogs, "Cannot remove\nfile");
}
furi_string_free(file_path);
return deleted;
}
bool mag_device_parse_card_string(MagDevice* mag_dev, FuriString* f_card_str) {
furi_assert(mag_dev);
FURI_LOG_D(TAG, "mag_device_parse_card_string");
const char* card_str = furi_string_get_cstr(f_card_str);
FURI_LOG_D(TAG, "Parsing card string: %s", card_str);
// Track 1
const char* track1_start = strchr(card_str, '%');
if(!track1_start) {
FURI_LOG_D(TAG, "Could not find track 1 start");
return false;
}
track1_start++;
const char* track1_end = strchr(track1_start, '?');
if(!track1_end) {
FURI_LOG_D(TAG, "Could not find track 1 end");
return false;
}
size_t track1_len = track1_end - track1_start;
FURI_LOG_D(TAG, "Track 1: %.*s", track1_len, track1_start);
mag_dev->dev_data.track[0].len = track1_len;
furi_string_printf(mag_dev->dev_data.track[0].str, "%%%.*s?", track1_len, track1_start);
// Track 2
const char* track2_start = strchr(track1_end, ';');
if(!track2_start) {
FURI_LOG_D(TAG, "Could not find track 2 start");
return true;
}
track2_start++;
const char* track2_end = strchr(track2_start, '?');
if(!track2_end) {
FURI_LOG_D(TAG, "Could not find track 2 end");
return true;
}
size_t track2_len = track2_end - track2_start;
FURI_LOG_D(TAG, "Track 2: %.*s", track2_len, track2_start);
mag_dev->dev_data.track[1].len = track2_len;
furi_string_printf(mag_dev->dev_data.track[1].str, "%%%.*s?", track2_len, track2_start);
// Track 3
const char* track3_start = strchr(track2_end, ';');
if(!track3_start) {
FURI_LOG_D(TAG, "Could not find track 3 start");
return true;
}
track3_start++;
const char* track3_end = strchr(track3_start, '?');
if(!track3_end) {
FURI_LOG_D(TAG, "Could not find track 3 end");
return true;
}
size_t track3_len = track3_end - track3_start;
FURI_LOG_D(TAG, "Track 3: %.*s", track3_len, track3_start);
mag_dev->dev_data.track[2].len = track3_len;
furi_string_printf(mag_dev->dev_data.track[2].str, "%%%.*s?", track3_len, track3_start);
return true;
}
MagTrackState mag_device_autoselect_track_state(MagDevice* mag_dev) {
// messy code to quickly check which tracks are available for emulation/display
bool is_empty_t1 = furi_string_empty(mag_dev->dev_data.track[0].str);
bool is_empty_t2 = furi_string_empty(mag_dev->dev_data.track[1].str);
bool is_empty_t3 = furi_string_empty(mag_dev->dev_data.track[2].str);
if(!is_empty_t1 && !is_empty_t2) {
return MagTrackStateOneAndTwo;
} else if(!is_empty_t1) {
return MagTrackStateOne;
} else if(!is_empty_t2) {
return MagTrackStateTwo;
} else if(!is_empty_t3) {
return MagTrackStateThree;
}
// if all empty (or something wrong with the above code)
// return default value
return MAG_STATE_DEFAULT_TRACK;
}
void mag_device_set_loading_callback(
MagDevice* mag_dev,
MagLoadingCallback callback,
void* context) {
furi_assert(mag_dev);
mag_dev->loading_cb = callback;
mag_dev->loading_cb_ctx = context;
}
@@ -1,67 +0,0 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include "mag_icons.h"
#include "helpers/mag_types.h"
#define MAG_DEV_NAME_MAX_LEN 22
#define MAG_DEV_TRACKS 3
#define MAG_APP_FOLDER STORAGE_APP_DATA_PATH_PREFIX
#define MAG_APP_EXTENSION ".mag"
#define MAG_EXAMPLE_FILE_1 "TestMagstripe.mag"
#define MAG_EXAMPLE_FILE_2 "SamyExpiredCard.mag"
#define MAG_EXAMPLE_FILE_3 "SamyExampleImage.mag"
typedef void (*MagLoadingCallback)(void* context, bool state);
typedef struct {
FuriString* str;
size_t len;
} MagTrack;
typedef struct {
MagTrack track[MAG_DEV_TRACKS];
} MagDeviceData;
typedef struct {
Storage* storage;
DialogsApp* dialogs;
MagDeviceData dev_data;
char dev_name[MAG_DEV_NAME_MAX_LEN + 1];
FuriString* load_path;
MagLoadingCallback loading_cb;
void* loading_cb_ctx;
} MagDevice;
MagDevice* mag_device_alloc();
void mag_device_free(MagDevice* mag_dev);
void mag_device_set_name(MagDevice* mag_dev, const char* name);
bool mag_device_save(MagDevice* mag_dev, const char* dev_name);
bool mag_device_load_data(MagDevice* mag_dev, FuriString* path, bool show_dialog);
bool mag_file_select(MagDevice* mag_dev);
void mag_device_data_clear(MagDeviceData* dev_data);
void mag_device_clear(MagDevice* mag_dev);
bool mag_device_delete(MagDevice* mag_dev, bool use_load_path);
bool mag_device_parse_card_string(MagDevice* mag_dev, FuriString* card_str);
MagTrackState mag_device_autoselect_track_state(MagDevice* mag_dev);
void mag_device_set_loading_callback(
MagDevice* mag_dev,
MagLoadingCallback callback,
void* context);
@@ -1,112 +0,0 @@
#pragma once
#include "mag_device.h"
#include "mag_state.h"
//#include "helpers/mag_helpers.h"
#include "helpers/mag_text_input.h"
#include "helpers/mag_types.h"
#include <furi.h>
#include <furi_hal.h>
#include <furi/core/log.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <expansion/expansion.h>
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <notification/notification_messages.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include <gui/modules/popup.h>
#include <gui/modules/loading.h>
#include <gui/modules/text_input.h>
#include <gui/modules/widget.h>
#include <gui/modules/variable_item_list.h>
#include <dialogs/dialogs.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#include <toolbox/path.h>
#include <toolbox/value_index.h>
#include "scenes/mag_scene.h"
#include "scenes/mag_scene_read.h"
#define MAG_TEXT_STORE_SIZE 150
// CFWs have `submenue_add_lockable_item`; OFW doesn't,
// replace with conditional submenu item
#ifdef FW_ORIGIN_Official
#define submenu_add_lockable_item( \
submenu, label, index, callback, callback_context, locked, locked_message) \
if(!locked) { \
submenu_add_item(submenu, label, index, callback, callback_context) \
}
#endif
enum MagCustomEvent {
MagEventNext = 100,
MagEventExit,
MagEventPopupClosed,
MagEventConfirmDialog,
};
typedef struct {
ViewDispatcher* view_dispatcher;
Gui* gui;
NotificationApp* notifications;
SceneManager* scene_manager;
Storage* storage;
DialogsApp* dialogs;
MagDevice* mag_dev;
char text_store[MAG_TEXT_STORE_SIZE + 1];
FuriString* file_path;
FuriString* file_name;
FuriString* args;
MagState state;
// Common views
Submenu* submenu;
Popup* popup;
Loading* loading;
TextInput* text_input;
Widget* widget;
VariableItemList* variable_item_list;
// Custom views
Mag_TextInput* mag_text_input;
// UART
Expansion* expansion;
FuriThread* uart_rx_thread;
FuriStreamBuffer* uart_rx_stream;
uint8_t uart_rx_buf[UART_RX_BUF_SIZE + 1];
void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context);
FuriHalSerialHandle* serial_handle;
char uart_text_input_store[UART_TERMINAL_TEXT_INPUT_STORE_SIZE + 1];
FuriString* uart_text_box_store;
size_t uart_text_box_store_strlen;
} Mag;
void mag_text_store_set(Mag* mag, const char* text, ...);
void mag_text_store_clear(Mag* mag);
void mag_show_loading_popup(void* context, bool show);
void mag_make_app_folder(Mag* mag);
void mag_migrate_and_copy_files(Mag* mag);
void mag_popup_timeout_callback(void* context);
void mag_widget_callback(GuiButtonType result, InputType type, void* context);
void mag_text_input_callback(void* context);
@@ -1,147 +0,0 @@
#include "mag_state.h"
#define TAG "MagState"
const GpioPin* mag_state_enum_to_pin(MagPin pin) {
switch(pin) {
case MagPinA7:
return &gpio_ext_pa7;
case MagPinA6:
return &gpio_ext_pa6;
case MagPinA4:
return &gpio_ext_pa4;
case MagPinB3:
return &gpio_ext_pb3;
case MagPinB2:
return &gpio_ext_pb2;
case MagPinC3:
return &gpio_ext_pc3;
case MagPinC1:
return &gpio_ext_pc1;
case MagPinC0:
return &gpio_ext_pc0;
default:
return NULL;
}
}
bool mag_state_gpio_is_valid(MagState* state) {
return (state->pin_input != state->pin_output) && (state->pin_input != state->pin_enable) &&
(state->pin_enable != state->pin_output);
}
void mag_state_gpio_reset(MagState* state) {
state->pin_input = MAG_STATE_DEFAULT_PIN_INPUT;
state->pin_output = MAG_STATE_DEFAULT_PIN_OUTPUT;
state->pin_enable = MAG_STATE_DEFAULT_PIN_ENABLE;
}
bool mag_state_load(MagState* out_state) {
MagState state;
// Try to load from file
bool loaded_from_file = false;
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_file_exists(storage, MAG_STATE_PATH)) {
FlipperFormat* file = flipper_format_file_alloc(storage);
do {
uint32_t tmp;
FuriString* str = furi_string_alloc();
if(!flipper_format_file_open_existing(file, MAG_STATE_PATH)) break;
if(!flipper_format_read_header(file, str, &tmp)) break;
if(furi_string_cmp_str(str, MAG_STATE_HEADER)) break;
// if(tmp != MAG_STATE_VER) break;
if(!flipper_format_read_uint32(file, "pin_input", &tmp, 1)) {
flipper_format_rewind(file);
tmp = MAG_STATE_DEFAULT_PIN_INPUT;
}
state.pin_input = (MagPin)tmp;
if(!flipper_format_read_uint32(file, "pin_output", &tmp, 1)) {
flipper_format_rewind(file);
tmp = MAG_STATE_DEFAULT_PIN_OUTPUT;
}
state.pin_output = (MagPin)tmp;
if(!flipper_format_read_uint32(file, "pin_enable", &tmp, 1)) {
flipper_format_rewind(file);
tmp = MAG_STATE_DEFAULT_PIN_ENABLE;
}
state.pin_enable = (MagPin)tmp;
if(!flipper_format_read_bool(file, "allow_uart", &state.allow_uart, 1)) {
flipper_format_rewind(file);
state.allow_uart = MAG_STATE_DEFAULT_ALLOW_UART;
}
if(!flipper_format_read_uint32(file, "n_repeats", &tmp, 1)) {
flipper_format_rewind(file);
tmp = MAG_STATE_DEFAULT_N_REPEATS;
}
state.n_repeats = (uint8_t)tmp;
if(!flipper_format_read_bool(file, "repeat_mode", &state.repeat_mode, 1)) {
flipper_format_rewind(file);
state.repeat_mode = MAG_STATE_DEFAULT_REPEAT_MODE;
}
loaded_from_file = true;
} while(0);
flipper_format_free(file);
}
furi_record_close(RECORD_STORAGE);
// If file's GPIO config is invalid (pins overlap)
// Reset to defaults
// Additionally raise message to user?
if(!mag_state_gpio_is_valid(&state)) {
mag_state_gpio_reset(&state);
}
if(!loaded_from_file) {
mag_state_gpio_reset(&state);
state.allow_uart = MAG_STATE_DEFAULT_ALLOW_UART;
state.n_repeats = MAG_STATE_DEFAULT_N_REPEATS;
state.repeat_mode = MAG_STATE_DEFAULT_REPEAT_MODE;
}
// set defaults we don't save
state.tx = MAG_STATE_DEFAULT_TX;
state.track = MAG_STATE_DEFAULT_TRACK;
state.reverse = MAG_STATE_DEFAULT_REVERSE;
state.us_clock = MAG_STATE_DEFAULT_US_CLOCK;
state.us_interpacket = MAG_STATE_DEFAULT_US_INTERPACKET;
state.is_debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug);
// Copy to caller state before popping stack
memcpy(out_state, &state, sizeof(state));
return loaded_from_file;
}
void mag_state_save(MagState* state) {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, MAG_STATE_DIR);
FlipperFormat* file = flipper_format_file_alloc(storage);
do {
uint32_t tmp;
if(!flipper_format_file_open_always(file, MAG_STATE_PATH)) break;
if(!flipper_format_write_header_cstr(file, MAG_STATE_HEADER, MAG_STATE_VER)) break;
tmp = (uint32_t)state->pin_input;
if(!flipper_format_write_uint32(file, "pin_input", &tmp, 1)) break;
tmp = (uint32_t)state->pin_output;
if(!flipper_format_write_uint32(file, "pin_output", &tmp, 1)) break;
tmp = (uint32_t)state->pin_enable;
if(!flipper_format_write_uint32(file, "pin_enable", &tmp, 1)) break;
if(!flipper_format_write_bool(file, "allow_uart", &state->allow_uart, 1)) break;
tmp = (uint32_t)state->n_repeats;
if(!flipper_format_write_uint32(file, "n_repeats", &tmp, 1)) break;
if(!flipper_format_write_bool(file, "repeat_mode", &state->repeat_mode, 1)) break;
} while(0);
flipper_format_free(file);
furi_record_close(RECORD_STORAGE);
}
@@ -1,44 +0,0 @@
#pragma once
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <furi_hal_resources.h>
#include <furi_hal_rtc.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#include "helpers/mag_types.h"
#define MAG_STATE_HEADER "Mag State"
#define MAG_STATE_VER 2
#define MAG_STATE_DIR STORAGE_APP_DATA_PATH_PREFIX
#define MAG_STATE_PATH MAG_STATE_DIR "/mag_state.txt"
typedef struct {
MagTxState tx;
MagTrackState track;
MagReverseState reverse;
uint32_t us_clock;
uint32_t us_interpacket;
MagPin pin_input;
MagPin pin_output;
MagPin pin_enable;
bool allow_uart;
bool is_debug;
uint8_t n_repeats;
bool repeat_mode;
} MagState;
const GpioPin* mag_state_enum_to_pin(MagPin pin);
bool mag_state_gpio_is_valid(MagState* state);
void mag_state_gpio_reset(MagState* state);
bool mag_state_load(MagState* out_state);
void mag_state_save(MagState* state);
@@ -1,7 +0,0 @@
Filetype: Flipper Mag device
Version: 1
# Mag device track data
# Data matching image example in Samy's repo, for ease of comparison
Track 1: %B426684131234567^LASTNAME/FIRST^YYMMSSSDDDDDDDDDDDDDDDDDDDDDDDDD?
Track 2: ;426684131234567=230188855555555555555?
Track 3:
@@ -1,7 +0,0 @@
Filetype: Flipper Mag device
Version: 1
# Mag device track data
# Found in samyk's MagSpoof branch f150bb783237051fba7e4e6ed96a722e542a9663; using as test data, card is long expired
Track 1: %B493173000682759^URISTA HDZ-IVAN JAVIER ^150220100234000000?
Track 2: ;493173000682759=15022100000234?
Track 3:
@@ -1,6 +0,0 @@
Filetype: Flipper Mag device
Version: 1
# Mag device track data
Track 1: %B123456781234567^LASTNAME/FIRST^YYMMSSSDDDDDDDDDDDDDDDDDDDDDDDDD?
Track 2: ;123456781234567=YYMMSSSDDDDDDDDDDDDDD?
Track 3:
@@ -1,30 +0,0 @@
#include "mag_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const mag_on_enter_handlers[])(void*) = {
#include "mag_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 mag_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "mag_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 mag_on_exit_handlers[])(void* context) = {
#include "mag_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers mag_scene_handlers = {
.on_enter_handlers = mag_on_enter_handlers,
.on_event_handlers = mag_on_event_handlers,
.on_exit_handlers = mag_on_exit_handlers,
.scene_num = MagSceneNum,
};
@@ -1,29 +0,0 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) MagScene##id,
typedef enum {
#include "mag_scene_config.h"
MagSceneNum,
} MagScene;
#undef ADD_SCENE
extern const SceneManagerHandlers mag_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "mag_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 "mag_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 "mag_scene_config.h"
#undef ADD_SCENE
@@ -1,40 +0,0 @@
#include "../mag_i.h"
void mag_scene_about_on_enter(void* context) {
Mag* mag = context;
Widget* widget = mag->widget;
FuriString* tmp_str;
tmp_str = furi_string_alloc();
furi_string_cat_printf(tmp_str, "Version: %s\n", MAG_VERSION_APP);
furi_string_cat_printf(tmp_str, "Developer: %s\n", MAG_DEVELOPER);
furi_string_cat_printf(tmp_str, "GitHub: %s\n\n", MAG_GITHUB);
furi_string_cat_printf(
tmp_str,
"Unfinished port of Samy Kamkar's MagSpoof. Confer GitHub for updates; in the interim, use responsibly and at your own risk.");
// TODO: Add credits
widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(tmp_str));
furi_string_free(tmp_str);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
}
bool mag_scene_about_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
UNUSED(event);
UNUSED(scene_manager);
return consumed;
}
void mag_scene_about_on_exit(void* context) {
Mag* mag = context;
widget_reset(mag->widget);
}
@@ -1,16 +0,0 @@
ADD_SCENE(mag, start, Start)
ADD_SCENE(mag, about, About)
ADD_SCENE(mag, read, Read)
ADD_SCENE(mag, settings, Settings)
ADD_SCENE(mag, emulate, Emulate)
ADD_SCENE(mag, emulate_config, EmulateConfig)
ADD_SCENE(mag, file_select, FileSelect)
ADD_SCENE(mag, saved_menu, SavedMenu)
ADD_SCENE(mag, saved_info, SavedInfo)
ADD_SCENE(mag, input_name, InputName)
ADD_SCENE(mag, input_value, InputValue)
ADD_SCENE(mag, save_success, SaveSuccess)
ADD_SCENE(mag, delete_success, DeleteSuccess)
ADD_SCENE(mag, delete_confirm, DeleteConfirm)
ADD_SCENE(mag, exit_confirm, ExitConfirm)
ADD_SCENE(mag, under_construction, UnderConstruction)
@@ -1,49 +0,0 @@
#include "../mag_i.h"
#include "../mag_device.h"
void mag_scene_delete_confirm_on_enter(void* context) {
Mag* mag = context;
Widget* widget = mag->widget;
MagDevice* mag_dev = mag->mag_dev;
FuriString* tmp_str;
tmp_str = furi_string_alloc();
furi_string_printf(tmp_str, "\e#Delete %s?\e#", mag_dev->dev_name);
//TODO: print concise summary of data on card? Would need to vary by card/track type
widget_add_text_box_element(
widget, 0, 0, 128, 27, AlignCenter, AlignCenter, furi_string_get_cstr(tmp_str), true);
widget_add_button_element(widget, GuiButtonTypeLeft, "Cancel", mag_widget_callback, mag);
widget_add_button_element(widget, GuiButtonTypeRight, "Delete", mag_widget_callback, mag);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
furi_string_free(tmp_str);
}
bool mag_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeRight) {
consumed = true;
if(mag_device_delete(mag->mag_dev, true)) {
scene_manager_next_scene(scene_manager, MagSceneDeleteSuccess);
}
} else if(event.event == GuiButtonTypeLeft) {
consumed = true;
scene_manager_previous_scene(scene_manager);
}
}
return consumed;
}
void mag_scene_delete_confirm_on_exit(void* context) {
Mag* mag = context;
widget_reset(mag->widget);
}
@@ -1,39 +0,0 @@
#include "../mag_i.h"
void mag_scene_delete_success_on_enter(void* context) {
Mag* mag = context;
Popup* popup = mag->popup;
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
popup_set_callback(popup, mag_popup_timeout_callback);
popup_set_context(popup, mag);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewPopup);
}
bool mag_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == MagEventPopupClosed) {
consumed = true;
scene_manager_search_and_switch_to_previous_scene(
mag->scene_manager, MagSceneFileSelect);
}
}
return consumed;
}
void mag_scene_delete_success_on_exit(void* context) {
Mag* mag = context;
Popup* popup = mag->popup;
popup_reset(popup);
}
@@ -1,93 +0,0 @@
#include "../mag_i.h"
#include "../helpers/mag_helpers.h"
#define TAG "MagSceneEmulate"
void cat_trackstr(FuriString* str, uint8_t calls, uint8_t i, FuriString* trackstr) {
furi_string_cat_printf(
str,
"%sTrack %d:%s%s\n",
(calls == 0) ? "" : "\n", // if first line, don't prepend a "\n"
(i + 1),
furi_string_empty(trackstr) ? " " : "\n",
furi_string_empty(trackstr) ? "< empty >" : furi_string_get_cstr(trackstr));
}
void mag_scene_emulate_on_enter(void* context) {
Mag* mag = context;
Widget* widget = mag->widget;
FuriString* tmp_str;
tmp_str = furi_string_alloc();
// Use strlcpy instead perhaps, to truncate to screen width, then add ellipses if needed?
furi_string_printf(tmp_str, "%s\r\n", mag->mag_dev->dev_name);
// TODO: Display other relevant config settings (namely RFID vs GPIO)?
widget_add_icon_element(widget, 1, 1, &I_mag_file_10px);
widget_add_string_element(
widget, 13, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp_str));
furi_string_reset(tmp_str);
FURI_LOG_D(TAG, "%d", mag->state.reverse);
// print relevant data
uint8_t cat_count = 0;
for(uint8_t i = 0; i < MAG_DEV_TRACKS; i++) {
FuriString* trackstr = mag->mag_dev->dev_data.track[i].str;
// still messy / dumb way to do this, but slightly cleaner than before.
// will clean up more later
switch(mag->state.track) {
case MagTrackStateOne:
if(i == 0) cat_trackstr(tmp_str, cat_count++, i, trackstr);
break;
case MagTrackStateTwo:
if(i == 1) cat_trackstr(tmp_str, cat_count++, i, trackstr);
break;
case MagTrackStateThree:
if(i == 2) cat_trackstr(tmp_str, cat_count++, i, trackstr);
break;
case MagTrackStateOneAndTwo:
if((i == 0) | (i == 1)) cat_trackstr(tmp_str, cat_count++, i, trackstr);
break;
}
}
widget_add_text_scroll_element(widget, 0, 15, 128, 49, furi_string_get_cstr(tmp_str));
widget_add_button_element(widget, GuiButtonTypeLeft, "Config", mag_widget_callback, mag);
widget_add_button_element(widget, GuiButtonTypeRight, "Send", mag_widget_callback, mag);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
furi_string_free(tmp_str);
}
bool mag_scene_emulate_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case GuiButtonTypeLeft:
consumed = true;
scene_manager_next_scene(scene_manager, MagSceneEmulateConfig);
break;
case GuiButtonTypeRight:
consumed = true;
notification_message(mag->notifications, &sequence_blink_start_cyan);
mag_spoof(mag);
notification_message(mag->notifications, &sequence_blink_stop);
break;
}
}
return consumed;
}
void mag_scene_emulate_on_exit(void* context) {
Mag* mag = context;
notification_message(mag->notifications, &sequence_blink_stop);
widget_reset(mag->widget);
}
@@ -1,286 +0,0 @@
#include "../mag_i.h"
#define TAG "MagSceneEmulateConfig"
enum MagEmulateConfigIndex {
MagEmulateConfigIndexClock,
MagEmulateConfigIndexTrack,
MagEmulateConfigIndexReverse,
MagEmulateConfigIndexRepeat,
MagEmulateConfigIndexTx,
// MagEmulateConfigIndexInterpacket,
};
#define TX_COUNT 7
const char* const tx_text[TX_COUNT] = {
"RFID",
"GPIO",
"Piezo",
"LF + P",
"NFC",
"434MHz",
"868MHz",
};
const uint32_t tx_value[TX_COUNT] = {
MagTxStateRFID,
MagTxStateGPIO,
MagTxStatePiezo,
MagTxStateLF_P,
MagTxStateNFC,
MagTxCC1101_434,
MagTxCC1101_868,
};
#define TRACK_COUNT 4
const char* const track_text[TRACK_COUNT] = {
"1 + 2",
"1",
"2",
"3",
};
const uint32_t track_value[TRACK_COUNT] = {
MagTrackStateOneAndTwo,
MagTrackStateOne,
MagTrackStateTwo,
MagTrackStateThree,
};
#define REVERSE_COUNT 2
const char* const reverse_text[REVERSE_COUNT] = {
"OFF",
"ON",
};
const uint32_t reverse_value[REVERSE_COUNT] = {
MagReverseStateOff,
MagReverseStateOn,
};
#define CLOCK_COUNT 15
const char* const clock_text[CLOCK_COUNT] = {
"200us",
"220us",
"240us",
"250us",
"260us",
"280us",
"300us",
"325us",
"350us",
"375us",
"400us",
"450us",
"500us",
"600us",
"700us",
};
const uint32_t clock_value[CLOCK_COUNT] = {
200,
220,
240,
250,
260,
280,
300,
325,
350,
375,
400,
450,
500,
600,
700,
};
#define INTERPACKET_COUNT 13
const char* const interpacket_text[INTERPACKET_COUNT] = {
"0us",
"2us",
"4us",
"6us",
"8us",
"10us",
"12us",
"14us",
"16us",
"18us",
"20us",
"25us",
"30us",
};
const uint32_t interpacket_value[INTERPACKET_COUNT] = {
0,
2,
4,
6,
8,
10,
12,
14,
16,
18,
20,
25,
30,
};
static void mag_scene_emulate_config_set_tx(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, tx_text[index]);
mag->state.tx = tx_value[index];
};
static void mag_scene_emulate_config_set_track(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
if(mag->state.reverse == MagReverseStateOff) {
variable_item_set_current_value_text(item, track_text[index]);
mag->state.track = track_value[index];
} else if(mag->state.reverse == MagReverseStateOn) {
variable_item_set_current_value_index(
item, value_index_uint32(MagTrackStateOneAndTwo, track_value, TRACK_COUNT));
}
// TODO: Check there is data in selected track?
// Only display track options with data?
};
static void mag_scene_emulate_config_set_reverse(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
if(mag->state.track == MagTrackStateOneAndTwo) {
// only allow reverse track to be set when playing both 1 and 2
variable_item_set_current_value_text(item, reverse_text[index]);
mag->state.reverse = reverse_value[index];
//FURI_LOG_D(TAG, "%s", reverse_text[index]);
//FURI_LOG_D(TAG, "%d", mag->setting->reverse);
} else {
variable_item_set_current_value_index(
item, value_index_uint32(MagReverseStateOff, reverse_value, REVERSE_COUNT));
}
};
static void mag_scene_emulate_config_set_repeat_mode(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, reverse_text[index]);
mag->state.repeat_mode = (bool)index;
}
static void mag_scene_emulate_config_set_clock(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, clock_text[index]);
mag->state.us_clock = clock_value[index];
};
static void mag_scene_emulate_config_set_interpacket(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, interpacket_text[index]);
mag->state.us_interpacket = interpacket_value[index];
};
void mag_scene_emulate_config_on_enter(void* context) {
Mag* mag = context;
VariableItem* item;
uint8_t value_index;
// Clock
item = variable_item_list_add(
mag->variable_item_list, "Clock:", CLOCK_COUNT, mag_scene_emulate_config_set_clock, mag);
value_index = value_index_uint32(mag->state.us_clock, clock_value, CLOCK_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, clock_text[value_index]);
// Track
item = variable_item_list_add(
mag->variable_item_list, "Track:", TRACK_COUNT, mag_scene_emulate_config_set_track, mag);
value_index = value_index_uint32(mag->state.track, track_value, TRACK_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, track_text[value_index]);
// Reverse
//FURI_LOG_D(TAG, "%d", mag->setting->reverse);
item = variable_item_list_add(
mag->variable_item_list,
"Reverse:",
REVERSE_COUNT,
mag_scene_emulate_config_set_reverse,
mag);
value_index = value_index_uint32(mag->state.reverse, reverse_value, REVERSE_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, reverse_text[value_index]);
// Repeated TX
item = variable_item_list_add(
mag->variable_item_list,
"Repeat:",
REVERSE_COUNT,
mag_scene_emulate_config_set_repeat_mode,
mag);
value_index = (uint32_t)mag->state.repeat_mode;
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, reverse_text[value_index]);
// TX
#ifdef FW_ORIGIN_Official
if(mag->state.is_debug) {
#endif
item = variable_item_list_add(
mag->variable_item_list, "TX via:", TX_COUNT, mag_scene_emulate_config_set_tx, mag);
value_index = value_index_uint32(mag->state.tx, tx_value, TX_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, tx_text[value_index]);
#ifdef FW_ORIGIN_Official
}
#else
variable_item_set_locked(item, !mag->state.is_debug, "Enable Debug!");
#endif
// Interpacket
/*
item = variable_item_list_add(
mag->variable_item_list,
"Interpacket:",
INTERPACKET_COUNT,
mag_scene_emulate_config_set_interpacket,
mag);
value_index =
value_index_uint32(mag->setting->us_interpacket, interpacket_value, INTERPACKET_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, interpacket_text[value_index]);*/
UNUSED(mag_scene_emulate_config_set_interpacket);
variable_item_list_set_selected_item(
mag->variable_item_list,
scene_manager_get_scene_state(mag->scene_manager, MagSceneEmulateConfig));
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewVariableItemList);
}
bool mag_scene_emulate_config_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
UNUSED(mag);
UNUSED(scene_manager);
UNUSED(event);
return consumed;
}
void mag_scene_emulate_config_on_exit(void* context) {
Mag* mag = context;
variable_item_list_set_selected_item(mag->variable_item_list, 0);
variable_item_list_reset(mag->variable_item_list);
}
@@ -1,20 +0,0 @@
#include "../mag_i.h"
void mag_scene_exit_confirm_on_enter(void* context) {
Mag* mag = context;
UNUSED(mag);
}
bool mag_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
UNUSED(mag);
UNUSED(event);
bool consumed = false;
return consumed;
}
void mag_scene_exit_confirm_on_exit(void* context) {
Mag* mag = context;
UNUSED(mag);
}
@@ -1,27 +0,0 @@
#include "../mag_i.h"
#include "../mag_device.h"
void mag_scene_file_select_on_enter(void* context) {
Mag* mag = context;
mag_device_set_loading_callback(mag->mag_dev, mag_show_loading_popup, mag);
if(mag_file_select(mag->mag_dev)) {
MagTrackState auto_track = mag_device_autoselect_track_state(mag->mag_dev);
if(auto_track) {
mag->state.track = auto_track;
}
scene_manager_next_scene(mag->scene_manager, MagSceneSavedMenu);
} else {
scene_manager_search_and_switch_to_previous_scene(mag->scene_manager, MagSceneStart);
}
mag_device_set_loading_callback(mag->mag_dev, NULL, mag);
}
bool mag_scene_file_select_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void mag_scene_file_select_on_exit(void* context) {
UNUSED(context);
}
@@ -1,82 +0,0 @@
#include <toolbox/name_generator.h>
#include "../mag_i.h"
void mag_scene_input_name_on_enter(void* context) {
Mag* mag = context;
TextInput* text_input = mag->text_input;
FuriString* folder_path;
folder_path = furi_string_alloc();
//TODO: compatible types / etc
//bool name_is_empty = furi_string_empty(mag->mag_dev->dev_name);
bool name_is_empty = true;
if(name_is_empty) {
furi_string_set(mag->file_path, MAG_APP_FOLDER);
name_generator_make_auto(mag->text_store, MAG_TEXT_STORE_SIZE, "Mag");
furi_string_set(folder_path, MAG_APP_FOLDER);
} else {
// TODO: compatible types etc
//mag_text_store_set(mag, "%s", furi_string_get_cstr(mag->mag_dev->dev_name));
path_extract_dirname(furi_string_get_cstr(mag->file_path), folder_path);
}
text_input_set_header_text(text_input, "Name the card");
text_input_set_result_callback(
text_input,
mag_text_input_callback,
mag,
mag->text_store,
MAG_DEV_NAME_MAX_LEN,
name_is_empty);
FURI_LOG_I("", "%s %s", furi_string_get_cstr(folder_path), mag->text_store);
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
furi_string_get_cstr(folder_path),
MAG_APP_EXTENSION,
furi_string_get_cstr(mag->file_name));
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
furi_string_free(folder_path);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewTextInput);
}
bool mag_scene_input_name_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == MagEventNext) {
consumed = true;
//if(!furi_string_empty(mag->file_name)) {
// mag_delete_key(mag);
//}
furi_string_set(mag->file_name, mag->text_store);
if(mag_device_save(mag->mag_dev, furi_string_get_cstr(mag->file_name))) {
scene_manager_next_scene(scene_manager, MagSceneSaveSuccess);
} else {
//scene_manager_search_and_switch_to_previous_scene(
// scene_manager, MagSceneReadKeyMenu);
// TODO: Replace with appropriate scene! No read scene prior if adding manually...
}
}
}
return consumed;
}
void mag_scene_input_name_on_exit(void* context) {
Mag* mag = context;
TextInput* text_input = mag->text_input;
void* validator_context = text_input_get_validator_callback_context(text_input);
text_input_set_validator(text_input, NULL, NULL);
validator_is_file_free((ValidatorIsFile*)validator_context);
text_input_reset(text_input);
}
@@ -1,37 +0,0 @@
#include "../mag_i.h"
void mag_scene_input_value_on_enter(void* context) {
Mag* mag = context;
Mag_TextInput* mag_text_input = mag->mag_text_input;
// TODO: retrieve stored/existing data if editing rather than adding anew?
mag_text_store_set(mag, furi_string_get_cstr(mag->mag_dev->dev_data.track[1].str));
mag_text_input_set_header_text(mag_text_input, "Enter track data (WIP)");
mag_text_input_set_result_callback(
mag_text_input, mag_text_input_callback, mag, mag->text_store, MAG_TEXT_STORE_SIZE, true);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewTextMagInput);
}
bool mag_scene_input_value_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == MagEventNext) {
consumed = true;
furi_string_set(mag->mag_dev->dev_data.track[1].str, mag->text_store);
scene_manager_next_scene(scene_manager, MagSceneInputName);
}
}
return consumed;
}
void mag_scene_input_value_on_exit(void* context) {
Mag* mag = context;
UNUSED(mag);
}
@@ -1,186 +0,0 @@
// Creator: Hummus@FlipperGang
#include "../mag_i.h"
#include "../helpers/mag_helpers.h"
#include "mag_scene_read.h"
#define TAG "MagSceneRead"
void uart_callback(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) {
Mag* mag = context;
if(event == FuriHalSerialRxEventData) {
uint8_t data = furi_hal_serial_async_rx(handle);
furi_stream_buffer_send(mag->uart_rx_stream, &data, 1, 0);
furi_thread_flags_set(furi_thread_get_id(mag->uart_rx_thread), WorkerEvtRxDone);
}
}
static int32_t uart_worker(void* context) {
Mag* mag = context;
mag->uart_rx_stream = furi_stream_buffer_alloc(UART_RX_BUF_SIZE, 1);
mag->uart_text_box_store_strlen = 0;
while(1) {
uint32_t events =
furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever);
// furi_check((events & FuriFlagError) == 0);
if(events & WorkerEvtStop) break;
if(events & WorkerEvtRxDone) {
FURI_LOG_D(TAG, "WorkerEvtRxDone");
// notification_message(mag->notifications, &sequence_success);
size_t len = furi_stream_buffer_receive(
mag->uart_rx_stream, mag->uart_rx_buf, UART_RX_BUF_SIZE, 200);
FURI_LOG_D(TAG, "UART RX len: %d", len);
if(len > 0) {
// If text box store gets too big, then truncate it
mag->uart_text_box_store_strlen += len;
if(mag->uart_text_box_store_strlen >= UART_TERMINAL_TEXT_BOX_STORE_SIZE - 1) {
furi_string_right(
mag->uart_text_box_store, mag->uart_text_box_store_strlen / 2);
mag->uart_text_box_store_strlen =
furi_string_size(mag->uart_text_box_store) + len;
}
// Add '\0' to the end of the string, and then add the new data
mag->uart_rx_buf[len] = '\0';
furi_string_cat_printf(mag->uart_text_box_store, "%s", mag->uart_rx_buf);
FURI_LOG_D(TAG, "UART RX buf: %*.s", len, mag->uart_rx_buf);
FURI_LOG_D(
TAG, "UART RX store: %s", furi_string_get_cstr(mag->uart_text_box_store));
}
FURI_LOG_D(TAG, "UARTEventRxData");
view_dispatcher_send_custom_event(mag->view_dispatcher, UARTEventRxData);
}
}
furi_stream_buffer_free(mag->uart_rx_stream);
return 0;
}
void update_widgets(Mag* mag) {
// Clear widget from all elements
widget_reset(mag->widget);
// Titlebar
widget_add_icon_element(mag->widget, 38, -1, &I_mag_file_10px);
widget_add_string_element(mag->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "READ");
widget_add_icon_element(mag->widget, 81, -1, &I_mag_file_10px);
// Text box
widget_add_text_scroll_element(
mag->widget, 0, 10, 128, 40, furi_string_get_cstr(mag->uart_text_box_store));
// Buttons
widget_add_button_element(mag->widget, GuiButtonTypeLeft, "Clear", mag_widget_callback, mag);
widget_add_button_element(mag->widget, GuiButtonTypeRight, "Parse", mag_widget_callback, mag);
}
void mag_scene_read_on_enter(void* context) {
Mag* mag = context;
FuriString* message = furi_string_alloc();
furi_string_printf(message, "Please swipe a card!\n");
mag->uart_text_box_store = message;
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
update_widgets(mag);
// Initialize UART
mag->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart);
furi_check(mag->serial_handle);
furi_hal_serial_init(mag->serial_handle, 9600);
furi_hal_serial_async_rx_start(mag->serial_handle, uart_callback, mag, false);
FURI_LOG_D(TAG, "UART initialized");
mag->uart_rx_thread = furi_thread_alloc();
furi_thread_set_name(mag->uart_rx_thread, "UartRx");
furi_thread_set_stack_size(mag->uart_rx_thread, 1024);
furi_thread_set_context(mag->uart_rx_thread, mag);
furi_thread_set_callback(mag->uart_rx_thread, uart_worker);
furi_thread_start(mag->uart_rx_thread);
FURI_LOG_D(TAG, "UART worker started");
}
bool mag_scene_read_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
FURI_LOG_D(TAG, "Custom event: %ld", event.event);
switch(event.event) {
case GuiButtonTypeLeft: // Clear
consumed = true;
// Clear text box store
furi_string_reset(mag->uart_text_box_store);
mag->uart_text_box_store_strlen = 0;
break;
case GuiButtonTypeRight: // Parse
consumed = true;
FURI_LOG_D(TAG, "Trying to parse");
MagDevice* mag_dev = mag->mag_dev;
bool res = mag_device_parse_card_string(mag_dev, mag->uart_text_box_store);
furi_string_reset(mag->uart_text_box_store);
if(res) {
notification_message(mag->notifications, &sequence_success);
furi_string_printf(
mag->uart_text_box_store,
"Track 1: %.*s\nTrack 2: %.*s\nTrack 3: %.*s",
mag_dev->dev_data.track[0].len,
furi_string_get_cstr(mag_dev->dev_data.track[0].str),
mag_dev->dev_data.track[1].len,
furi_string_get_cstr(mag_dev->dev_data.track[1].str),
mag_dev->dev_data.track[2].len,
furi_string_get_cstr(mag_dev->dev_data.track[2].str));
// Switch to saved menu scene
scene_manager_next_scene(mag->scene_manager, MagSceneSavedMenu);
} else {
furi_string_printf(mag->uart_text_box_store, "Failed to parse! Try again\n");
notification_message(mag->notifications, &sequence_error);
}
break;
}
update_widgets(mag);
}
return consumed;
}
void mag_scene_read_on_exit(void* context) {
Mag* mag = context;
// notification_message(mag->notifications, &sequence_blink_stop);
widget_reset(mag->widget);
// view_dispatcher_remove_view(mag->view_dispatcher, MagViewWidget);
// Stop UART worker
FURI_LOG_D(TAG, "Stopping UART worker");
furi_hal_serial_async_rx_stop(mag->serial_handle);
furi_hal_serial_deinit(mag->serial_handle);
furi_hal_serial_control_release(mag->serial_handle);
furi_thread_flags_set(furi_thread_get_id(mag->uart_rx_thread), WorkerEvtStop);
furi_thread_join(mag->uart_rx_thread);
furi_thread_free(mag->uart_rx_thread);
FURI_LOG_D(TAG, "UART worker stopped");
furi_string_free(mag->uart_text_box_store);
notification_message(mag->notifications, &sequence_blink_stop);
}
@@ -1,20 +0,0 @@
#pragma once
#include <gui/modules/text_box.h>
#define UART_RX_BUF_SIZE (320)
#define UART_TERMINAL_TEXT_BOX_STORE_SIZE (4096)
#define UART_TERMINAL_TEXT_INPUT_STORE_SIZE (512)
#define UART_CH (FuriHalSerialIdUsart)
#define UART_BAUDRATE (9600)
typedef enum {
WorkerEvtStop = (1 << 0),
WorkerEvtRxDone = (1 << 1),
} WorkerEvtFlags;
typedef enum {
UARTEventRxData = 100,
} UARTEvents;
#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone)
@@ -1,43 +0,0 @@
#include "../mag_i.h"
void mag_scene_save_success_on_enter(void* context) {
Mag* mag = context;
Popup* popup = mag->popup;
// Clear state of data enter scene
//scene_manager_set_scene_state(mag->scene_manager, LfRfidSceneSaveData, 0);
mag_text_store_clear(mag);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
popup_set_context(popup, mag);
popup_set_callback(popup, mag_popup_timeout_callback);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewPopup);
}
bool mag_scene_save_success_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
bool consumed = false;
if((event.type == SceneManagerEventTypeBack) ||
((event.type == SceneManagerEventTypeCustom) && (event.event == MagEventPopupClosed))) {
bool result =
scene_manager_search_and_switch_to_previous_scene(mag->scene_manager, MagSceneStart);
if(!result) {
scene_manager_search_and_switch_to_another_scene(
mag->scene_manager, MagSceneFileSelect);
}
consumed = true;
}
return consumed;
}
void mag_scene_save_success_on_exit(void* context) {
Mag* mag = context;
popup_reset(mag->popup);
}
@@ -1,50 +0,0 @@
#include "../mag_i.h"
void mag_scene_saved_info_on_enter(void* context) {
Mag* mag = context;
Widget* widget = mag->widget;
FuriString* tmp_str;
tmp_str = furi_string_alloc();
// Use strlcpy instead perhaps, to truncate to screen width, then add ellipses if needed?
furi_string_printf(tmp_str, "%s\r\n", mag->mag_dev->dev_name);
widget_add_icon_element(widget, 1, 1, &I_mag_file_10px);
widget_add_string_element(
widget, 13, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp_str));
furi_string_reset(tmp_str);
for(uint8_t i = 0; i < MAG_DEV_TRACKS; i++) {
FuriString* trackstr = mag->mag_dev->dev_data.track[i].str;
furi_string_cat_printf(
tmp_str,
"Track %d:%s%s%s",
(i + 1),
furi_string_empty(trackstr) ? " " : "\n",
furi_string_empty(trackstr) ? "< empty >" : furi_string_get_cstr(trackstr),
(i + 1 == MAG_DEV_TRACKS) ? "" : "\n\n");
}
widget_add_text_scroll_element(widget, 0, 15, 128, 49, furi_string_get_cstr(tmp_str));
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
furi_string_free(tmp_str);
}
bool mag_scene_saved_info_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
UNUSED(event);
UNUSED(scene_manager);
return consumed;
}
void mag_scene_saved_info_on_exit(void* context) {
Mag* mag = context;
widget_reset(mag->widget);
}
@@ -1,65 +0,0 @@
#include "../mag_i.h"
enum SubmenuIndex {
SubmenuIndexEmulate,
//SubmenuIndexEdit,
SubmenuIndexDelete,
SubmenuIndexInfo,
};
void mag_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
Mag* mag = context;
view_dispatcher_send_custom_event(mag->view_dispatcher, index);
}
void mag_scene_saved_menu_on_enter(void* context) {
Mag* mag = context;
Submenu* submenu = mag->submenu;
submenu_add_item(
submenu, "Emulate", SubmenuIndexEmulate, mag_scene_saved_menu_submenu_callback, mag);
//submenu_add_item(
// submenu, "Edit (WIP)", SubmenuIndexEdit, mag_scene_saved_menu_submenu_callback, mag);
submenu_add_item(
submenu, "Delete", SubmenuIndexDelete, mag_scene_saved_menu_submenu_callback, mag);
submenu_add_item(
submenu, "Info", SubmenuIndexInfo, mag_scene_saved_menu_submenu_callback, mag);
submenu_set_selected_item(
mag->submenu, scene_manager_get_scene_state(mag->scene_manager, MagSceneSavedMenu));
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewSubmenu);
}
bool mag_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(mag->scene_manager, MagSceneSavedMenu, event.event);
// TODO: replace with actual next scenes once built
if(event.event == SubmenuIndexEmulate) {
scene_manager_next_scene(mag->scene_manager, MagSceneEmulate);
consumed = true;
//} else if(event.event == SubmenuIndexEdit) {
// scene_manager_next_scene(mag->scene_manager, MagSceneUnderConstruction);
// consumed = true;
} else if(event.event == SubmenuIndexDelete) {
scene_manager_next_scene(mag->scene_manager, MagSceneDeleteConfirm);
consumed = true;
} else if(event.event == SubmenuIndexInfo) {
scene_manager_next_scene(mag->scene_manager, MagSceneSavedInfo);
consumed = true;
}
}
return consumed;
}
void mag_scene_saved_menu_on_exit(void* context) {
Mag* mag = context;
submenu_reset(mag->submenu);
}
@@ -1,267 +0,0 @@
#include "../mag_i.h"
#include "../mag_state.h"
#include "../helpers/mag_helpers.h"
#define TAG "MagSceneEmulateConfig"
enum VarItemListIndex {
VarItemListIndexPinInput,
VarItemListIndexPinOutput,
VarItemListIndexPinEnable,
VarItemListIndexNRepeats,
VarItemListIndexRepeatModeOn,
VarItemListIndexAllowUART,
};
static const char* gpio[] = {
[MagPinA7] = "2 (A7)",
[MagPinA6] = "3 (A6)",
[MagPinA4] = "4 (A4)",
[MagPinB3] = "5 (B3)",
[MagPinB2] = "6 (B2)",
[MagPinC3] = "7 (C3)",
[MagPinC1] = "15 (C1)",
[MagPinC0] = "16 (C0)",
};
const uint8_t GPIO_COUNT = COUNT_OF(gpio);
// static const char* uart_pins[] = {[DapUartTypeUSART1] = "13,14", [DapUartTypeLPUART1] = "15,16"};
// static const char* uart_swap[] = {[DapUartTXRXNormal] = "No", [DapUartTXRXSwap] = "Yes"};
#define N_REPEATS_COUNT 10
const char* const n_repeats_text[N_REPEATS_COUNT] = {
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"20",
};
const uint32_t n_repeats_value[N_REPEATS_COUNT] = {
2,
3,
4,
5,
6,
7,
8,
9,
10,
20,
};
#define OFF_ON_COUNT 2
const char* const off_on_text[OFF_ON_COUNT] = {
"OFF",
"ON",
};
void mag_scene_settings_var_item_list_callback(void* context, uint32_t index) {
Mag* mag = context;
view_dispatcher_send_custom_event(mag->view_dispatcher, index);
}
static void mag_scene_settings_set_gpio(VariableItem* item, MagPin* pin_out) {
MagPin pin = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, gpio[pin]);
*pin_out = pin;
}
static void mag_scene_settings_set_gpio_input(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
mag_scene_settings_set_gpio(item, &mag->state.pin_input);
};
static void mag_scene_settings_set_gpio_output(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
mag_scene_settings_set_gpio(item, &mag->state.pin_output);
};
static void mag_scene_settings_set_gpio_enable(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
mag_scene_settings_set_gpio(item, &mag->state.pin_enable);
};
static void mag_scene_settings_set_n_repeats(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, n_repeats_text[index]);
mag->state.n_repeats = n_repeats_value[index];
}
static void mag_scene_settings_set_bool(VariableItem* item, bool* bool_out) {
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, off_on_text[index]);
*bool_out = (bool)index;
}
static void mag_scene_settings_set_repeat_mode(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
mag_scene_settings_set_bool(item, &mag->state.repeat_mode);
}
static void mag_scene_settings_set_allow_uart(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
// rising change when value index is truth-y, and prior value false
bool rising = !mag->state.allow_uart && !!variable_item_get_current_value_index(item);
// trigger dialog only on rising change
if(rising) {
scene_manager_set_scene_state(mag->scene_manager, MagSceneSettings, (uint32_t)item);
view_dispatcher_send_custom_event(mag->view_dispatcher, MagEventConfirmDialog);
}
// set value & text based on current varitem index
mag_scene_settings_set_bool(item, &mag->state.allow_uart);
}
static void mag_pin_variable_item_list_add(
Mag* mag,
const char* label,
MagPin pin,
VariableItemChangeCallback change_callback) {
VariableItem* item =
variable_item_list_add(mag->variable_item_list, label, GPIO_COUNT, change_callback, mag);
variable_item_set_current_value_index(item, pin);
variable_item_set_current_value_text(item, gpio[pin]);
}
static void mag_bool_variable_item_list_add(
Mag* mag,
const char* label,
bool value,
VariableItemChangeCallback change_callback) {
VariableItem* item =
variable_item_list_add(mag->variable_item_list, label, OFF_ON_COUNT, change_callback, mag);
uint32_t value_index = (uint32_t)value;
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, off_on_text[value_index]);
}
void mag_scene_settings_on_enter(void* context) {
Mag* mag = context;
VariableItemList* var_item_list = mag->variable_item_list;
VariableItem* item;
uint32_t value_index;
// reload state in the event temporary changes have been
// made on the emulate config screen
// only changes made in this scene should be saved, and this scene
// should always represent the saved settings, not the transient ones for
// a given emulation.
mag_state_load(&mag->state);
mag_pin_variable_item_list_add(
mag, "Input pin:", mag->state.pin_input, mag_scene_settings_set_gpio_input);
mag_pin_variable_item_list_add(
mag, "Output pin:", mag->state.pin_output, mag_scene_settings_set_gpio_output);
mag_pin_variable_item_list_add(
mag, "Enable pin:", mag->state.pin_enable, mag_scene_settings_set_gpio_enable);
mag_bool_variable_item_list_add(
mag, "Repeat default:", mag->state.repeat_mode, mag_scene_settings_set_repeat_mode);
item = variable_item_list_add(
var_item_list, "# repeats: ", N_REPEATS_COUNT, mag_scene_settings_set_n_repeats, mag);
value_index = value_index_uint32(mag->state.n_repeats, n_repeats_value, N_REPEATS_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, n_repeats_text[value_index]);
mag_bool_variable_item_list_add(
mag, "UART MSR:", mag->state.allow_uart, mag_scene_settings_set_allow_uart);
variable_item_list_set_enter_callback(
var_item_list, mag_scene_settings_var_item_list_callback, mag);
variable_item_list_set_selected_item(
var_item_list, scene_manager_get_scene_state(mag->scene_manager, MagSceneSettings));
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewVariableItemList);
}
void mag_scene_settings_dialog_invalid_pins(Mag* mag) {
SceneManager* scene_manager = mag->scene_manager;
DialogMessage* message = dialog_message_alloc();
dialog_message_set_header(message, "Invalid Pin Config!", 64, 0, AlignCenter, AlignTop);
dialog_message_set_buttons(message, "Modify", NULL, "Reset");
dialog_message_set_text(
message,
"Pins cannot overlap.\nChange, or reset to defaults.",
64,
32,
AlignCenter,
AlignCenter);
DialogMessageButton res = dialog_message_show(furi_record_open(RECORD_DIALOGS), message);
dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
if(res == DialogMessageButtonRight) {
mag_state_gpio_reset(&mag->state);
scene_manager_previous_scene(scene_manager);
}
}
bool mag_scene_settings_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
switch(event.type) {
case SceneManagerEventTypeBack:
// when attempting to exit, validate pin configuration
// if invalid, prompt
consumed = true;
if(!mag_state_gpio_is_valid(&mag->state)) {
mag_scene_settings_dialog_invalid_pins(mag);
} else {
scene_manager_previous_scene(scene_manager);
}
break;
case SceneManagerEventTypeCustom:
// scene_manager_set_scene_state(mag->scene_manager, MagSceneSettings, event.event);
consumed = true;
if(event.event == MagEventConfirmDialog) {
DialogMessage* msg = dialog_message_alloc();
dialog_message_set_header(msg, "UART MSR", 64, 0, AlignCenter, AlignTop);
dialog_message_set_buttons(msg, "No", NULL, "Yes");
dialog_message_set_text(
msg,
"This option requires a\nUART-compatible mag reader.\nIs it installed?\n",
64,
32,
AlignCenter,
AlignCenter);
DialogMessageButton res = dialog_message_show(furi_record_open(RECORD_DIALOGS), msg);
if(res != DialogMessageButtonRight) {
// if not "Yes", reset to "OFF" (0 / false-y)
VariableItem* item =
(VariableItem*)scene_manager_get_scene_state(scene_manager, MagSceneSettings);
variable_item_set_current_value_index(item, 0);
mag_scene_settings_set_bool(item, &mag->state.allow_uart);
}
dialog_message_free(msg);
furi_record_close(RECORD_DIALOGS);
// clear item from scene state
scene_manager_set_scene_state(scene_manager, MagSceneSettings, 0);
}
break;
default:
break;
}
return consumed;
}
void mag_scene_settings_on_exit(void* context) {
Mag* mag = context;
variable_item_list_reset(mag->variable_item_list);
mag_state_save(&mag->state);
}
@@ -1,86 +0,0 @@
#include "../mag_i.h"
typedef enum {
SubmenuIndexSaved,
SubmenuIndexRead,
//SubmenuIndexAddManually,
SubmenuIndexSettings,
SubmenuIndexAbout,
} SubmenuIndex;
static void mag_scene_start_submenu_callback(void* context, uint32_t index) {
Mag* mag = context;
view_dispatcher_send_custom_event(mag->view_dispatcher, index);
}
void mag_scene_start_on_enter(void* context) {
Mag* mag = context;
Submenu* submenu = mag->submenu;
submenu_add_item(submenu, "Saved", SubmenuIndexSaved, mag_scene_start_submenu_callback, mag);
submenu_add_lockable_item(
submenu,
"Read",
SubmenuIndexRead,
mag_scene_start_submenu_callback,
mag,
(!mag->state.is_debug && !mag->state.allow_uart),
"Enable Debug!");
//submenu_add_item(
// submenu, "Add Manually", SubmenuIndexAddManually, mag_scene_start_submenu_callback, mag);
submenu_add_item(
submenu, "Settings", SubmenuIndexSettings, mag_scene_start_submenu_callback, mag);
submenu_add_item(submenu, "About", SubmenuIndexAbout, mag_scene_start_submenu_callback, mag);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(mag->scene_manager, MagSceneStart));
// clear key
furi_string_reset(mag->file_name);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewSubmenu);
}
bool mag_scene_start_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case SubmenuIndexSaved:
furi_string_set(mag->file_path, MAG_APP_FOLDER);
scene_manager_next_scene(mag->scene_manager, MagSceneFileSelect);
consumed = true;
break;
case SubmenuIndexRead:
scene_manager_next_scene(mag->scene_manager, MagSceneRead);
consumed = true;
break;
//case SubmenuIndexAddManually:
// scene_manager_next_scene(mag->scene_manager, MagSceneInputValue);
// consumed = true;
// break;
case SubmenuIndexSettings:
scene_manager_next_scene(mag->scene_manager, MagSceneSettings);
consumed = true;
break;
case SubmenuIndexAbout:
scene_manager_next_scene(mag->scene_manager, MagSceneAbout);
consumed = true;
break;
}
scene_manager_set_scene_state(mag->scene_manager, MagSceneStart, event.event);
}
return consumed;
}
void mag_scene_start_on_exit(void* context) {
Mag* mag = context;
submenu_reset(mag->submenu);
}
@@ -1,40 +0,0 @@
#include "../mag_i.h"
void mag_scene_under_construction_on_enter(void* context) {
Mag* mag = context;
Widget* widget = mag->widget;
FuriString* tmp_str;
tmp_str = furi_string_alloc();
widget_add_button_element(widget, GuiButtonTypeLeft, "Back", mag_widget_callback, mag);
furi_string_printf(tmp_str, "Under construction!");
widget_add_string_element(
widget, 64, 4, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp_str));
furi_string_reset(tmp_str);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
furi_string_free(tmp_str);
}
bool mag_scene_under_construction_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = true;
scene_manager_previous_scene(scene_manager);
}
}
return consumed;
}
void mag_scene_under_construction_on_exit(void* context) {
Mag* mag = context;
widget_reset(mag->widget);
}
+1 -1
View File
@@ -3,7 +3,7 @@ import posixpath
# For more details on these options, run 'fbt -h'
FIRMWARE_ORIGIN = "ARF"
FIRMWARE_ORIGIN = "Official"
# Default hardware target
TARGET_HW = 7
+166 -102
View File
@@ -170,7 +170,7 @@ static bool kia_v3_v4_process_buffer(SubGhzProtocolDecoderKiaV3V4* instance) {
((uint64_t)b[6] << 8) | (uint64_t)b[7];
instance->generic.data = key_data;
instance->generic.data_count_bit = 68;
instance->decoder.decode_data = key_data;
instance->decoder.decode_count_bit = 68;
@@ -204,8 +204,8 @@ const SubGhzProtocolEncoder subghz_protocol_kia_v3_v4_encoder = {
const SubGhzProtocol subghz_protocol_kia_v3_v4 = {
.name = SUBGHZ_PROTOCOL_KIA_V3_V4_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_kia_v3_v4_decoder,
.encoder = &subghz_protocol_kia_v3_v4_encoder,
@@ -411,7 +411,6 @@ SubGhzProtocolStatus
for(size_t i = 0; i < str_len && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9') {
nibble = c - '0';
@@ -422,20 +421,31 @@ SubGhzProtocolStatus
} else {
break;
}
key = (key << 4) | nibble;
hex_pos++;
}
furi_string_free(temp_str);
if(hex_pos != 16) {
if(hex_pos < 14) {
FURI_LOG_E(TAG, "Invalid key length: %zu nibbles", hex_pos);
break;
}
instance->generic.data = key;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
instance->encrypted = 0;
}
flipper_format_rewind(flipper_format);
uint32_t decrypted_temp = 0;
if(!flipper_format_read_uint32(flipper_format, "Decrypted", &decrypted_temp, 1)) {
decrypted_temp = 0;
}
instance->decrypted = decrypted_temp;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1)) {
uint8_t b[8];
@@ -447,11 +457,10 @@ SubGhzProtocolStatus
b[5] = (key >> 16) & 0xFF;
b[6] = (key >> 8) & 0xFF;
b[7] = key & 0xFF;
instance->serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) | ((uint32_t)reverse8(b[5]) << 8) |
((uint32_t)reverse8(b[6]) << 16) |
((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
} else {
}
instance->generic.serial = instance->serial;
@@ -468,14 +477,10 @@ SubGhzProtocolStatus
uint32_t cnt_temp;
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt_temp, 1)) {
instance->cnt = (uint16_t)cnt_temp;
} else if(instance->decrypted != 0) {
instance->cnt = instance->decrypted & 0xFFFF;
} else {
flipper_format_rewind(flipper_format);
uint32_t decrypted_temp;
if(flipper_format_read_uint32(flipper_format, "Decrypted", &decrypted_temp, 1)) {
instance->cnt = decrypted_temp & 0xFFFF;
} else {
instance->cnt = 0;
}
instance->cnt = 0;
}
flipper_format_rewind(flipper_format);
@@ -488,6 +493,26 @@ SubGhzProtocolStatus
instance->version = 0;
}
flipper_format_rewind(flipper_format);
uint32_t crc_temp;
if(flipper_format_read_uint32(flipper_format, "CRC", &crc_temp, 1)) {
instance->crc = (uint8_t)crc_temp;
} else {
instance->crc = 0;
}
if(instance->encrypted == 0 && instance->decrypted != 0) {
instance->encrypted = keeloq_common_encrypt(instance->decrypted, KIA_MF_KEY);
}
if(instance->decrypted != 0) {
instance->generic.btn = (instance->decrypted >> 28) & 0x0F;
instance->generic.cnt = instance->decrypted & 0xFFFF;
if(instance->btn == 0) {
instance->btn = instance->generic.btn;
}
}
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
@@ -505,7 +530,7 @@ SubGhzProtocolStatus
} else {
selected_btn = subghz_custom_btn_get();
}
if(selected_btn == 5) {
instance->btn = 0x8;
} else if(selected_btn >= 1 && selected_btn <= 4) {
@@ -528,48 +553,35 @@ SubGhzProtocolStatus
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t cnt_to_write = instance->cnt;
if(!flipper_format_update_uint32(flipper_format, "Cnt", &cnt_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t btn_to_write = instance->btn;
if(!flipper_format_update_uint32(flipper_format, "Btn", &btn_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t decrypted_to_write = instance->decrypted;
if(!flipper_format_update_uint32(flipper_format, "Decrypted", &decrypted_to_write, 1)) {
}
flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t));
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t encrypted_to_write = instance->encrypted;
if(!flipper_format_update_uint32(flipper_format, "Encrypted", &encrypted_to_write, 1)) {
flipper_format_update_uint32(flipper_format, "Encrypted", &encrypted_to_write, 1);
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t decrypted_to_write = instance->decrypted;
flipper_format_update_uint32(flipper_format, "Decrypted", &decrypted_to_write, 1);
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t version_to_write = instance->version;
flipper_format_update_uint32(flipper_format, "Version", &version_to_write, 1);
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t crc_to_write = instance->crc;
if(!flipper_format_update_uint32(flipper_format, "CRC", &crc_to_write, 1)) {
}
flipper_format_update_uint32(flipper_format, "CRC", &crc_to_write, 1);
instance->encoder.is_running = true;
instance->encoder.front = 0;
@@ -587,7 +599,6 @@ void subghz_protocol_encoder_kia_v3_v4_stop(void* context) {
instance->encoder.front = 0;
}
static void subghz_protocol_encoder_kia_v3_v4_patch_crc(SubGhzProtocolEncoderKiaV3V4* instance) {
if(!instance || !instance->encoder.upload) return;
const bool v4 = (instance->version == 0);
@@ -655,6 +666,7 @@ void* subghz_protocol_decoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
instance->is_v3_sync = false;
return instance;
}
void subghz_protocol_decoder_kia_v3_v4_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
@@ -677,7 +689,7 @@ void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t
SubGhzProtocolDecoderKiaV3V4* instance = context;
switch(instance->decoder.parser_step) {
case KiaV3V4DecoderStepReset:
case KiaV3V4DecoderStepReset:
if(level && DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta) {
instance->decoder.parser_step = KiaV3V4DecoderStepCheckPreamble;
@@ -778,16 +790,19 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v3_v4_serialize(
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
if(!flipper_format_write_uint32(
flipper_format, "Encrypted", &instance->encrypted, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
if(!flipper_format_write_uint32(
flipper_format, "Decrypted", &instance->decrypted, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
@@ -813,71 +828,123 @@ SubGhzProtocolStatus
subghz_protocol_decoder_kia_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_deserialize_check_count_bit(&instance->generic, flipper_format, 64);
if(ret == SubGhzProtocolStatusOk) {
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
flipper_format_rewind(flipper_format);
uint32_t bit_count = 0;
if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count, 1)) {
FURI_LOG_E(TAG, "Missing Bit field");
break;
}
if(bit_count != 68 && bit_count != 64) {
FURI_LOG_E(TAG, "Wrong bit count: %lu", bit_count);
break;
}
instance->generic.data_count_bit = 68;
flipper_format_rewind(flipper_format);
FuriString* temp_str = furi_string_alloc();
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
FURI_LOG_E(TAG, "Missing Key field");
furi_string_free(temp_str);
break;
}
const char* key_str = furi_string_get_cstr(temp_str);
uint64_t key = 0;
size_t str_len = strlen(key_str);
size_t hex_pos = 0;
for(size_t i = 0; i < str_len && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9') {
nibble = c - '0';
} else if(c >= 'A' && c <= 'F') {
nibble = c - 'A' + 10;
} else if(c >= 'a' && c <= 'f') {
nibble = c - 'a' + 10;
} else {
break;
}
key = (key << 4) | nibble;
hex_pos++;
}
furi_string_free(temp_str);
if(hex_pos < 14) {
FURI_LOG_E(TAG, "Invalid key: %zu nibbles", hex_pos);
break;
}
instance->generic.data = key;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
instance->encrypted = 0;
}
if(!flipper_format_read_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
instance->decrypted = 0;
flipper_format_rewind(flipper_format);
uint32_t decrypted_temp = 0;
if(!flipper_format_read_uint32(flipper_format, "Decrypted", &decrypted_temp, 1)) {
decrypted_temp = 0;
}
instance->decrypted = decrypted_temp;
flipper_format_rewind(flipper_format);
uint32_t temp_version = 0;
if(flipper_format_read_uint32(flipper_format, "Version", &temp_version, 1)) {
instance->version = temp_version;
instance->version = (uint8_t)temp_version;
} else {
instance->version = 0;
}
flipper_format_rewind(flipper_format);
uint32_t temp_crc = 0;
if(flipper_format_read_uint32(flipper_format, "CRC", &temp_crc, 1)) {
instance->crc = temp_crc;
instance->crc = (uint8_t)temp_crc;
} else {
instance->crc = 0;
}
if(instance->decrypted != 0) {
instance->generic.btn = (instance->decrypted >> 28) & 0x0F;
instance->generic.cnt = instance->decrypted & 0xFFFF;
}
if(instance->generic.data != 0) {
uint8_t b[8];
for(int i = 0; i < 8; i++) {
b[i] = (instance->generic.data >> ((7-i) * 8)) & 0xFF;
b[i] = (instance->generic.data >> ((7 - i) * 8)) & 0xFF;
}
instance->generic.serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) |
((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
instance->generic.serial =
((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) |
((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
}
if(instance->encrypted == 0 && instance->decrypted != 0) {
instance->encrypted = keeloq_common_encrypt(instance->decrypted, KIA_MF_KEY);
}
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->generic.btn);
}
subghz_custom_btn_set_max(5);
}
return ret;
}
static uint64_t compute_yek(uint64_t key) {
uint64_t yek = 0;
for(int i = 0; i < 64; i++) {
yek |= ((key >> i) & 1) << (63 - i);
}
return yek;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
static bool kia_v3_v4_verify_crc_from_data(uint64_t data, uint8_t received_crc) {
uint8_t bytes[8];
for(int i = 0; i < 8; i++) {
bytes[i] = (data >> ((7-i) * 8)) & 0xFF;
bytes[i] = (data >> ((7 - i) * 8)) & 0xFF;
}
uint8_t calculated_crc = kia_v3_v4_calculate_crc(bytes);
return (calculated_crc == received_crc);
@@ -887,33 +954,30 @@ void subghz_protocol_decoder_kia_v3_v4_get_string(void* context, FuriString* out
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
uint64_t yek = compute_yek(instance->generic.data);
uint32_t key_hi = (uint32_t)(instance->generic.data >> 32);
uint32_t key_lo = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
uint32_t yek_hi = (uint32_t)(yek >> 32);
uint32_t yek_lo = (uint32_t)(yek & 0xFFFFFFFF);
bool crc_valid = kia_v3_v4_verify_crc_from_data(instance->generic.data, instance->crc);
uint8_t kb[8];
for(int i = 0; i < 8; i++) {
kb[i] = (instance->generic.data >> ((7 - i) * 8)) & 0xFF;
}
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Yek:%08lX%08lX\r\n"
"Sn:%07lX Btn:%X [%s]\r\n"
"Dec:%08lX Cnt:%04lX\r\n"
"CRC:%X %s",
"Key:%02X %02X %02X %02X %02X %02X %02X %02X\r\n"
"Encrypted:%lu\r\n"
"Decrypted:%lu\r\n"
"Btn:%X [%s]\r\n"
"Version:%u\r\n"
"CRC:%u %s",
kia_version_names[instance->version],
instance->generic.data_count_bit,
key_hi,
key_lo,
yek_hi,
yek_lo,
instance->generic.serial,
instance->generic.btn,
kb[0], kb[1], kb[2], kb[3], kb[4], kb[5], kb[6], kb[7],
(unsigned long)instance->encrypted,
(unsigned long)instance->decrypted,
(unsigned)instance->generic.btn,
subghz_protocol_kia_v3_v4_get_name_button(instance->generic.btn),
instance->decrypted,
instance->generic.cnt,
instance->crc,
(unsigned)instance->version,
(unsigned)instance->crc,
crc_valid ? "(OK)" : "(FAIL)");
}
+36 -45
View File
@@ -226,8 +226,6 @@ void* subghz_protocol_encoder_kia_v5_alloc(SubGhzEnvironment* environment) {
return instance;
}
static bool subghz_protocol_encoder_kia_v5_get_upload(SubGhzProtocolEncoderKiaV5* instance) {
furi_assert(instance);
@@ -320,7 +318,7 @@ SubGhzProtocolStatus
instance->generic.cnt = mixer_decode(encrypted);
uint32_t mult = furi_hal_subghz_get_rolling_counter_mult();
instance->generic.cnt = (instance->generic.cnt + mult) & 0xFFFF;
instance->generic.cnt = (instance->generic.cnt + mult) & 0xFFFF;
FURI_LOG_I(TAG, "deserialize #%lu, cnt after=%04lX", call_count, (uint32_t)instance->generic.cnt);
if(subghz_custom_btn_get_original() == 0) {
@@ -560,7 +558,8 @@ void subghz_protocol_decoder_kia_v5_feed(void* context, bool level, uint32_t dur
} else {
if(instance->bit_count >= subghz_protocol_kia_v5_const.min_count_bit_for_found) {
instance->generic.data = instance->saved_key;
instance->generic.data_count_bit = (instance->bit_count > 67) ? 67 : instance->bit_count;
instance->generic.data_count_bit =
(instance->bit_count > 67) ? 67 : instance->bit_count;
instance->crc = (uint8_t)(instance->decoded_data & 0x07);
@@ -599,7 +598,8 @@ void subghz_protocol_decoder_kia_v5_feed(void* context, bool level, uint32_t dur
bool data_bit;
if(instance->bit_count <= 66 &&
manchester_advance(instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
manchester_advance(
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
kia_v5_add_bit(instance, data_bit);
if(instance->bit_count == 64) {
instance->saved_key = instance->decoded_data;
@@ -627,10 +627,12 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v5_serialize(
furi_assert(context);
SubGhzProtocolDecoderKiaV5* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) {
if(!flipper_format_write_uint32(
flipper_format, "Serial", &instance->generic.serial, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
@@ -641,7 +643,8 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v5_serialize(
}
}
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1)) {
if(!flipper_format_write_uint32(
flipper_format, "Cnt", &instance->generic.cnt, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
@@ -651,21 +654,9 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v5_serialize(
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t raw_high = (uint32_t)(instance->generic.data >> 32);
uint32_t raw_low = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
if(!flipper_format_write_uint32(flipper_format, "DataHi", &raw_high, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "DataLo", &raw_low, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t yek_high = (uint32_t)(instance->yek >> 32);
uint32_t yek_low = (uint32_t)(instance->yek & 0xFFFFFFFF);
uint32_t yek_low = (uint32_t)(instance->yek & 0xFFFFFFFF);
if(!flipper_format_write_uint32(flipper_format, "YekHi", &yek_high, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
@@ -690,11 +681,15 @@ SubGhzProtocolStatus
subghz_protocol_kia_v5_const.min_count_bit_for_found);
if(ret == SubGhzProtocolStatusOk) {
flipper_format_rewind(flipper_format);
uint32_t temp_crc = 0;
if(flipper_format_read_uint32(flipper_format, "CRC", &temp_crc, 1)) {
instance->crc = temp_crc;
instance->crc = (uint8_t)temp_crc;
} else {
instance->crc = 0;
}
flipper_format_rewind(flipper_format);
uint32_t yek_high = 0, yek_low = 0;
if(flipper_format_read_uint32(flipper_format, "YekHi", &yek_high, 1) &&
flipper_format_read_uint32(flipper_format, "YekLo", &yek_low, 1)) {
@@ -714,7 +709,7 @@ SubGhzProtocolStatus
}
instance->generic.serial = (uint32_t)((instance->yek >> 32) & 0x0FFFFFFF);
instance->generic.btn = (uint8_t)((instance->yek >> 60) & 0x0F);
instance->generic.btn = (uint8_t)((instance->yek >> 60) & 0x0F);
uint32_t encrypted = (uint32_t)(instance->yek & 0xFFFFFFFF);
instance->generic.cnt = mixer_decode(encrypted);
@@ -730,11 +725,11 @@ SubGhzProtocolStatus
static const char* subghz_protocol_kia_v5_get_name_button(uint8_t btn) {
switch(btn) {
case 0x01: return "Unlock";
case 0x02: return "Lock";
case 0x04: return "Trunk";
case 0x08: return "Horn";
default: return "Unknown";
case 0x01: return "Unlock";
case 0x02: return "Lock";
case 0x04: return "Trunk";
case 0x08: return "Horn";
default: return "Unknown";
}
}
@@ -742,10 +737,10 @@ void subghz_protocol_decoder_kia_v5_get_string(void* context, FuriString* output
furi_assert(context);
SubGhzProtocolDecoderKiaV5* instance = context;
uint32_t code_found_hi = instance->generic.data >> 32;
uint32_t code_found_lo = instance->generic.data & 0xFFFFFFFF;
uint32_t yek_hi = (uint32_t)(instance->yek >> 32);
uint32_t yek_lo = (uint32_t)(instance->yek & 0xFFFFFFFF);
uint8_t kb[8];
for(int i = 0; i < 8; i++) {
kb[i] = (instance->generic.data >> ((7 - i) * 8)) & 0xFF;
}
uint8_t calculated_crc = kia_v5_calculate_crc(instance->yek);
bool crc_valid = (instance->crc == calculated_crc);
@@ -756,22 +751,18 @@ void subghz_protocol_decoder_kia_v5_get_string(void* context, FuriString* output
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Yek:%08lX%08lX\r\n"
"Key:%02X %02X %02X %02X %02X %02X %02X %02X\r\n"
"Sn:%07lX Cnt:%04lX\r\n"
"Btn:%02X:[%s] Seed:%04X\r\n"
"CRC:%X %s",
"Btn:%02X [%s] Seed:%04X\r\n"
"CRC:%u %s",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_found_hi,
code_found_lo,
yek_hi,
yek_lo,
instance->generic.serial,
instance->generic.cnt,
instance->generic.btn,
kb[0], kb[1], kb[2], kb[3], kb[4], kb[5], kb[6], kb[7],
(unsigned long)instance->generic.serial,
(unsigned long)instance->generic.cnt,
(unsigned)instance->generic.btn,
subghz_protocol_kia_v5_get_name_button(instance->generic.btn),
seed,
instance->crc,
(unsigned)seed,
(unsigned)instance->crc,
crc_valid ? "(OK)" : "(FAIL)");
}
+9 -37
View File
@@ -1,3 +1,4 @@
import re
import os
import subprocess
@@ -79,46 +80,17 @@ def __invoke_git(args, source_dir):
def _proto_ver_generator(target, source, env):
target_file = target[0]
src_dir = source[0].dir.abspath
changelog_file = source[0]
def fetch(unshallow=False):
git_args = ["fetch", "--tags"]
if unshallow:
git_args.append("--unshallow")
with open(str(changelog_file), "rt") as f:
content = f.read()
try:
__invoke_git(git_args, source_dir=src_dir)
except (subprocess.CalledProcessError, EnvironmentError):
# Not great, not terrible
print(fg.boldred("Git: fetch failed"))
def describe():
try:
return __invoke_git(
["describe", "--tags", "--abbrev=0"],
source_dir=src_dir,
)
except (subprocess.CalledProcessError, EnvironmentError):
return None
fetch()
git_describe = describe()
if not git_describe:
fetch(unshallow=True)
git_describe = describe()
if not git_describe:
git_describe = "0.0"
# Strip leading non-numeric characters (e.g. "v" prefix)
import re
version_match = re.match(r"v?(\d+)(?:\.(\d+))?", git_describe)
if version_match:
git_major = version_match.group(1)
git_minor = version_match.group(2) or "0"
else:
match = re.search(r"##\s*\[(\d+)\.(\d+)\]", content)
if not match:
git_major, git_minor = "0", "0"
else:
git_major, git_minor = match.group(1), match.group(2)
version_file_data = (
"#pragma once",
f"#define PROTOBUF_MAJOR_VERSION {git_major}",