Add magspoof

This commit is contained in:
Willy-JL
2023-08-04 19:25:43 +02:00
parent e83ffb6f58
commit 700b257897
38 changed files with 3188 additions and 0 deletions

21
applications/external/magspoof/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
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.

View File

@@ -0,0 +1,21 @@
App(
appid="magspoof",
name="MagSpoof",
apptype=FlipperAppType.EXTERNAL,
entry_point="mag_app",
requires=[
"gui",
"storage",
"notification",
"dialogs",
],
stack_size=6 * 1024,
order=64, # keep it at the bottom of the list while still WIP
fap_icon="icons/mag_10px.png",
fap_category="GPIO",
fap_icon_assets="icons",
fap_version=(0, 5), # major, minor
fap_description="WIP MagSpoof port using the RFID subsystem",
fap_author="Zachary Weiss",
fap_weburl="https://github.com/hummusec/magspoof_flipper", # Original by zacharyweiss
)

View File

@@ -0,0 +1,480 @@
#include "mag_helpers.h"
#define TAG "MagHelpers"
// Haviv Board - pins gpio_ext_pa7 & gpio_ext_pa6 was swapped.
#define GPIO_PIN_A &gpio_ext_pa7
#define GPIO_PIN_B &gpio_ext_pa6
#define GPIO_PIN_ENABLE &gpio_ext_pa4
#define RFID_PIN_OUT &gpio_rfid_carrier_out
#define ZERO_PREFIX 25 // n zeros prefix
#define ZERO_BETWEEN 53 // n zeros between tracks
#define ZERO_SUFFIX 25 // n zeros suffix
// 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, MagSetting* setting) {
switch(setting->tx) {
case MagTxStateRFID:
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);
furi_hal_gpio_write(RFID_PIN_OUT, value);*/
break;
case MagTxStateGPIO:
furi_hal_gpio_write(GPIO_PIN_A, value);
furi_hal_gpio_write(GPIO_PIN_B, !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(RFID_PIN_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, MagSetting* setting, 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, setting);
furi_delay_us(setting->us_clock);
// if (i % 2 == 1) furi_delay_us(setting->us_interpacket);
}
}
void tx_init_rfid() {
// initialize RFID system for TX
// OTG needed for RFID? Or just legacy from GPIO?
// furi_hal_power_enable_otg();
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 setting
// 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(RFID_PIN_OUT, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_delay_ms(300);
}
void tx_deinit_rfid() {
// reset RFID system
furi_hal_gpio_write(RFID_PIN_OUT, 0);
furi_hal_rfid_pins_reset();
furi_hal_power_disable_otg();
}
void tx_init_rf(int hz) {
// presets and frequency will need some experimenting
furi_hal_subghz_reset();
// furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async);
// furi_hal_subghz_load_preset(FuriHalSubGhzPresetGFSK9_99KbAsync);
// furi_hal_subghz_load_preset(FuriHalSubGhzPresetMSK99_97KbAsync);
// furi_hal_subghz_load_preset(FuriHalSubGhzPreset2FSKDev238Async);
// furi_hal_subghz_load_preset(FuriHalSubGhzPreset2FSKDev476Async);
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(MagSetting* setting) {
// Initialize configured TX method
switch(setting->tx) {
case MagTxStateRFID:
tx_init_rfid();
break;
case MagTxStateGPIO:
furi_hal_power_enable_otg();
// gpio_item_configure_all_pins(GpioModeOutputPushPull);
furi_hal_gpio_init(GPIO_PIN_A, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(GPIO_PIN_B, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(GPIO_PIN_ENABLE, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(GPIO_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(MagSetting* setting) {
// Reset configured TX method
switch(setting->tx) {
case MagTxStateRFID:
tx_deinit_rfid();
break;
case MagTxStateGPIO:
furi_hal_gpio_write(GPIO_PIN_A, 0);
furi_hal_gpio_write(GPIO_PIN_B, 0);
furi_hal_gpio_write(GPIO_PIN_ENABLE, 0);
// set back to analog output mode?
//gpio_item_configure_all_pins(GpioModeAnalog);
furi_hal_power_disable_otg();
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) {
MagSetting* setting = mag->setting;
// 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(setting)) return;
FURI_CRITICAL_ENTER();
for(uint16_t i = 0; i < (ZERO_PREFIX * 2); i++) {
// is this right?
if(!!(i % 2)) bit ^= 1;
play_halfbit(bit, setting);
furi_delay_us(setting->us_clock);
}
if((setting->track == MagTrackStateOneAndTwo) || (setting->track == MagTrackStateOne))
play_track((uint8_t*)bits_t1_manchester, bits_t1_count, setting, false);
if((setting->track == MagTrackStateOneAndTwo))
for(uint16_t i = 0; i < (ZERO_BETWEEN * 2); i++) {
if(!!(i % 2)) bit ^= 1;
play_halfbit(bit, setting);
furi_delay_us(setting->us_clock);
}
if((setting->track == MagTrackStateOneAndTwo) || (setting->track == MagTrackStateTwo))
play_track(
(uint8_t*)bits_t2_manchester,
bits_t2_count,
setting,
(setting->reverse == MagReverseStateOn));
if((setting->track == MagTrackStateThree))
play_track((uint8_t*)bits_t3_manchester, bits_t3_count, setting, false);
for(uint16_t i = 0; i < (ZERO_SUFFIX * 2); i++) {
if(!!(i % 2)) bit ^= 1;
play_halfbit(bit, setting);
furi_delay_us(setting->us_clock);
}
FURI_CRITICAL_EXIT();
free(data1);
free(data2);
free(data3);
tx_deinit(setting);
}
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");
}

View File

@@ -0,0 +1,25 @@
#include "../mag_i.h"
#include <stdio.h>
#include <string.h>
void play_halfbit(bool value, MagSetting* setting);
void play_track(uint8_t* bits_manchester, uint16_t n_bits, MagSetting* setting, bool reverse);
void tx_init_rf(int hz);
void tx_init_rfid();
void tx_init_piezo();
bool tx_init(MagSetting* setting);
void tx_deinit_piezo();
void tx_deinit_rfid();
bool tx_deinit(MagSetting* setting);
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);

View File

@@ -0,0 +1,583 @@
#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);
}

View File

@@ -0,0 +1,82 @@
#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

View File

@@ -0,0 +1,44 @@
#pragma once
#define MAG_VERSION_APP "0.05"
#define MAG_DEVELOPER "Zachary Weiss"
#define MAG_GITHUB "github.com/zacharyweiss/magspoof_flipper"
typedef enum {
MagViewSubmenu,
MagViewDialogEx,
MagViewPopup,
MagViewLoading,
MagViewWidget,
MagViewVariableItemList,
MagViewTextInput,
MagViewMagTextInput,
} MagView;
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 {
UART_TerminalEventRefreshConsoleOutput = 0,
UART_TerminalEventStartConsole,
UART_TerminalEventStartKeyboard,
} UART_TerminalCustomEvent;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

244
applications/external/magspoof/mag.c vendored Normal file
View File

@@ -0,0 +1,244 @@
#include "mag_i.h"
#define TAG "Mag"
#define SETTING_DEFAULT_REVERSE MagReverseStateOff
#define SETTING_DEFAULT_TRACK MagTrackStateOneAndTwo
#define SETTING_DEFAULT_TX_RFID MagTxStateGPIO
#define SETTING_DEFAULT_US_CLOCK 240
#define SETTING_DEFAULT_US_INTERPACKET 10
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 MagSetting* mag_setting_alloc() {
// temp hardcoded defaults
MagSetting* setting = malloc(sizeof(MagSetting));
setting->reverse = SETTING_DEFAULT_REVERSE;
setting->track = SETTING_DEFAULT_TRACK;
setting->tx = SETTING_DEFAULT_TX_RFID;
setting->us_clock = SETTING_DEFAULT_US_CLOCK;
setting->us_interpacket = SETTING_DEFAULT_US_INTERPACKET;
return setting;
}
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->view_dispatcher = view_dispatcher_alloc();
mag->scene_manager = scene_manager_alloc(&mag_scene_handlers, mag);
view_dispatcher_enable_queue(mag->view_dispatcher);
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->setting = mag_setting_alloc();
// 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));
// Dialog
mag->dialog_ex = dialog_ex_alloc();
view_dispatcher_add_view(
mag->view_dispatcher, MagViewDialogEx, dialog_ex_get_view(mag->dialog_ex));
// 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, MagViewMagTextInput, mag_text_input_get_view(mag->mag_text_input));
return mag;
}
static void mag_setting_free(MagSetting* setting) {
furi_assert(setting);
free(setting);
}
static void mag_free(Mag* mag) {
furi_assert(mag);
furi_string_free(mag->file_name);
furi_string_free(mag->file_path);
// Mag device
mag_device_free(mag->mag_dev);
mag->mag_dev = NULL;
// Mag setting
mag_setting_free(mag->setting);
mag->setting = NULL;
// Submenu
view_dispatcher_remove_view(mag->view_dispatcher, MagViewSubmenu);
submenu_free(mag->submenu);
// DialogEx
view_dispatcher_remove_view(mag->view_dispatcher, MagViewDialogEx);
dialog_ex_free(mag->dialog_ex);
// 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);
// Custom Mag TextInput
view_dispatcher_remove_view(mag->view_dispatcher, MagViewMagTextInput);
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;
furi_record_close(RECORD_STORAGE);
furi_record_close(RECORD_DIALOGS);
free(mag);
}
// entry point for app
int32_t mag_app(void* p) {
Mag* mag = mag_alloc();
UNUSED(p);
mag_make_app_folder(mag);
view_dispatcher_attach_to_gui(mag->view_dispatcher, mag->gui, ViewDispatcherTypeFullscreen);
scene_manager_next_scene(mag->scene_manager, MagSceneStart);
view_dispatcher_run(mag->view_dispatcher);
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_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;
TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
if(show) {
// Raise timer priority so that animations can play
vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewLoading);
} else {
// Restore default timer priority
vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
}
}

View File

@@ -0,0 +1,311 @@
#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);
}
static bool mag_device_load_data(MagDevice* mag_dev, FuriString* path, bool show_dialog) {
bool parsed = false;
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);
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) {
FuriString* filename;
filename = furi_string_alloc();
path_extract_filename(mag_dev->load_path, filename, true);
strncpy(mag_dev->dev_name, furi_string_get_cstr(filename), MAG_DEV_NAME_MAX_LEN);
res = mag_device_load_data(mag_dev, mag_dev->load_path, true);
if(res) {
mag_device_set_name(mag_dev, mag_dev->dev_name);
}
furi_string_free(filename);
}
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;
}
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;
}

View File

@@ -0,0 +1,58 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include "magspoof_icons.h"
#define MAG_DEV_NAME_MAX_LEN 22
#define MAG_DEV_TRACKS 3
#define MAG_APP_FOLDER ANY_PATH("mag")
#define MAG_APP_EXTENSION ".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_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);
void mag_device_set_loading_callback(
MagDevice* mag_dev,
MagLoadingCallback callback,
void* context);

105
applications/external/magspoof/mag_i.h vendored Normal file
View File

@@ -0,0 +1,105 @@
#pragma once
#include "mag_device.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 <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
enum MagCustomEvent {
MagEventNext = 100,
MagEventExit,
MagEventPopupClosed,
};
typedef struct {
MagTxState tx;
MagTrackState track;
MagReverseState reverse;
uint32_t us_clock;
uint32_t us_interpacket;
} MagSetting;
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;
MagSetting* setting;
// Common views
Submenu* submenu;
DialogEx* dialog_ex;
Popup* popup;
Loading* loading;
TextInput* text_input;
Widget* widget;
VariableItemList* variable_item_list;
// Custom views
Mag_TextInput* mag_text_input;
// UART
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);
char uart_text_input_store[UART_TERMINAL_TEXT_INPUT_STORE_SIZE + 1];
FuriString* uart_text_box_store;
size_t uart_text_box_store_strlen;
// UART_TextInput* text_input;
} 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_popup_timeout_callback(void* context);
void mag_widget_callback(GuiButtonType result, InputType type, void* context);
void mag_text_input_callback(void* context);

View File

@@ -0,0 +1,30 @@
#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,
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) 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

View File

@@ -0,0 +1,40 @@
#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);
}

View File

@@ -0,0 +1,15 @@
ADD_SCENE(mag, start, Start)
ADD_SCENE(mag, about, About)
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)
ADD_SCENE(mag, read, Read)

View File

@@ -0,0 +1,49 @@
#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);
}

View File

@@ -0,0 +1,39 @@
#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);
}

View File

@@ -0,0 +1,93 @@
#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->setting->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->setting->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);
}

View File

@@ -0,0 +1,264 @@
#include "../mag_i.h"
#define TAG "MagSceneEmulateConfig"
enum MagSettingIndex {
MagSettingIndexTx,
MagSettingIndexTrack,
MagSettingIndexReverse,
MagSettingIndexClock,
MagSettingIndexInterpacket,
};
#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->setting->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->setting->reverse == MagReverseStateOff) {
variable_item_set_current_value_text(item, track_text[index]);
mag->setting->track = track_value[index];
} else if(mag->setting->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->setting->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->setting->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_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->setting->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->setting->us_interpacket = interpacket_value[index];
};
void mag_scene_emulate_config_on_enter(void* context) {
// TODO: retrieve current values from struct, rather than setting to default on setup
Mag* mag = context;
VariableItem* item;
uint8_t value_index;
// TX
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->setting->tx, tx_value, TX_COUNT);
scene_manager_set_scene_state(mag->scene_manager, MagSceneEmulateConfig, (uint32_t)item);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, tx_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->setting->track, track_value, TRACK_COUNT);
scene_manager_set_scene_state(mag->scene_manager, MagSceneEmulateConfig, (uint32_t)item);
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->setting->reverse, reverse_value, REVERSE_COUNT);
scene_manager_set_scene_state(mag->scene_manager, MagSceneEmulateConfig, (uint32_t)item);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, reverse_text[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->setting->us_clock, clock_value, CLOCK_COUNT);
scene_manager_set_scene_state(mag->scene_manager, MagSceneEmulateConfig, (uint32_t)item);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, clock_text[value_index]);
// 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);
scene_manager_set_scene_state(mag->scene_manager, MagSceneEmulateConfig, (uint32_t)item);
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);
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);
// mag_last_settings_save?
// scene_manager_set_scene_state? Using subghz_scene_reciever_config as framework/inspo
}

View File

@@ -0,0 +1,20 @@
#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);
}

View File

@@ -0,0 +1,24 @@
#include "../mag_i.h"
#include "../mag_device.h"
void mag_scene_file_select_on_enter(void* context) {
Mag* mag = context;
//UNUSED(mag);
mag_device_set_loading_callback(mag->mag_dev, mag_show_loading_popup, mag);
if(mag_file_select(mag->mag_dev)) {
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);
}

View File

@@ -0,0 +1,82 @@
#include <lib/toolbox/random_name.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);
set_random_name(mag->text_store, MAG_TEXT_STORE_SIZE);
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);
}

View File

@@ -0,0 +1,37 @@
#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, MagViewMagTextInput);
}
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);
}

View File

@@ -0,0 +1,185 @@
// Creator: Hummus@FlipperGang
#include "../mag_i.h"
#include "../helpers/mag_helpers.h"
#include "mag_scene_read.h"
#define TAG "MagSceneRead"
void uart_callback(UartIrqEvent event, uint8_t data, void* context) {
Mag* mag = context;
if(event == UartIrqEventRXNE) {
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
// furi_hal_console_disable();
furi_hal_uart_deinit(FuriHalUartIdUSART1);
furi_hal_uart_init(FuriHalUartIdUSART1, 9600);
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_callback, mag);
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_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);
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, NULL, NULL);
furi_hal_uart_deinit(FuriHalUartIdUSART1);
// furi_hal_console_enable();
notification_message(mag->notifications, &sequence_blink_stop);
}

View File

@@ -0,0 +1,20 @@
#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 (FuriHalUartIdUSART1)
#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)

View File

@@ -0,0 +1,43 @@
#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);
}

View File

@@ -0,0 +1,50 @@
#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);
}

View File

@@ -0,0 +1,83 @@
#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;
notification_message(mag->notifications, &sequence_blink_cyan_10);
// messy code to quickly check which tracks are available for emulation/display
// there's likely a better spot to do this, but the MagDevice functions don't have access to the full mag struct...
bool is_empty_t1 = furi_string_empty(mag->mag_dev->dev_data.track[0].str);
bool is_empty_t2 = furi_string_empty(mag->mag_dev->dev_data.track[1].str);
bool is_empty_t3 = furi_string_empty(mag->mag_dev->dev_data.track[2].str);
if(!is_empty_t1 && !is_empty_t2) {
mag->setting->track = MagTrackStateOneAndTwo;
} else if(!is_empty_t1) {
mag->setting->track = MagTrackStateOne;
} else if(!is_empty_t2) {
mag->setting->track = MagTrackStateTwo;
} else if(!is_empty_t3) {
mag->setting->track = MagTrackStateThree;
} // TODO: what happens if no track data present?
submenu_add_item(
submenu, "Emulate (WIP)", 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);
}

View File

@@ -0,0 +1,71 @@
#include "../mag_i.h"
typedef enum {
SubmenuIndexSaved,
SubmenuIndexRead,
//SubmenuIndexAddManually,
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_item(submenu, "Read", SubmenuIndexRead, mag_scene_start_submenu_callback, mag);
//submenu_add_item(
// submenu, "Add Manually", SubmenuIndexAddManually, 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 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);
}

View File

@@ -0,0 +1,40 @@
#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);
}