diff --git a/applications/external/magspoof/LICENSE b/applications/external/magspoof/LICENSE new file mode 100644 index 000000000..ac4d2d21a --- /dev/null +++ b/applications/external/magspoof/LICENSE @@ -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. diff --git a/applications/external/magspoof/application.fam b/applications/external/magspoof/application.fam new file mode 100644 index 000000000..78359f831 --- /dev/null +++ b/applications/external/magspoof/application.fam @@ -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 +) diff --git a/applications/external/magspoof/helpers/mag_helpers.c b/applications/external/magspoof/helpers/mag_helpers.c new file mode 100644 index 000000000..997cf3862 --- /dev/null +++ b/applications/external/magspoof/helpers/mag_helpers.c @@ -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"); +} diff --git a/applications/external/magspoof/helpers/mag_helpers.h b/applications/external/magspoof/helpers/mag_helpers.h new file mode 100644 index 000000000..a61f143b8 --- /dev/null +++ b/applications/external/magspoof/helpers/mag_helpers.h @@ -0,0 +1,25 @@ +#include "../mag_i.h" +#include +#include + +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); diff --git a/applications/external/magspoof/helpers/mag_text_input.c b/applications/external/magspoof/helpers/mag_text_input.c new file mode 100644 index 000000000..e5daa74a0 --- /dev/null +++ b/applications/external/magspoof/helpers/mag_text_input.c @@ -0,0 +1,583 @@ +#include "mag_text_input.h" +#include +#include +#include + +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); +} diff --git a/applications/external/magspoof/helpers/mag_text_input.h b/applications/external/magspoof/helpers/mag_text_input.h new file mode 100644 index 000000000..1b3d1689a --- /dev/null +++ b/applications/external/magspoof/helpers/mag_text_input.h @@ -0,0 +1,82 @@ +#pragma once + +#include +// #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 diff --git a/applications/external/magspoof/helpers/mag_types.h b/applications/external/magspoof/helpers/mag_types.h new file mode 100644 index 000000000..96b8ee6e6 --- /dev/null +++ b/applications/external/magspoof/helpers/mag_types.h @@ -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; diff --git a/applications/external/magspoof/icons/DolphinMafia_115x62.png b/applications/external/magspoof/icons/DolphinMafia_115x62.png new file mode 100644 index 000000000..66fdb40ff Binary files /dev/null and b/applications/external/magspoof/icons/DolphinMafia_115x62.png differ diff --git a/applications/external/magspoof/icons/DolphinNice_96x59.png b/applications/external/magspoof/icons/DolphinNice_96x59.png new file mode 100644 index 000000000..a299d3630 Binary files /dev/null and b/applications/external/magspoof/icons/DolphinNice_96x59.png differ diff --git a/applications/external/magspoof/icons/KeyBackspaceSelected_16x9.png b/applications/external/magspoof/icons/KeyBackspaceSelected_16x9.png new file mode 100644 index 000000000..7cc0759a8 Binary files /dev/null and b/applications/external/magspoof/icons/KeyBackspaceSelected_16x9.png differ diff --git a/applications/external/magspoof/icons/KeyBackspace_16x9.png b/applications/external/magspoof/icons/KeyBackspace_16x9.png new file mode 100644 index 000000000..9946232d9 Binary files /dev/null and b/applications/external/magspoof/icons/KeyBackspace_16x9.png differ diff --git a/applications/external/magspoof/icons/KeySaveSelected_24x11.png b/applications/external/magspoof/icons/KeySaveSelected_24x11.png new file mode 100644 index 000000000..eeb3569d3 Binary files /dev/null and b/applications/external/magspoof/icons/KeySaveSelected_24x11.png differ diff --git a/applications/external/magspoof/icons/KeySave_24x11.png b/applications/external/magspoof/icons/KeySave_24x11.png new file mode 100644 index 000000000..e7dba987a Binary files /dev/null and b/applications/external/magspoof/icons/KeySave_24x11.png differ diff --git a/applications/external/magspoof/icons/mag_10px.png b/applications/external/magspoof/icons/mag_10px.png new file mode 100644 index 000000000..5e4c15244 Binary files /dev/null and b/applications/external/magspoof/icons/mag_10px.png differ diff --git a/applications/external/magspoof/icons/mag_file_10px.png b/applications/external/magspoof/icons/mag_file_10px.png new file mode 100644 index 000000000..28d683d82 Binary files /dev/null and b/applications/external/magspoof/icons/mag_file_10px.png differ diff --git a/applications/external/magspoof/mag.c b/applications/external/magspoof/mag.c new file mode 100644 index 000000000..ce4a46be8 --- /dev/null +++ b/applications/external/magspoof/mag.c @@ -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); + } +} diff --git a/applications/external/magspoof/mag_device.c b/applications/external/magspoof/mag_device.c new file mode 100644 index 000000000..f3be3a5ff --- /dev/null +++ b/applications/external/magspoof/mag_device.c @@ -0,0 +1,311 @@ +#include "mag_device.h" + +#include +#include + +#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; +} diff --git a/applications/external/magspoof/mag_device.h b/applications/external/magspoof/mag_device.h new file mode 100644 index 000000000..ca6715d99 --- /dev/null +++ b/applications/external/magspoof/mag_device.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include + +#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); diff --git a/applications/external/magspoof/mag_i.h b/applications/external/magspoof/mag_i.h new file mode 100644 index 000000000..fcf9ccd57 --- /dev/null +++ b/applications/external/magspoof/mag_i.h @@ -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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#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); diff --git a/applications/external/magspoof/scenes/mag_scene.c b/applications/external/magspoof/scenes/mag_scene.c new file mode 100644 index 000000000..61d847add --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene.c @@ -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, +}; \ No newline at end of file diff --git a/applications/external/magspoof/scenes/mag_scene.h b/applications/external/magspoof/scenes/mag_scene.h new file mode 100644 index 000000000..0f61112b9 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// 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 diff --git a/applications/external/magspoof/scenes/mag_scene_about.c b/applications/external/magspoof/scenes/mag_scene_about.c new file mode 100644 index 000000000..247c83199 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_about.c @@ -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); +} \ No newline at end of file diff --git a/applications/external/magspoof/scenes/mag_scene_config.h b/applications/external/magspoof/scenes/mag_scene_config.h new file mode 100644 index 000000000..7ab276e53 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_config.h @@ -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) \ No newline at end of file diff --git a/applications/external/magspoof/scenes/mag_scene_delete_confirm.c b/applications/external/magspoof/scenes/mag_scene_delete_confirm.c new file mode 100644 index 000000000..b7349ba70 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_delete_confirm.c @@ -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); +} diff --git a/applications/external/magspoof/scenes/mag_scene_delete_success.c b/applications/external/magspoof/scenes/mag_scene_delete_success.c new file mode 100644 index 000000000..ca7dbbbf7 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_delete_success.c @@ -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); +} \ No newline at end of file diff --git a/applications/external/magspoof/scenes/mag_scene_emulate.c b/applications/external/magspoof/scenes/mag_scene_emulate.c new file mode 100644 index 000000000..e7e8737eb --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_emulate.c @@ -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); +} \ No newline at end of file diff --git a/applications/external/magspoof/scenes/mag_scene_emulate_config.c b/applications/external/magspoof/scenes/mag_scene_emulate_config.c new file mode 100644 index 000000000..437f536a7 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_emulate_config.c @@ -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 +} \ No newline at end of file diff --git a/applications/external/magspoof/scenes/mag_scene_exit_confirm.c b/applications/external/magspoof/scenes/mag_scene_exit_confirm.c new file mode 100644 index 000000000..e26234fb8 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_exit_confirm.c @@ -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); +} diff --git a/applications/external/magspoof/scenes/mag_scene_file_select.c b/applications/external/magspoof/scenes/mag_scene_file_select.c new file mode 100644 index 000000000..b759c4d18 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_file_select.c @@ -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); +} \ No newline at end of file diff --git a/applications/external/magspoof/scenes/mag_scene_input_name.c b/applications/external/magspoof/scenes/mag_scene_input_name.c new file mode 100644 index 000000000..7368b4598 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_input_name.c @@ -0,0 +1,82 @@ +#include +#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); +} diff --git a/applications/external/magspoof/scenes/mag_scene_input_value.c b/applications/external/magspoof/scenes/mag_scene_input_value.c new file mode 100644 index 000000000..1a8ac0904 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_input_value.c @@ -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); +} \ No newline at end of file diff --git a/applications/external/magspoof/scenes/mag_scene_read.c b/applications/external/magspoof/scenes/mag_scene_read.c new file mode 100644 index 000000000..48a223c0f --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_read.c @@ -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); +} \ No newline at end of file diff --git a/applications/external/magspoof/scenes/mag_scene_read.h b/applications/external/magspoof/scenes/mag_scene_read.h new file mode 100644 index 000000000..df8327877 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_read.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#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) diff --git a/applications/external/magspoof/scenes/mag_scene_save_success.c b/applications/external/magspoof/scenes/mag_scene_save_success.c new file mode 100644 index 000000000..fe9b6b4af --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_save_success.c @@ -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); +} \ No newline at end of file diff --git a/applications/external/magspoof/scenes/mag_scene_saved_info.c b/applications/external/magspoof/scenes/mag_scene_saved_info.c new file mode 100644 index 000000000..fd4e808b5 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_saved_info.c @@ -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); +} diff --git a/applications/external/magspoof/scenes/mag_scene_saved_menu.c b/applications/external/magspoof/scenes/mag_scene_saved_menu.c new file mode 100644 index 000000000..f9235cd54 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_saved_menu.c @@ -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); +} diff --git a/applications/external/magspoof/scenes/mag_scene_start.c b/applications/external/magspoof/scenes/mag_scene_start.c new file mode 100644 index 000000000..b048daaba --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_start.c @@ -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); +} diff --git a/applications/external/magspoof/scenes/mag_scene_under_construction.c b/applications/external/magspoof/scenes/mag_scene_under_construction.c new file mode 100644 index 000000000..520f0a792 --- /dev/null +++ b/applications/external/magspoof/scenes/mag_scene_under_construction.c @@ -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); +} \ No newline at end of file