diff --git a/gps_nmea/README.md b/gps_nmea/README.md index 6df52f2dfc9..9cdc619e14d 100644 --- a/gps_nmea/README.md +++ b/gps_nmea/README.md @@ -1,9 +1,21 @@ # GPS for Flipper Zero -A simple Flipper Zero application for NMEA 0183 serial GPS modules, such as the -- Adafruit Ultimate GPS Breakout. +[![FAP Build](https://github.com/ezod/flipperzero-gps/actions/workflows/build.yml/badge.svg)](https://github.com/ezod/flipperzero-gps/actions/workflows/build.yml) -Heavy lifting (NMEA parsing) provided by minmea. +A simple Flipper Zero application for NMEA 0183 serial GPS modules. + +![ui](ui.png) + +Heavy lifting (NMEA parsing) provided by [minmea]. + +## Installation + +1. Navigate to the [FAP Build](https://github.com/ezod/flipperzero-gps/actions/workflows/build.yml) + GitHub action workflow, and select the most recent run. +2. The FAP is built for both the `dev` and `release` channels of the official + firmware. Download the artifact corresponding to your firmware version. +3. Extract `gps_nmea.fap` from the ZIP file to `apps/GPIO` on your Flipper + Zero SD card. ## Usage @@ -11,7 +23,7 @@ This is a single-screen app, and a few interactions are provided via the hardware buttons: - Long press the up button to change the **baud rate**. The default baud rate - is 9600, but 19200, 38400, 57600, and 115200 baud are also supported. + is 9600, but 4800, 19200, 38400, 57600, and 115200 baud are also supported. - Long press the right button to change **speed units** from knots to kilometers per hour. - Press the OK button to set the **backlight** to always on mode. Press it @@ -23,14 +35,15 @@ hardware buttons: Connect the GPS module to power and the USART using GPIO pins 9 (3.3V), 11 (GND), 13 (TX), and 14 (RX), as appropriate. +![wiring](wiring.png) -See the tutorial video - https://www.youtube.com/watch?v=5vSGFzEBp-k from -Lab401 by RocketGod - https://github.com/RocketGod-git for a visual guide to +See the [tutorial video](https://www.youtube.com/watch?v=5vSGFzEBp-k) from +Lab401 by [RocketGod](https://github.com/RocketGod-git) for a visual guide to the hardware setup. -## Confirmed Compatible Modules +### Confirmed Compatible Modules -* Adafruit Ultimate GPS Breakout +* [Adafruit Ultimate GPS Breakout] * ATGM336H * Beitian BN-180 * Beitian BN-220 @@ -42,17 +55,26 @@ the hardware setup. * Beitian BE-280 * Beitian BN-280ZF * Beitian BN-357ZF +* Fastrax UP500 +* [mRo GPS u-Blox Neo-M8N] * Royaltek RBT-2100LP -* u-blox NEO-6M -* u-blox NEO-7M -* Uputronics u-blox MAX-M8C Pico +* [u-Blox NEO-6M] +* [u-Blox NEO-7M] +* [Uputronics u-blox MAX-M8C Pico] If you have verified this application working with a module not listed here, please submit a PR adding it to the list. -## Links +## Building + +This application can be compiled using [uFBT]. Run `ufbt` in the root directory +of the repository. -Original repo link - https://github.com/ezod/flipperzero-gps -Adafruit Ultimate GPS Breakout: https://www.adafruit.com/product/746 -minmea: https://github.com/kosma/minmea -u-blox NEO-6M: https://www.u-blox.com/en/product/neo-6-series +[Adafruit Ultimate GPS Breakout]: https://www.adafruit.com/product/746 +[minmea]: https://github.com/kosma/minmea +[mRo GPS u-Blox Neo-M8N]: https://store.mrobotics.io/product-p/m10034-solo.htm +[qFlipper]: https://flipperzero.one/update +[u-Blox NEO-6M]: https://www.u-blox.com/en/product/neo-6-series +[u-Blox NEO-7M]: https://www.u-blox.com/en/product/neo-7-series +[uFBT]: https://github.com/flipperdevices/flipperzero-ufbt +[Uputronics u-blox MAX-M8C Pico]: https://store.uputronics.com/index.php?route=product/product&product_id=72 diff --git a/gps_nmea/gps.c b/gps_nmea/gps.c index 0c17412962e..85a4157db41 100644 --- a/gps_nmea/gps.c +++ b/gps_nmea/gps.c @@ -2,6 +2,7 @@ #include "constants.h" #include +#include #include #include #include @@ -38,7 +39,7 @@ static void render_callback(Canvas* const canvas, void* context) { 32, AlignCenter, AlignBottom, - gps_uart->backlight_enabled ? "Backlight enabled" : "Backlight disabled"); + gps_uart->backlight_on ? "Backlight enabled" : "Backlight disabled"); break; case CHANGE_DEEPSLEEP: canvas_set_font(canvas, FontPrimary); @@ -182,16 +183,16 @@ int32_t gps_app(void* p) { processing = false; break; case InputKeyOk: - if(!gps_uart->backlight_enabled) { + if(!gps_uart->backlight_on) { notification_message_block( gps_uart->notifications, &sequence_display_backlight_enforce_on); - gps_uart->backlight_enabled = true; + gps_uart->backlight_on = true; } else { notification_message_block( gps_uart->notifications, &sequence_display_backlight_enforce_auto); notification_message( gps_uart->notifications, &sequence_display_backlight_off); - gps_uart->backlight_enabled = false; + gps_uart->backlight_on = false; } gps_uart->view_state = CHANGE_BACKLIGHT; @@ -217,7 +218,6 @@ int32_t gps_app(void* p) { gps_uart_init_thread(gps_uart); gps_uart->view_state = CHANGE_BAUDRATE; - furi_mutex_release(gps_uart->mutex); view_port_update(view_port); furi_delay_ms(1000); @@ -259,7 +259,6 @@ int32_t gps_app(void* p) { } } } - if(gps_uart->view_state == NORMAL) { furi_mutex_release(gps_uart->mutex); view_port_update(view_port); diff --git a/gps_nmea/gps_uart.c b/gps_nmea/gps_uart.c index a8c4821850a..fbeba1472cf 100644 --- a/gps_nmea/gps_uart.c +++ b/gps_nmea/gps_uart.c @@ -11,10 +11,10 @@ typedef enum { #define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) static void - gps_uart_on_irq_cb(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { + gps_uart_on_irq_cb(FuriHalSerialHandle* handle, FuriHalSerialRxEvent ev, void* context) { GpsUart* gps_uart = (GpsUart*)context; - if(event == FuriHalSerialRxEventData) { + if(ev == FuriHalSerialRxEventData) { uint8_t data = furi_hal_serial_async_rx(handle); furi_stream_buffer_send(gps_uart->rx_stream, &data, 1, 0); furi_thread_flags_set(furi_thread_get_id(gps_uart->thread), WorkerEvtRxDone); @@ -22,6 +22,8 @@ static void } static void gps_uart_serial_init(GpsUart* gps_uart) { + furi_assert(!gps_uart->serial_handle); + gps_uart->serial_handle = furi_hal_serial_control_acquire(UART_CH); furi_check(gps_uart->serial_handle); furi_hal_serial_init(gps_uart->serial_handle, gps_uart->baudrate); @@ -32,10 +34,11 @@ static void gps_uart_serial_init(GpsUart* gps_uart) { } static void gps_uart_serial_deinit(GpsUart* gps_uart) { - UNUSED(gps_uart); + furi_assert(gps_uart->serial_handle); furi_hal_serial_async_rx_stop(gps_uart->serial_handle); furi_hal_serial_deinit(gps_uart->serial_handle); furi_hal_serial_control_release(gps_uart->serial_handle); + gps_uart->serial_handle = NULL; } static void gps_uart_parse_nmea(GpsUart* gps_uart, char* line) { @@ -209,8 +212,8 @@ GpsUart* gps_uart_enable() { gps_uart->notifications = furi_record_open(RECORD_NOTIFICATION); gps_uart->baudrate = gps_baudrates[current_gps_baudrate]; + gps_uart->backlight_on = false; gps_uart->speed_units = KNOTS; - gps_uart->backlight_enabled = false; gps_uart->deep_sleep_enabled = false; gps_uart->view_state = NORMAL; diff --git a/gps_nmea/gps_uart.h b/gps_nmea/gps_uart.h index 6bc1cec32b5..92b0c7fca38 100644 --- a/gps_nmea/gps_uart.h +++ b/gps_nmea/gps_uart.h @@ -5,9 +5,8 @@ #include -#define UART_CH (momentum_settings.uart_nmea_channel) - #define RX_BUF_SIZE 1024 +#define UART_CH (momentum_settings.uart_nmea_channel) static const int gps_baudrates[6] = {4800, 9600, 19200, 38400, 57600, 115200}; static int current_gps_baudrate = 1; @@ -42,15 +41,16 @@ typedef struct { FuriThread* thread; FuriStreamBuffer* rx_stream; uint8_t rx_buf[RX_BUF_SIZE]; - FuriHalSerialHandle* serial_handle; NotificationApp* notifications; uint32_t baudrate; - bool backlight_enabled; + bool backlight_on; bool deep_sleep_enabled; SpeedUnit speed_units; ViewState view_state; + FuriHalSerialHandle* serial_handle; + GpsStatus status; } GpsUart; diff --git a/magspoof/.github/ISSUE_TEMPLATE/bug_report.md b/magspoof/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..1ff2aafd040 --- /dev/null +++ b/magspoof/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Select '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Versions & Hardware** + - Firmware: [e.g. mntm-001, unlshd-072 , official 0.100.3] + - MagSpoof: [e.g. Version 0.05, or commit {HASH}] + - GPIO Module: [e.g. Rabbit-Labs Multi Pass] + - Magstripe Reader: [e.g. MSR-90] + +**Additional context** +Add any other context about the problem here. diff --git a/magspoof/.github/ISSUE_TEMPLATE/feature_request.md b/magspoof/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..11fc491ef1d --- /dev/null +++ b/magspoof/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/magspoof/README.md b/magspoof/README.md index fbbf3bdc952..b78e0874a12 100644 --- a/magspoof/README.md +++ b/magspoof/README.md @@ -1,5 +1,5 @@ # magspoof_flipper -WIP of MagSpoof for the Flipper Zero. Basic TX of saved files confirmed working against an MSR90 with an external H-bridge module mirroring Samy Kamkar's design. Sample files with test data are included in `assets` for anyone wishing to experiment. +WIP of MagSpoof for the Flipper Zero. Basic TX of saved files confirmed working against an MSR90 with an external H-bridge module mirroring Samy Kamkar's design. Sample files are included in `resources`. RFID coil output weaker; able to be picked up/detected by more compact mag readers such as Square, but yet to have success with it being decoded/parsed properly. Additional investigation was made into alternate internal TX options (CC1101, ST25R3916, piezo); tentatively, RFID coil + speaker (`LF + P` config setting) results in the strongest internal TX tested to date but still weaker than a dedicated external module or an actual card swipe (and sounds like a dial-up modem from hell). For information on the state of internal TX &/or misc TODOs, known bugs, etc, confer `NOTES.md`. diff --git a/magspoof/application.fam b/magspoof/application.fam index 6044ec4e343..be70ced6b46 100644 --- a/magspoof/application.fam +++ b/magspoof/application.fam @@ -18,8 +18,8 @@ App( fap_category="GPIO", fap_icon_assets="icons", fap_icon_assets_symbol="mag", - fap_version=(0, 5), # major, minor - fap_description="WIP MagSpoof port using the RFID subsystem", + fap_version=(0, 7), # major, minor + fap_description="Enables wireless transmission of magstripe data", fap_author="Zachary Weiss", fap_weburl="https://github.com/zacharyweiss/magspoof_flipper", ) diff --git a/magspoof/helpers/mag_helpers.c b/magspoof/helpers/mag_helpers.c index a297775359a..83bf6c322dc 100644 --- a/magspoof/helpers/mag_helpers.c +++ b/magspoof/helpers/mag_helpers.c @@ -2,12 +2,6 @@ #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 @@ -19,18 +13,18 @@ const int sublen[] = {32, 48, 48}; uint8_t last_value = 2; -void play_halfbit(bool value, MagSetting* setting) { - switch(setting->tx) { +void play_halfbit(bool value, MagState* state) { + switch(state->tx) { case MagTxStateRFID: - furi_hal_gpio_write(RFID_PIN_OUT, value); + furi_hal_gpio_write(&gpio_rfid_carrier_out, value); /*furi_hal_gpio_write(RFID_PIN_OUT, !value); furi_hal_gpio_write(RFID_PIN_OUT, value); furi_hal_gpio_write(RFID_PIN_OUT, !value); furi_hal_gpio_write(RFID_PIN_OUT, value);*/ break; case MagTxStateGPIO: - furi_hal_gpio_write(GPIO_PIN_A, value); - furi_hal_gpio_write(GPIO_PIN_B, !value); + furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_input), value); + furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_output), !value); break; case MagTxStatePiezo: furi_hal_gpio_write(&gpio_speaker, value); @@ -41,7 +35,7 @@ void play_halfbit(bool value, MagSetting* setting) { break; case MagTxStateLF_P: - furi_hal_gpio_write(RFID_PIN_OUT, value); + furi_hal_gpio_write(&gpio_rfid_carrier_out, value); furi_hal_gpio_write(&gpio_speaker, value); /* // Weaker but cleaner signal @@ -69,7 +63,7 @@ void play_halfbit(bool value, MagSetting* setting) { if(last_value == 2 || value != (bool)last_value) { //furi_hal_nfc_ll_txrx_on(); - //furi_delay_us(64); + furi_delay_us(64); //furi_hal_nfc_ll_txrx_off(); } break; @@ -88,7 +82,7 @@ void play_halfbit(bool value, MagSetting* setting) { last_value = value; } -void play_track(uint8_t* bits_manchester, uint16_t n_bits, MagSetting* setting, bool reverse) { +void play_track(uint8_t* bits_manchester, uint16_t n_bits, MagState* state, bool reverse) { for(uint16_t i = 0; i < n_bits; i++) { uint16_t j = (reverse) ? (n_bits - i - 1) : i; uint8_t byte = j / 8; @@ -117,9 +111,9 @@ void play_track(uint8_t* bits_manchester, uint16_t n_bits, MagSetting* setting, // 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); + play_halfbit(bit, state); + furi_delay_us(state->us_clock); + // if (i % 2 == 1) furi_delay_us(state->us_interpacket); } } @@ -131,7 +125,7 @@ void tx_init_rfid() { // 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 + // Initializing at GpioSpeedLow seems sufficient for our needs; no improvements seen by increasing speed state // this doesn't seem to make a difference, leaving it in furi_hal_gpio_init(&gpio_rfid_data_in, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); @@ -143,14 +137,14 @@ void tx_init_rfid() { 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_hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); furi_delay_ms(300); } void tx_deinit_rfid() { // reset RFID system - furi_hal_gpio_write(RFID_PIN_OUT, 0); + furi_hal_gpio_write(&gpio_rfid_carrier_out, 0); furi_hal_rfid_pins_reset(); } @@ -179,19 +173,31 @@ void tx_deinit_piezo() { furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } -bool tx_init(MagSetting* setting) { +bool tx_init(MagState* state) { // Initialize configured TX method - switch(setting->tx) { + switch(state->tx) { case MagTxStateRFID: tx_init_rfid(); break; case MagTxStateGPIO: // 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); + furi_hal_gpio_init( + mag_state_enum_to_pin(state->pin_input), + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedLow); + furi_hal_gpio_init( + mag_state_enum_to_pin(state->pin_output), + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedLow); + furi_hal_gpio_init( + mag_state_enum_to_pin(state->pin_enable), + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedLow); + + furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_enable), 1); // had some issues with ~300; bumped higher temporarily furi_delay_ms(500); @@ -219,21 +225,24 @@ bool tx_init(MagSetting* setting) { return true; } -bool tx_deinit(MagSetting* setting) { +bool tx_deinit(MagState* state) { // Reset configured TX method - switch(setting->tx) { + switch(state->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); + furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_input), 0); + furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_output), 0); + furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_enable), 0); // set back to analog output mode? - YES - furi_hal_gpio_init(GPIO_PIN_A, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(GPIO_PIN_B, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(GPIO_PIN_ENABLE, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init( + mag_state_enum_to_pin(state->pin_input), GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init( + mag_state_enum_to_pin(state->pin_output), GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init( + mag_state_enum_to_pin(state->pin_enable), GpioModeAnalog, GpioPullNo, GpioSpeedLow); //gpio_item_configure_all_pins(GpioModeAnalog); break; @@ -262,7 +271,7 @@ bool tx_deinit(MagSetting* setting) { } void mag_spoof(Mag* mag) { - MagSetting* setting = mag->setting; + MagState* state = &mag->state; // TODO: cleanup this section. Possibly move precompute + tx_init to emulate_on_enter? FuriString* ft1 = mag->mag_dev->dev_data.track[0].str; @@ -320,47 +329,47 @@ void mag_spoof(Mag* mag) { last_value = 2; bool bit = false; - if(!tx_init(setting)) return; + if(!tx_init(state)) 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); + play_halfbit(bit, state); + furi_delay_us(state->us_clock); } - if((setting->track == MagTrackStateOneAndTwo) || (setting->track == MagTrackStateOne)) - play_track((uint8_t*)bits_t1_manchester, bits_t1_count, setting, false); + if((state->track == MagTrackStateOneAndTwo) || (state->track == MagTrackStateOne)) + play_track((uint8_t*)bits_t1_manchester, bits_t1_count, state, false); - if((setting->track == MagTrackStateOneAndTwo)) + if((state->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); + play_halfbit(bit, state); + furi_delay_us(state->us_clock); } - if((setting->track == MagTrackStateOneAndTwo) || (setting->track == MagTrackStateTwo)) + if((state->track == MagTrackStateOneAndTwo) || (state->track == MagTrackStateTwo)) play_track( (uint8_t*)bits_t2_manchester, bits_t2_count, - setting, - (setting->reverse == MagReverseStateOn)); + state, + (state->reverse == MagReverseStateOn)); - if((setting->track == MagTrackStateThree)) - play_track((uint8_t*)bits_t3_manchester, bits_t3_count, setting, false); + if((state->track == MagTrackStateThree)) + play_track((uint8_t*)bits_t3_manchester, bits_t3_count, state, false); for(uint16_t i = 0; i < (ZERO_SUFFIX * 2); i++) { if(!!(i % 2)) bit ^= 1; - play_halfbit(bit, setting); - furi_delay_us(setting->us_clock); + play_halfbit(bit, state); + furi_delay_us(state->us_clock); } FURI_CRITICAL_EXIT(); free(data1); free(data2); free(data3); - tx_deinit(setting); + tx_deinit(state); } uint16_t add_bit(bool value, uint8_t* out, uint16_t count) { diff --git a/magspoof/helpers/mag_helpers.h b/magspoof/helpers/mag_helpers.h index a61f143b85e..a6d3723fe70 100644 --- a/magspoof/helpers/mag_helpers.h +++ b/magspoof/helpers/mag_helpers.h @@ -2,16 +2,16 @@ #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 play_halfbit(bool value, MagState* state); +void play_track(uint8_t* bits_manchester, uint16_t n_bits, MagState* state, bool reverse); void tx_init_rf(int hz); void tx_init_rfid(); void tx_init_piezo(); -bool tx_init(MagSetting* setting); +bool tx_init(MagState* state); void tx_deinit_piezo(); void tx_deinit_rfid(); -bool tx_deinit(MagSetting* setting); +bool tx_deinit(MagState* state); uint16_t add_bit(bool value, uint8_t* out, uint16_t count); uint16_t add_bit_manchester(bool value, uint8_t* out, uint16_t count); diff --git a/magspoof/helpers/mag_types.h b/magspoof/helpers/mag_types.h index 39cad5ec434..898f876f82c 100644 --- a/magspoof/helpers/mag_types.h +++ b/magspoof/helpers/mag_types.h @@ -1,19 +1,9 @@ #pragma once -#define MAG_VERSION_APP "0.06" +#define MAG_VERSION_APP FAP_VERSION #define MAG_DEVELOPER "Zachary Weiss" #define MAG_GITHUB "github.com/zacharyweiss/magspoof_flipper" -typedef enum { - MagViewSubmenu, - MagViewDialogEx, - MagViewPopup, - MagViewLoading, - MagViewWidget, - MagViewVariableItemList, - MagViewTextInput, -} MagView; - typedef enum { MagReverseStateOff, MagReverseStateOn, @@ -36,6 +26,37 @@ typedef enum { MagTxCC1101_868, } MagTxState; +typedef enum { + MagPinA7, + MagPinA6, + MagPinA4, + MagPinB3, + MagPinB2, + MagPinC3, + MagPinC1, + MagPinC0, +} MagPin; + +#define MAG_STATE_DEFAULT_REVERSE MagReverseStateOff +#define MAG_STATE_DEFAULT_TRACK MagTrackStateOneAndTwo +#define MAG_STATE_DEFAULT_TX MagTxStateGPIO +#define MAG_STATE_DEFAULT_US_CLOCK 240 +#define MAG_STATE_DEFAULT_US_INTERPACKET 10 +#define MAG_STATE_DEFAULT_PIN_INPUT MagPinA7 +#define MAG_STATE_DEFAULT_PIN_OUTPUT MagPinA6 +#define MAG_STATE_DEFAULT_PIN_ENABLE MagPinA4 +#define MAG_STATE_DEFAULT_ALLOW_UART false + +typedef enum { + MagViewSubmenu, + MagViewDialogEx, + MagViewPopup, + MagViewLoading, + MagViewWidget, + MagViewVariableItemList, + MagViewTextInput, +} MagView; + typedef enum { UART_TerminalEventRefreshConsoleOutput = 0, UART_TerminalEventStartConsole, diff --git a/magspoof/mag.c b/magspoof/mag.c index 71e1cdd45e4..af13659f2b9 100644 --- a/magspoof/mag.c +++ b/magspoof/mag.c @@ -1,14 +1,7 @@ #include "mag_i.h" -#include #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; @@ -21,18 +14,6 @@ static bool mag_debug_back_event_callback(void* 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)); @@ -41,6 +22,7 @@ static Mag* mag_alloc() { mag->file_name = furi_string_alloc(); mag->file_path = furi_string_alloc_set(MAG_APP_FOLDER); + mag->args = furi_string_alloc(); mag->view_dispatcher = view_dispatcher_alloc(); mag->scene_manager = scene_manager_alloc(&mag_scene_handlers, mag); @@ -52,7 +34,7 @@ static Mag* mag_alloc() { mag->view_dispatcher, mag_debug_back_event_callback); mag->mag_dev = mag_device_alloc(); - mag->setting = mag_setting_alloc(); + mag_state_load(&mag->state); // Open GUI record mag->gui = furi_record_open(RECORD_GUI); @@ -64,11 +46,6 @@ static Mag* mag_alloc() { 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)); @@ -93,13 +70,13 @@ static Mag* mag_alloc() { view_dispatcher_add_view( mag->view_dispatcher, MagViewTextInput, text_input_get_view(mag->text_input)); - return mag; -} + // Disable expansion protocol to avoid interference with UART Handle + mag->expansion = furi_record_open(RECORD_EXPANSION); + expansion_disable(mag->expansion); -static void mag_setting_free(MagSetting* setting) { - furi_assert(setting); + // Move UART here? conditional upon setting? - free(setting); + return mag; } static void mag_free(Mag* mag) { @@ -107,23 +84,16 @@ static void mag_free(Mag* mag) { furi_string_free(mag->file_name); furi_string_free(mag->file_path); + furi_string_free(mag->args); // Mag device mag_device_free(mag->mag_dev); mag->mag_dev = NULL; - // 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); @@ -158,6 +128,10 @@ static void mag_free(Mag* mag) { furi_record_close(RECORD_NOTIFICATION); mag->notifications = NULL; + // Return previous state of expansion + expansion_enable(mag->expansion); + furi_record_close(RECORD_EXPANSION); + furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_DIALOGS); @@ -166,14 +140,14 @@ static void mag_free(Mag* mag) { // entry point for app int32_t mag_app(void* p) { - UNUSED(p); - - // Disable expansion protocol to avoid interference with UART Handle - Expansion* expansion = furi_record_open(RECORD_EXPANSION); - expansion_disable(expansion); + const char* args = p; Mag* mag = mag_alloc(); + if(args && strlen(args)) { + furi_string_set(mag->args, args); + } + mag_make_app_folder(mag); // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering @@ -185,7 +159,17 @@ int32_t mag_app(void* p) { } view_dispatcher_attach_to_gui(mag->view_dispatcher, mag->gui, ViewDispatcherTypeFullscreen); - scene_manager_next_scene(mag->scene_manager, MagSceneStart); + + if(furi_string_empty(mag->args)) { + scene_manager_next_scene(mag->scene_manager, MagSceneStart); + } else { + mag_device_load_data(mag->mag_dev, mag->args, true); + MagTrackState auto_track = mag_device_autoselect_track_state(mag->mag_dev); + if(auto_track) { + mag->state.track = auto_track; + } + scene_manager_next_scene(mag->scene_manager, MagSceneEmulate); + } view_dispatcher_run(mag->view_dispatcher); @@ -196,10 +180,6 @@ int32_t mag_app(void* p) { mag_free(mag); - // Return previous state of expansion - expansion_enable(expansion); - furi_record_close(RECORD_EXPANSION); - return 0; } diff --git a/magspoof/mag_device.c b/magspoof/mag_device.c index f3be3a5ff4a..fa54adac949 100644 --- a/magspoof/mag_device.c +++ b/magspoof/mag_device.c @@ -118,9 +118,14 @@ bool mag_device_save(MagDevice* mag_dev, const char* dev_name) { 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 mag_device_load_data(MagDevice* mag_dev, FuriString* path, bool show_dialog) { bool parsed = false; + FuriString* filename; + filename = furi_string_alloc(); + path_extract_filename(path, filename, true); + strncpy(mag_dev->dev_name, furi_string_get_cstr(filename), MAG_DEV_NAME_MAX_LEN); + FlipperFormat* file = flipper_format_file_alloc(mag_dev->storage); FuriString* temp_str; temp_str = furi_string_alloc(); @@ -168,6 +173,7 @@ static bool mag_device_load_data(MagDevice* mag_dev, FuriString* path, bool show } furi_string_free(temp_str); + furi_string_free(filename); flipper_format_free(file); return parsed; @@ -189,15 +195,10 @@ bool mag_file_select(MagDevice* mag_dev) { 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; @@ -300,6 +301,27 @@ bool mag_device_parse_card_string(MagDevice* mag_dev, FuriString* f_card_str) { return true; } +MagTrackState mag_device_autoselect_track_state(MagDevice* mag_dev) { + // messy code to quickly check which tracks are available for emulation/display + bool is_empty_t1 = furi_string_empty(mag_dev->dev_data.track[0].str); + bool is_empty_t2 = furi_string_empty(mag_dev->dev_data.track[1].str); + bool is_empty_t3 = furi_string_empty(mag_dev->dev_data.track[2].str); + + if(!is_empty_t1 && !is_empty_t2) { + return MagTrackStateOneAndTwo; + } else if(!is_empty_t1) { + return MagTrackStateOne; + } else if(!is_empty_t2) { + return MagTrackStateTwo; + } else if(!is_empty_t3) { + return MagTrackStateThree; + } + + // if all empty (or something wrong with the above code) + // return default value + return MAG_STATE_DEFAULT_TRACK; +} + void mag_device_set_loading_callback( MagDevice* mag_dev, MagLoadingCallback callback, diff --git a/magspoof/mag_device.h b/magspoof/mag_device.h index c9586af558c..1b351a18dac 100644 --- a/magspoof/mag_device.h +++ b/magspoof/mag_device.h @@ -6,6 +6,7 @@ #include #include "mag_icons.h" +#include "helpers/mag_types.h" #include @@ -44,6 +45,8 @@ void mag_device_set_name(MagDevice* mag_dev, const char* name); bool mag_device_save(MagDevice* mag_dev, const char* dev_name); +bool mag_device_load_data(MagDevice* mag_dev, FuriString* path, bool show_dialog); + bool mag_file_select(MagDevice* mag_dev); void mag_device_data_clear(MagDeviceData* dev_data); @@ -54,6 +57,8 @@ bool mag_device_delete(MagDevice* mag_dev, bool use_load_path); bool mag_device_parse_card_string(MagDevice* mag_dev, FuriString* card_str); +MagTrackState mag_device_autoselect_track_state(MagDevice* mag_dev); + void mag_device_set_loading_callback( MagDevice* mag_dev, MagLoadingCallback callback, diff --git a/magspoof/mag_i.h b/magspoof/mag_i.h index 0c34e7a384a..084f16fc395 100644 --- a/magspoof/mag_i.h +++ b/magspoof/mag_i.h @@ -1,6 +1,7 @@ #pragma once #include "mag_device.h" +#include "mag_state.h" //#include "helpers/mag_helpers.h" #include "helpers/mag_types.h" @@ -9,6 +10,7 @@ #include #include #include +#include #include #include @@ -36,20 +38,22 @@ #define MAG_TEXT_STORE_SIZE 150 +// CFWs have `submenue_add_lockable_item`; OFW doesn't, +// replace with conditional submenu item +#ifdef FW_ORIGIN_Official +#define submenu_add_lockable_item( \ + submenu, label, index, callback, callback_context, locked, locked_message) \ + if(!locked) { \ + submenu_add_item(submenu, label, index, callback, callback_context) \ + } +#endif + enum MagCustomEvent { MagEventNext = 100, MagEventExit, MagEventPopupClosed, }; -typedef struct { - MagTxState tx; - MagTrackState track; - MagReverseState reverse; - uint32_t us_clock; - uint32_t us_interpacket; -} MagSetting; - typedef struct { ViewDispatcher* view_dispatcher; Gui* gui; @@ -62,12 +66,12 @@ typedef struct { char text_store[MAG_TEXT_STORE_SIZE + 1]; FuriString* file_path; FuriString* file_name; + FuriString* args; - MagSetting* setting; + MagState state; // Common views Submenu* submenu; - DialogEx* dialog_ex; Popup* popup; Loading* loading; TextInput* text_input; @@ -75,6 +79,7 @@ typedef struct { VariableItemList* variable_item_list; // UART + Expansion* expansion; FuriThread* uart_rx_thread; FuriStreamBuffer* uart_rx_stream; uint8_t uart_rx_buf[UART_RX_BUF_SIZE + 1]; diff --git a/magspoof/mag_state.c b/magspoof/mag_state.c new file mode 100644 index 00000000000..f1e4d89608b --- /dev/null +++ b/magspoof/mag_state.c @@ -0,0 +1,116 @@ +#include "mag_state.h" + +#define TAG "MagState" + +const GpioPin* mag_state_enum_to_pin(MagPin pin) { + switch(pin) { + case MagPinA7: + return &gpio_ext_pa7; + case MagPinA6: + return &gpio_ext_pa6; + case MagPinA4: + return &gpio_ext_pa4; + case MagPinB3: + return &gpio_ext_pb3; + case MagPinB2: + return &gpio_ext_pb2; + case MagPinC3: + return &gpio_ext_pc3; + case MagPinC1: + return &gpio_ext_pc1; + case MagPinC0: + return &gpio_ext_pc0; + default: + return NULL; + } +} + +bool mag_state_gpio_is_valid(MagState* state) { + return (state->pin_input != state->pin_output) && (state->pin_input != state->pin_enable) && + (state->pin_enable != state->pin_output); +} + +void mag_state_gpio_reset(MagState* state) { + state->pin_input = MAG_STATE_DEFAULT_PIN_INPUT; + state->pin_output = MAG_STATE_DEFAULT_PIN_OUTPUT; + state->pin_enable = MAG_STATE_DEFAULT_PIN_ENABLE; +} + +bool mag_state_load(MagState* out_state) { + MagState state; + + // Try to load from file + bool loaded_from_file = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_file_exists(storage, MAG_STATE_PATH)) { + FlipperFormat* file = flipper_format_file_alloc(storage); + do { + uint32_t tmp; + FuriString* str = furi_string_alloc(); + if(!flipper_format_file_open_existing(file, MAG_STATE_PATH)) break; + if(!flipper_format_read_header(file, str, &tmp)) break; + if(furi_string_cmp_str(str, MAG_STATE_HEADER)) break; + if(tmp != MAG_STATE_VER) break; + + if(!flipper_format_read_uint32(file, "pin_input", &tmp, 1)) break; + state.pin_input = tmp; + if(!flipper_format_read_uint32(file, "pin_output", &tmp, 1)) break; + state.pin_output = tmp; + if(!flipper_format_read_uint32(file, "pin_enable", &tmp, 1)) break; + state.pin_enable = tmp; + if(!flipper_format_read_bool(file, "allow_uart", &state.allow_uart, 1)) break; + + loaded_from_file = true; + } while(0); + flipper_format_free(file); + } + furi_record_close(RECORD_STORAGE); + + // If could not be read from file + // Or file GPIO config is invalid (pins overlap) + // Set defaults + // Additionally raise message to user? + if(!loaded_from_file || !mag_state_gpio_is_valid(&state)) { + mag_state_gpio_reset(&state); + } + + if(!loaded_from_file) { + state.allow_uart = MAG_STATE_DEFAULT_ALLOW_UART; + } + + // set defaults we don't save + state.tx = MAG_STATE_DEFAULT_TX; + state.track = MAG_STATE_DEFAULT_TRACK; + state.reverse = MAG_STATE_DEFAULT_REVERSE; + state.us_clock = MAG_STATE_DEFAULT_US_CLOCK; + state.us_interpacket = MAG_STATE_DEFAULT_US_INTERPACKET; + state.is_debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); + + // Copy to caller state before popping stack + memcpy(out_state, &state, sizeof(state)); + + return loaded_from_file; +} + +void mag_state_save(MagState* state) { + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_mkdir(storage, MAG_STATE_DIR); + FlipperFormat* file = flipper_format_file_alloc(storage); + + do { + uint32_t tmp; + if(!flipper_format_file_open_always(file, MAG_STATE_PATH)) break; + if(!flipper_format_write_header_cstr(file, MAG_STATE_HEADER, MAG_STATE_VER)) break; + + tmp = state->pin_input; + if(!flipper_format_write_uint32(file, "pin_input", &tmp, 1)) break; + tmp = state->pin_output; + if(!flipper_format_write_uint32(file, "pin_output", &tmp, 1)) break; + tmp = state->pin_enable; + if(!flipper_format_write_uint32(file, "pin_enable", &tmp, 1)) break; + if(!flipper_format_write_bool(file, "allow_uart", &state->allow_uart, 1)) break; + + } while(0); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); +} diff --git a/magspoof/mag_state.h b/magspoof/mag_state.h new file mode 100644 index 00000000000..b2e26623bfc --- /dev/null +++ b/magspoof/mag_state.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "helpers/mag_types.h" + +#define MAG_STATE_HEADER "Mag State" +#define MAG_STATE_VER 1 +#define MAG_STATE_DIR STORAGE_APP_DATA_PATH_PREFIX +#define MAG_STATE_PATH MAG_STATE_DIR "/mag_state.txt" + +typedef struct { + MagTxState tx; + MagTrackState track; + MagReverseState reverse; + uint32_t us_clock; + uint32_t us_interpacket; + MagPin pin_input; + MagPin pin_output; + MagPin pin_enable; + bool allow_uart; + bool is_debug; +} MagState; + +const GpioPin* mag_state_enum_to_pin(MagPin pin); + +bool mag_state_gpio_is_valid(MagState* state); + +void mag_state_gpio_reset(MagState* state); + +bool mag_state_load(MagState* out_state); + +void mag_state_save(MagState* state); diff --git a/magspoof/scenes/mag_scene_config.h b/magspoof/scenes/mag_scene_config.h index 7ab276e53bf..76de9373598 100644 --- a/magspoof/scenes/mag_scene_config.h +++ b/magspoof/scenes/mag_scene_config.h @@ -1,5 +1,7 @@ ADD_SCENE(mag, start, Start) ADD_SCENE(mag, about, About) +ADD_SCENE(mag, read, Read) +ADD_SCENE(mag, settings, Settings) ADD_SCENE(mag, emulate, Emulate) ADD_SCENE(mag, emulate_config, EmulateConfig) ADD_SCENE(mag, file_select, FileSelect) @@ -11,5 +13,4 @@ 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 +ADD_SCENE(mag, under_construction, UnderConstruction) \ No newline at end of file diff --git a/magspoof/scenes/mag_scene_emulate.c b/magspoof/scenes/mag_scene_emulate.c index e7e8737ebe3..4fdf0d8a16b 100644 --- a/magspoof/scenes/mag_scene_emulate.c +++ b/magspoof/scenes/mag_scene_emulate.c @@ -30,7 +30,7 @@ void mag_scene_emulate_on_enter(void* context) { 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); + FURI_LOG_D(TAG, "%d", mag->state.reverse); // print relevant data uint8_t cat_count = 0; @@ -39,7 +39,7 @@ void mag_scene_emulate_on_enter(void* context) { // still messy / dumb way to do this, but slightly cleaner than before. // will clean up more later - switch(mag->setting->track) { + switch(mag->state.track) { case MagTrackStateOne: if(i == 0) cat_trackstr(tmp_str, cat_count++, i, trackstr); break; diff --git a/magspoof/scenes/mag_scene_emulate_config.c b/magspoof/scenes/mag_scene_emulate_config.c index 437f536a742..05374793b4e 100644 --- a/magspoof/scenes/mag_scene_emulate_config.c +++ b/magspoof/scenes/mag_scene_emulate_config.c @@ -2,12 +2,12 @@ #define TAG "MagSceneEmulateConfig" -enum MagSettingIndex { - MagSettingIndexTx, - MagSettingIndexTrack, - MagSettingIndexReverse, - MagSettingIndexClock, - MagSettingIndexInterpacket, +enum MagEmulateConfigIndex { + MagEmulateConfigIndexTx, + MagEmulateConfigIndexTrack, + MagEmulateConfigIndexReverse, + MagEmulateConfigIndexClock, + MagEmulateConfigIndexInterpacket, }; #define TX_COUNT 7 @@ -128,17 +128,17 @@ static void mag_scene_emulate_config_set_tx(VariableItem* item) { variable_item_set_current_value_text(item, tx_text[index]); - mag->setting->tx = tx_value[index]; + mag->state.tx = tx_value[index]; }; static void mag_scene_emulate_config_set_track(VariableItem* item) { Mag* mag = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); - if(mag->setting->reverse == MagReverseStateOff) { + if(mag->state.reverse == MagReverseStateOff) { variable_item_set_current_value_text(item, track_text[index]); - mag->setting->track = track_value[index]; - } else if(mag->setting->reverse == MagReverseStateOn) { + mag->state.track = track_value[index]; + } else if(mag->state.reverse == MagReverseStateOn) { variable_item_set_current_value_index( item, value_index_uint32(MagTrackStateOneAndTwo, track_value, TRACK_COUNT)); } @@ -151,10 +151,10 @@ 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) { + if(mag->state.track == MagTrackStateOneAndTwo) { // only allow reverse track to be set when playing both 1 and 2 variable_item_set_current_value_text(item, reverse_text[index]); - mag->setting->reverse = reverse_value[index]; + mag->state.reverse = reverse_value[index]; //FURI_LOG_D(TAG, "%s", reverse_text[index]); //FURI_LOG_D(TAG, "%d", mag->setting->reverse); } else { @@ -169,7 +169,7 @@ static void mag_scene_emulate_config_set_clock(VariableItem* item) { variable_item_set_current_value_text(item, clock_text[index]); - mag->setting->us_clock = clock_value[index]; + mag->state.us_clock = clock_value[index]; }; static void mag_scene_emulate_config_set_interpacket(VariableItem* item) { @@ -178,7 +178,7 @@ static void mag_scene_emulate_config_set_interpacket(VariableItem* item) { variable_item_set_current_value_text(item, interpacket_text[index]); - mag->setting->us_interpacket = interpacket_value[index]; + mag->state.us_interpacket = interpacket_value[index]; }; void mag_scene_emulate_config_on_enter(void* context) { @@ -188,18 +188,18 @@ void mag_scene_emulate_config_on_enter(void* context) { VariableItem* item; uint8_t value_index; - // TX + // Clock 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); + mag->variable_item_list, "Clock:", CLOCK_COUNT, mag_scene_emulate_config_set_clock, mag); + value_index = value_index_uint32(mag->state.us_clock, clock_value, CLOCK_COUNT); 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]); + variable_item_set_current_value_text(item, clock_text[value_index]); // Track item = variable_item_list_add( mag->variable_item_list, "Track:", TRACK_COUNT, mag_scene_emulate_config_set_track, mag); - value_index = value_index_uint32(mag->setting->track, track_value, TRACK_COUNT); + value_index = value_index_uint32(mag->state.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]); @@ -212,19 +212,26 @@ void mag_scene_emulate_config_on_enter(void* context) { REVERSE_COUNT, mag_scene_emulate_config_set_reverse, mag); - value_index = value_index_uint32(mag->setting->reverse, reverse_value, REVERSE_COUNT); + value_index = value_index_uint32(mag->state.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]); - + // TX +#ifdef FW_ORIGIN_Official + if(mag->state.is_debug) { +#endif + item = variable_item_list_add( + mag->variable_item_list, "TX via:", TX_COUNT, mag_scene_emulate_config_set_tx, mag); + value_index = value_index_uint32(mag->state.tx, tx_value, TX_COUNT); + 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]); +#ifdef FW_ORIGIN_Official + } +#else + variable_item_set_locked(item, !mag->state.is_debug, "Enable Debug!"); +#endif // Interpacket /* item = variable_item_list_add( diff --git a/magspoof/scenes/mag_scene_file_select.c b/magspoof/scenes/mag_scene_file_select.c index b759c4d18fa..47b12eea2d3 100644 --- a/magspoof/scenes/mag_scene_file_select.c +++ b/magspoof/scenes/mag_scene_file_select.c @@ -3,9 +3,12 @@ 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)) { + MagTrackState auto_track = mag_device_autoselect_track_state(mag->mag_dev); + if(auto_track) { + mag->state.track = auto_track; + } scene_manager_next_scene(mag->scene_manager, MagSceneSavedMenu); } else { scene_manager_search_and_switch_to_previous_scene(mag->scene_manager, MagSceneStart); diff --git a/magspoof/scenes/mag_scene_saved_menu.c b/magspoof/scenes/mag_scene_saved_menu.c index efb322d0852..fcf9fc5a080 100644 --- a/magspoof/scenes/mag_scene_saved_menu.c +++ b/magspoof/scenes/mag_scene_saved_menu.c @@ -24,13 +24,13 @@ void mag_scene_saved_menu_on_enter(void* context) { 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; + mag->state.track = MagTrackStateOneAndTwo; } else if(!is_empty_t1) { - mag->setting->track = MagTrackStateOne; + mag->state.track = MagTrackStateOne; } else if(!is_empty_t2) { - mag->setting->track = MagTrackStateTwo; + mag->state.track = MagTrackStateTwo; } else if(!is_empty_t3) { - mag->setting->track = MagTrackStateThree; + mag->state.track = MagTrackStateThree; } // TODO: what happens if no track data present? submenu_add_item( diff --git a/magspoof/scenes/mag_scene_settings.c b/magspoof/scenes/mag_scene_settings.c new file mode 100644 index 00000000000..3ce0c683b7c --- /dev/null +++ b/magspoof/scenes/mag_scene_settings.c @@ -0,0 +1,177 @@ +#include "../mag_i.h" +#include "../mag_state.h" +#include "../helpers/mag_helpers.h" + +#define TAG "MagSceneEmulateConfig" + +enum VarItemListIndex { + VarItemListIndexPinInput, + VarItemListIndexPinOutput, + VarItemListIndexPinEnable, + VarItemListIndexAllowUART, +}; + +static const char* gpio[] = { + [MagPinA7] = "2 (A7)", + [MagPinA6] = "3 (A6)", + [MagPinA4] = "4 (A4)", + [MagPinB3] = "5 (B3)", + [MagPinB2] = "6 (B2)", + [MagPinC3] = "7 (C3)", + [MagPinC1] = "15 (C1)", + [MagPinC0] = "16 (C0)", +}; +const uint8_t GPIO_COUNT = COUNT_OF(gpio); +// static const char* uart_pins[] = {[DapUartTypeUSART1] = "13,14", [DapUartTypeLPUART1] = "15,16"}; +// static const char* uart_swap[] = {[DapUartTXRXNormal] = "No", [DapUartTXRXSwap] = "Yes"}; + +void mag_scene_settings_var_item_list_callback(void* context, uint32_t index) { + Mag* mag = context; + view_dispatcher_send_custom_event(mag->view_dispatcher, index); +} + +static void mag_scene_settings_set_gpio(VariableItem* item, MagPin* pin_out) { + MagPin pin = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, gpio[pin]); + *pin_out = pin; +} + +static void mag_scene_settings_set_gpio_input(VariableItem* item) { + Mag* mag = variable_item_get_context(item); + mag_scene_settings_set_gpio(item, &mag->state.pin_input); +}; + +static void mag_scene_settings_set_gpio_output(VariableItem* item) { + Mag* mag = variable_item_get_context(item); + mag_scene_settings_set_gpio(item, &mag->state.pin_output); +}; + +static void mag_scene_settings_set_gpio_enable(VariableItem* item) { + Mag* mag = variable_item_get_context(item); + mag_scene_settings_set_gpio(item, &mag->state.pin_enable); +}; + +static void mag_pin_variable_item_list_add( + Mag* mag, + const char* label, + MagPin pin, + VariableItemChangeCallback change_callback) { + VariableItem* item = + variable_item_list_add(mag->variable_item_list, label, GPIO_COUNT, change_callback, mag); + variable_item_set_current_value_index(item, pin); + variable_item_set_current_value_text(item, gpio[pin]); +} + +void mag_scene_settings_on_enter(void* context) { + Mag* mag = context; + VariableItem* item; + VariableItemList* var_item_list = mag->variable_item_list; + + mag_pin_variable_item_list_add( + mag, "Input pin:", mag->state.pin_input, mag_scene_settings_set_gpio_input); + mag_pin_variable_item_list_add( + mag, "Output pin:", mag->state.pin_output, mag_scene_settings_set_gpio_output); + mag_pin_variable_item_list_add( + mag, "Enable pin:", mag->state.pin_enable, mag_scene_settings_set_gpio_enable); + + item = variable_item_list_add(var_item_list, "UART MSR: ", 1, NULL, mag); + variable_item_set_current_value_text(item, mag->state.allow_uart ? "ON" : "OFF"); + + variable_item_list_set_enter_callback( + var_item_list, mag_scene_settings_var_item_list_callback, mag); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(mag->scene_manager, MagSceneSettings)); + + view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewVariableItemList); +} + +void mag_scene_settings_dialog_invalid_pins(Mag* mag) { + SceneManager* scene_manager = mag->scene_manager; + + DialogMessage* message = dialog_message_alloc(); + + dialog_message_set_header(message, "Invalid Pin Config!", 64, 0, AlignCenter, AlignTop); + dialog_message_set_buttons(message, "Modify", NULL, "Reset"); + dialog_message_set_text( + message, + "Pins cannot overlap.\nChange, or reset to defaults.", + 64, + 32, + AlignCenter, + AlignCenter); + DialogMessageButton res = dialog_message_show(furi_record_open(RECORD_DIALOGS), message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + if(res == DialogMessageButtonRight) { + mag_state_gpio_reset(&mag->state); + scene_manager_previous_scene(scene_manager); + } +} + +void mag_scene_settings_dialog_allow_uart(Mag* mag) { + bool change = mag->state.allow_uart; + if(!change) { + DialogMessage* msg = dialog_message_alloc(); + dialog_message_set_header(msg, "UART MSR", 64, 0, AlignCenter, AlignTop); + dialog_message_set_buttons(msg, "No", NULL, "Yes"); + dialog_message_set_text( + msg, + "This option requires a\nUART-compatible mag reader.\nIs it installed?\n", + 64, + 32, + AlignCenter, + AlignCenter); + DialogMessageButton res = dialog_message_show(furi_record_open(RECORD_DIALOGS), msg); + if(res == DialogMessageButtonRight) { + change = true; + } + dialog_message_free(msg); + furi_record_close(RECORD_DIALOGS); + } + if(change) { + mag->state.allow_uart = !mag->state.allow_uart; + variable_item_set_current_value_text( + variable_item_list_get(mag->variable_item_list, VarItemListIndexAllowUART), + mag->state.allow_uart ? "ON" : "OFF"); + } +} + +bool mag_scene_settings_on_event(void* context, SceneManagerEvent event) { + Mag* mag = context; + SceneManager* scene_manager = mag->scene_manager; + bool consumed = false; + + switch(event.type) { + case SceneManagerEventTypeBack: + // when attempting to exit, validate pin configuration + // if invalid, prompt + consumed = true; + + if(!mag_state_gpio_is_valid(&mag->state)) { + mag_scene_settings_dialog_invalid_pins(mag); + } else { + scene_manager_previous_scene(scene_manager); + } + break; + case SceneManagerEventTypeCustom: + scene_manager_set_scene_state(mag->scene_manager, MagSceneSettings, event.event); + consumed = true; + if(event.event == VarItemListIndexAllowUART) { + mag_scene_settings_dialog_allow_uart(mag); + } + break; + default: + break; + } + + return consumed; +} + +void mag_scene_settings_on_exit(void* context) { + Mag* mag = context; + + variable_item_list_reset(mag->variable_item_list); + + mag_state_save(&mag->state); +} \ No newline at end of file diff --git a/magspoof/scenes/mag_scene_start.c b/magspoof/scenes/mag_scene_start.c index 5382341891c..8b8c0d1a3cc 100644 --- a/magspoof/scenes/mag_scene_start.c +++ b/magspoof/scenes/mag_scene_start.c @@ -4,6 +4,7 @@ typedef enum { SubmenuIndexSaved, SubmenuIndexRead, //SubmenuIndexAddManually, + SubmenuIndexSettings, SubmenuIndexAbout, } SubmenuIndex; @@ -18,9 +19,19 @@ void mag_scene_start_on_enter(void* 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_lockable_item( + submenu, + "Read", + SubmenuIndexRead, + mag_scene_start_submenu_callback, + mag, + (!mag->state.is_debug && !mag->state.allow_uart), + "Enable Debug!"); //submenu_add_item( // submenu, "Add Manually", SubmenuIndexAddManually, mag_scene_start_submenu_callback, mag); + submenu_add_item( + submenu, "Settings", SubmenuIndexSettings, mag_scene_start_submenu_callback, mag); + submenu_add_item(submenu, "About", SubmenuIndexAbout, mag_scene_start_submenu_callback, mag); submenu_set_selected_item( @@ -52,6 +63,10 @@ bool mag_scene_start_on_event(void* context, SceneManagerEvent event) { // scene_manager_next_scene(mag->scene_manager, MagSceneInputValue); // consumed = true; // break; + case SubmenuIndexSettings: + scene_manager_next_scene(mag->scene_manager, MagSceneSettings); + consumed = true; + break; case SubmenuIndexAbout: scene_manager_next_scene(mag->scene_manager, MagSceneAbout); consumed = true; diff --git a/picopass/application.fam b/picopass/application.fam index a880bf78ab0..937387bdc2d 100644 --- a/picopass/application.fam +++ b/picopass/application.fam @@ -4,6 +4,10 @@ App( apptype=FlipperAppType.EXTERNAL, targets=["f7"], entry_point="picopass_app", + sources=[ + "*.c", + "!plugin/*.c", + ], requires=[ "storage", "gui", @@ -23,3 +27,12 @@ App( fap_icon_assets="icons", fap_file_assets="files", ) + +App( + appid="picopass_plugin_wiegand", + apptype=FlipperAppType.PLUGIN, + entry_point="plugin_wiegand_ep", + requires=["picopass"], + sources=["plugin/wiegand.c"], + fal_embedded=True, +) diff --git a/picopass/picopass.c b/picopass/picopass.c index 9b5532e534d..0343d010af4 100644 --- a/picopass/picopass.c +++ b/picopass/picopass.c @@ -97,6 +97,27 @@ Picopass* picopass_alloc() { view_dispatcher_add_view( picopass->view_dispatcher, PicopassViewLoclass, loclass_get_view(picopass->loclass)); + picopass->plugin_manager = + plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface); + + picopass->plugin_wiegand = NULL; + if(plugin_manager_load_all(picopass->plugin_manager, APP_ASSETS_PATH("plugins")) != + PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load all libs"); + } else { + uint32_t plugin_count = plugin_manager_get_count(picopass->plugin_manager); + FURI_LOG_I(TAG, "Loaded %lu plugin(s)", plugin_count); + + for(uint32_t i = 0; i < plugin_count; i++) { + const PluginWiegand* plugin = plugin_manager_get_ep(picopass->plugin_manager, i); + FURI_LOG_I(TAG, "plugin name: %s", plugin->name); + if(strcmp(plugin->name, "Plugin Wiegand") == 0) { + // Have to cast to drop "const" qualifier + picopass->plugin_wiegand = (PluginWiegand*)plugin; + } + } + } + return picopass; } @@ -158,6 +179,8 @@ void picopass_free(Picopass* picopass) { furi_record_close(RECORD_NOTIFICATION); picopass->notifications = NULL; + plugin_manager_free(picopass->plugin_manager); + free(picopass); } diff --git a/picopass/picopass_i.h b/picopass/picopass_i.h index dac41311ad9..a2eb79f77ea 100644 --- a/picopass/picopass_i.h +++ b/picopass/picopass_i.h @@ -36,6 +36,11 @@ #include "protocol/picopass_poller.h" #include "protocol/picopass_listener.h" +#include "plugin/interface.h" +#include +#include +#include + #define PICOPASS_TEXT_STORE_SIZE 129 #define PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt") @@ -109,6 +114,9 @@ struct Picopass { DictAttack* dict_attack; Loclass* loclass; + PluginManager* plugin_manager; + PluginWiegand* plugin_wiegand; + PicopassDictAttackContext dict_attack_ctx; PicopassWriteKeyContext write_key_context; PicopassLoclassContext loclass_context; diff --git a/picopass/plugin/.gitsubtree b/picopass/plugin/.gitsubtree new file mode 100644 index 00000000000..7ec4d2c4663 --- /dev/null +++ b/picopass/plugin/.gitsubtree @@ -0,0 +1 @@ +https://gitlab.com/bettse/flipper-wiegand-plugin main / diff --git a/picopass/plugin/README.md b/picopass/plugin/README.md new file mode 100644 index 00000000000..2fe51c876c2 --- /dev/null +++ b/picopass/plugin/README.md @@ -0,0 +1,14 @@ +# Flipper zero wiegand plugin + +Add as git submodule: `git submodule add https://gitlab.com/bettse/flipper-wiegand-plugin.git plugin` + +Add to your `application.fam` +``` +App( + appid="plugin_wiegand", + apptype=FlipperAppType.PLUGIN, + entry_point="plugin_wiegand_ep", + requires=["seader"], + sources=["plugin/wiegand.c"], +) +``` diff --git a/picopass/plugin/interface.h b/picopass/plugin/interface.h new file mode 100644 index 00000000000..714b24598b7 --- /dev/null +++ b/picopass/plugin/interface.h @@ -0,0 +1,20 @@ +/** + * @file plugin_interface.h + * @brief Example plugin interface. + * + * Common interface between a plugin and host application + */ +#pragma once + +#include +#include +#include + +#define PLUGIN_APP_ID "plugin_wiegand" +#define PLUGIN_API_VERSION 1 + +typedef struct { + const char* name; + int (*count)(uint8_t, uint64_t); + void (*description)(uint8_t, uint64_t, size_t, FuriString*); +} PluginWiegand; diff --git a/picopass/plugin/wiegand.c b/picopass/plugin/wiegand.c new file mode 100644 index 00000000000..1a87112d06d --- /dev/null +++ b/picopass/plugin/wiegand.c @@ -0,0 +1,152 @@ + +#include "interface.h" + +#include +#include + +/* + * Huge thanks to the proxmark codebase: + * https://github.com/RfidResearchGroup/proxmark3/blob/master/client/src/wiegand_formats.c + */ + +// Structure for packed wiegand messages +// Always align lowest value (last transmitted) bit to ordinal position 0 (lowest valued bit bottom) +typedef struct { + uint8_t Length; // Number of encoded bits in wiegand message (excluding headers and preamble) + uint32_t Top; // Bits in x<<64 positions + uint32_t Mid; // Bits in x<<32 positions + uint32_t Bot; // Lowest ordinal positions +} wiegand_message_t; + +static inline uint8_t oddparity32(uint32_t x) { + return bit_lib_test_parity_32(x, BitLibParityOdd); +} + +static inline uint8_t evenparity32(uint32_t x) { + return bit_lib_test_parity_32(x, BitLibParityEven); +} + +static int wiegand_C1k35s_parse(uint8_t bit_length, uint64_t bits, FuriString* description) { + if(bit_length != 35) { + return 0; + } + + wiegand_message_t value; + value.Mid = bits >> 32; + value.Bot = bits; + wiegand_message_t* packed = &value; + + uint32_t cn = (packed->Bot >> 1) & 0x000FFFFF; + uint32_t fc = ((packed->Mid & 1) << 11) | ((packed->Bot >> 21)); + bool valid = (evenparity32((packed->Mid & 0x1) ^ (packed->Bot & 0xB6DB6DB6)) == + ((packed->Mid >> 1) & 1)) && + (oddparity32((packed->Mid & 0x3) ^ (packed->Bot & 0x6DB6DB6C)) == + ((packed->Bot >> 0) & 1)) && + (oddparity32((packed->Mid & 0x3) ^ (packed->Bot & 0xFFFFFFFF)) == + ((packed->Mid >> 2) & 1)); + + if(valid) { + furi_string_cat_printf(description, "C1k35s\nFC: %ld CN: %ld\n", fc, cn); + return 1; + } + + return 0; +} + +static int wiegand_h10301_parse(uint8_t bit_length, uint64_t bits, FuriString* description) { + if(bit_length != 26) { + return 0; + } + + //E XXXX XXXX XXXX + //XXXX XXXX XXXX O + uint32_t eBitMask = 0x02000000; + uint32_t oBitMask = 0x00000001; + uint32_t eParityMask = 0x01FFE000; + uint32_t oParityMask = 0x00001FFE; + uint8_t eBit = (eBitMask & bits) >> 25; + uint8_t oBit = (oBitMask & bits) >> 0; + + bool eParity = bit_lib_test_parity_32((bits & eParityMask) >> 13, BitLibParityEven) == + (eBit == 1); + bool oParity = bit_lib_test_parity_32((bits & oParityMask) >> 1, BitLibParityOdd) == + (oBit == 1); + + FURI_LOG_D( + PLUGIN_APP_ID, + "eBit: %d, oBit: %d, eParity: %d, oParity: %d", + eBit, + oBit, + eParity, + oParity); + + if(eParity && oParity) { + uint32_t cnMask = 0x1FFFE; + uint16_t cn = ((bits & cnMask) >> 1); + + uint32_t fcMask = 0x1FE0000; + uint16_t fc = ((bits & fcMask) >> 17); + + furi_string_cat_printf(description, "H10301\nFC: %d CN: %d\n", fc, cn); + return 1; + } + + return 0; +} + +static int wiegand_format_count(uint8_t bit_length, uint64_t bits) { + UNUSED(bit_length); + UNUSED(bits); + int count = 0; + FuriString* ignore = furi_string_alloc(); + + count += wiegand_h10301_parse(bit_length, bits, ignore); + count += wiegand_C1k35s_parse(bit_length, bits, ignore); + + furi_string_free(ignore); + + FURI_LOG_I(PLUGIN_APP_ID, "count: %i", count); + return count; +} + +static void wiegand_format_description( + uint8_t bit_length, + uint64_t bits, + size_t index, + FuriString* description) { + FURI_LOG_I(PLUGIN_APP_ID, "description %d", index); + UNUSED(bit_length); + UNUSED(bits); + + size_t i = 0; + + i += wiegand_h10301_parse(bit_length, bits, description); + if(i - 1 == index) { + return; + } + i += wiegand_C1k35s_parse(bit_length, bits, description); + if(i - 1 == index) { + return; + } + + furi_string_cat_printf(description, "[%i] FC: CN:", index); +} + +/* Actual implementation of app<>plugin interface */ +static const PluginWiegand plugin_wiegand = { + .name = "Plugin Wiegand", + .count = &wiegand_format_count, + .description = &wiegand_format_description, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor plugin_wiegand_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &plugin_wiegand, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* plugin_wiegand_ep(void) { + return &plugin_wiegand_descriptor; +} diff --git a/picopass/scenes/picopass_scene_card_menu.c b/picopass/scenes/picopass_scene_card_menu.c index d9e27ea5c4b..9d44f5642d9 100644 --- a/picopass/scenes/picopass_scene_card_menu.c +++ b/picopass/scenes/picopass_scene_card_menu.c @@ -4,6 +4,7 @@ enum SubmenuIndex { SubmenuIndexSave, SubmenuIndexSaveAsLF, SubmenuIndexSaveAsSeader, + SubmenuIndexParse, SubmenuIndexChangeKey, SubmenuIndexWrite, SubmenuIndexEmulate, @@ -22,6 +23,7 @@ void picopass_scene_card_menu_on_enter(void* context) { PicopassPacs* pacs = &picopass->dev->dev_data.pacs; PicopassBlock* card_data = picopass->dev->dev_data.card_data; PicopassDeviceAuthMethod auth = picopass->dev->dev_data.auth; + PluginWiegand* plugin = picopass->plugin_wiegand; bool SE = card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].valid && 0x30 == card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data[0]; @@ -59,6 +61,23 @@ void picopass_scene_card_menu_on_enter(void* context) { SubmenuIndexSaveAsLF, picopass_scene_card_menu_submenu_callback, picopass); + + if(plugin) { + // Convert from byte array to uint64_t + uint64_t credential = 0; + memcpy(&credential, pacs->credential, sizeof(uint64_t)); + credential = __builtin_bswap64(credential); + + size_t format_count = plugin->count(pacs->bitLength, credential); + if(format_count > 0) { + submenu_add_item( + submenu, + "Parse", + SubmenuIndexParse, + picopass_scene_card_menu_submenu_callback, + picopass); + } + } } if(auth == PicopassDeviceAuthMethodNone || auth == PicopassDeviceAuthMethodKey) { @@ -131,6 +150,11 @@ bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) { picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexChangeKey); scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyMenu); consumed = true; + } else if(event.event == SubmenuIndexParse) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexParse); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneFormats); + consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { consumed = scene_manager_search_and_switch_to_previous_scene( diff --git a/picopass/scenes/picopass_scene_config.h b/picopass/scenes/picopass_scene_config.h index 0830834f811..9eb5f7f5ff6 100644 --- a/picopass/scenes/picopass_scene_config.h +++ b/picopass/scenes/picopass_scene_config.h @@ -21,3 +21,4 @@ ADD_SCENE(picopass, loclass, Loclass) ADD_SCENE(picopass, key_input, KeyInput) ADD_SCENE(picopass, nr_mac_saved, NrMacSaved) ADD_SCENE(picopass, more_info, MoreInfo) +ADD_SCENE(picopass, formats, Formats) diff --git a/picopass/scenes/picopass_scene_device_info.c b/picopass/scenes/picopass_scene_device_info.c index c08adfda236..4d4aba5ae9b 100644 --- a/picopass/scenes/picopass_scene_device_info.c +++ b/picopass/scenes/picopass_scene_device_info.c @@ -23,6 +23,7 @@ void picopass_scene_device_info_on_enter(void* context) { // Setup view PicopassBlock* card_data = picopass->dev->dev_data.card_data; PicopassPacs* pacs = &picopass->dev->dev_data.pacs; + PluginWiegand* plugin = picopass->plugin_wiegand; Widget* widget = picopass->widget; uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; @@ -77,10 +78,27 @@ void picopass_scene_device_info_on_enter(void* context) { "Back", picopass_scene_device_info_widget_callback, picopass); + + if(plugin) { + // Convert from byte array to uint64_t + uint64_t credential = 0; + memcpy(&credential, pacs->credential, sizeof(uint64_t)); + credential = __builtin_bswap64(credential); + + size_t format_count = plugin->count(pacs->bitLength, credential); + if(format_count > 0) { + widget_add_button_element( + picopass->widget, + GuiButtonTypeCenter, + "Parse", + picopass_scene_device_info_widget_callback, + picopass); + } + } widget_add_button_element( picopass->widget, GuiButtonTypeRight, - "More", + "Raw", picopass_scene_device_info_widget_callback, picopass); @@ -97,6 +115,9 @@ bool picopass_scene_device_info_on_event(void* context, SceneManagerEvent event) } else if(event.event == GuiButtonTypeRight) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneMoreInfo); consumed = true; + } else if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneFormats); + consumed = true; } else if(event.event == PicopassCustomEventViewExit) { view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); consumed = true; diff --git a/picopass/scenes/picopass_scene_formats.c b/picopass/scenes/picopass_scene_formats.c new file mode 100644 index 00000000000..5cf97b16392 --- /dev/null +++ b/picopass/scenes/picopass_scene_formats.c @@ -0,0 +1,54 @@ +#include "../picopass_i.h" +#include + +void picopass_scene_formats_on_enter(void* context) { + Picopass* picopass = context; + PluginWiegand* plugin = picopass->plugin_wiegand; + PicopassDevice* dev = picopass->dev; + PicopassDeviceData dev_data = dev->dev_data; + PicopassPacs pacs = dev_data.pacs; + + FuriString* str = picopass->text_box_store; + furi_string_reset(str); + + // Convert from byte array to uint64_t + uint64_t credential = 0; + memcpy(&credential, pacs.credential, sizeof(pacs.credential)); + credential = __builtin_bswap64(credential); + + if(plugin) { + FuriString* description = furi_string_alloc(); + size_t format_count = plugin->count(pacs.bitLength, credential); + for(size_t i = 0; i < format_count; i++) { + plugin->description(pacs.bitLength, credential, i, description); + + furi_string_cat_printf(str, "%s\n", furi_string_get_cstr(description)); + } + furi_string_free(description); + } + + text_box_set_font(picopass->text_box, TextBoxFontHex); + text_box_set_text(picopass->text_box, furi_string_get_cstr(picopass->text_box_store)); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextBox); +} + +bool picopass_scene_formats_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } + return consumed; +} + +void picopass_scene_formats_on_exit(void* context) { + Picopass* picopass = context; + + // Clear views + text_box_reset(picopass->text_box); +} diff --git a/seader/application.fam b/seader/application.fam index 2587378b45b..5f72f4b12f2 100644 --- a/seader/application.fam +++ b/seader/application.fam @@ -44,7 +44,7 @@ App( ) App( - appid="plugin_wiegand", + appid="picopass_plugin_wiegand", apptype=FlipperAppType.PLUGIN, entry_point="plugin_wiegand_ep", requires=["seader"],