Просмотр исходного кода

Merge magspoof from https://github.com/zacharyweiss/magspoof_flipper

Willy-JL 1 год назад
Родитель
Сommit
879465b5a3

+ 30 - 0
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.

+ 20 - 0
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.

+ 1 - 1
magspoof/README.md

@@ -1,5 +1,5 @@
 # magspoof_flipper
 # 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`.
  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`.
 
 

+ 61 - 52
magspoof/helpers/mag_helpers.c

@@ -2,12 +2,6 @@
 
 
 #define TAG "MagHelpers"
 #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_PREFIX 25 // n zeros prefix
 #define ZERO_BETWEEN 53 // n zeros between tracks
 #define ZERO_BETWEEN 53 // n zeros between tracks
 #define ZERO_SUFFIX 25 // n zeros suffix
 #define ZERO_SUFFIX 25 // n zeros suffix
@@ -19,18 +13,18 @@ const int sublen[] = {32, 48, 48};
 
 
 uint8_t last_value = 2;
 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:
     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);
         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;
         break;
     case MagTxStateGPIO:
     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;
         break;
     case MagTxStatePiezo:
     case MagTxStatePiezo:
         furi_hal_gpio_write(&gpio_speaker, value);
         furi_hal_gpio_write(&gpio_speaker, value);
@@ -41,7 +35,7 @@ void play_halfbit(bool value, MagSetting* setting) {
 
 
         break;
         break;
     case MagTxStateLF_P:
     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);
         furi_hal_gpio_write(&gpio_speaker, value);
 
 
         /* // Weaker but cleaner signal
         /* // Weaker but cleaner signal
@@ -69,7 +63,7 @@ void play_halfbit(bool value, MagSetting* setting) {
 
 
         if(last_value == 2 || value != (bool)last_value) {
         if(last_value == 2 || value != (bool)last_value) {
             //furi_hal_nfc_ll_txrx_on();
             //furi_hal_nfc_ll_txrx_on();
-            //furi_delay_us(64);
+            furi_delay_us(64);
             //furi_hal_nfc_ll_txrx_off();
             //furi_hal_nfc_ll_txrx_off();
         }
         }
         break;
         break;
@@ -88,7 +82,7 @@ void play_halfbit(bool value, MagSetting* setting) {
     last_value = value;
     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++) {
     for(uint16_t i = 0; i < n_bits; i++) {
         uint16_t j = (reverse) ? (n_bits - i - 1) : i;
         uint16_t j = (reverse) ? (n_bits - i - 1) : i;
         uint8_t byte = j / 8;
         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
         // for DWT->CYCCNT. Note timer is aliased to 64us as per
         // #define FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND (SystemCoreClock / 1000000) | furi_hal_cortex.c
         // #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_start_drive();
     furi_hal_ibutton_pin_write(false);
     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
     // 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_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_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
     furi_hal_gpio_write(&gpio_nfc_irq_rfid_pull, false);
     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);
     furi_delay_ms(300);
 }
 }
 
 
 void tx_deinit_rfid() {
 void tx_deinit_rfid() {
     // reset RFID system
     // 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();
     furi_hal_rfid_pins_reset();
 }
 }
@@ -179,19 +173,31 @@ void tx_deinit_piezo() {
     furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
     furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
 }
 }
 
 
-bool tx_init(MagSetting* setting) {
+bool tx_init(MagState* state) {
     // Initialize configured TX method
     // Initialize configured TX method
-    switch(setting->tx) {
+    switch(state->tx) {
     case MagTxStateRFID:
     case MagTxStateRFID:
         tx_init_rfid();
         tx_init_rfid();
         break;
         break;
     case MagTxStateGPIO:
     case MagTxStateGPIO:
         // gpio_item_configure_all_pins(GpioModeOutputPushPull);
         // 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
         // had some issues with ~300; bumped higher temporarily
         furi_delay_ms(500);
         furi_delay_ms(500);
@@ -219,21 +225,24 @@ bool tx_init(MagSetting* setting) {
     return true;
     return true;
 }
 }
 
 
-bool tx_deinit(MagSetting* setting) {
+bool tx_deinit(MagState* state) {
     // Reset configured TX method
     // Reset configured TX method
-    switch(setting->tx) {
+    switch(state->tx) {
     case MagTxStateRFID:
     case MagTxStateRFID:
         tx_deinit_rfid();
         tx_deinit_rfid();
         break;
         break;
     case MagTxStateGPIO:
     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
         // 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);
         //gpio_item_configure_all_pins(GpioModeAnalog);
         break;
         break;
@@ -262,7 +271,7 @@ bool tx_deinit(MagSetting* setting) {
 }
 }
 
 
 void mag_spoof(Mag* mag) {
 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?
     // TODO: cleanup this section. Possibly move precompute + tx_init to emulate_on_enter?
     FuriString* ft1 = mag->mag_dev->dev_data.track[0].str;
     FuriString* ft1 = mag->mag_dev->dev_data.track[0].str;
@@ -320,47 +329,47 @@ void mag_spoof(Mag* mag) {
     last_value = 2;
     last_value = 2;
     bool bit = false;
     bool bit = false;
 
 
-    if(!tx_init(setting)) return;
+    if(!tx_init(state)) return;
 
 
     FURI_CRITICAL_ENTER();
     FURI_CRITICAL_ENTER();
     for(uint16_t i = 0; i < (ZERO_PREFIX * 2); i++) {
     for(uint16_t i = 0; i < (ZERO_PREFIX * 2); i++) {
         // is this right?
         // is this right?
         if(!!(i % 2)) bit ^= 1;
         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++) {
         for(uint16_t i = 0; i < (ZERO_BETWEEN * 2); i++) {
             if(!!(i % 2)) bit ^= 1;
             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(
         play_track(
             (uint8_t*)bits_t2_manchester,
             (uint8_t*)bits_t2_manchester,
             bits_t2_count,
             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++) {
     for(uint16_t i = 0; i < (ZERO_SUFFIX * 2); i++) {
         if(!!(i % 2)) bit ^= 1;
         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();
     FURI_CRITICAL_EXIT();
 
 
     free(data1);
     free(data1);
     free(data2);
     free(data2);
     free(data3);
     free(data3);
-    tx_deinit(setting);
+    tx_deinit(state);
 }
 }
 
 
 uint16_t add_bit(bool value, uint8_t* out, uint16_t count) {
 uint16_t add_bit(bool value, uint8_t* out, uint16_t count) {

+ 4 - 4
magspoof/helpers/mag_helpers.h

@@ -2,16 +2,16 @@
 #include <stdio.h>
 #include <stdio.h>
 #include <string.h>
 #include <string.h>
 
 
-void play_halfbit(bool value, MagSetting* setting);
-void play_track(uint8_t* bits_manchester, uint16_t n_bits, MagSetting* setting, bool reverse);
+void 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_rf(int hz);
 void tx_init_rfid();
 void tx_init_rfid();
 void tx_init_piezo();
 void tx_init_piezo();
-bool tx_init(MagSetting* setting);
+bool tx_init(MagState* state);
 void tx_deinit_piezo();
 void tx_deinit_piezo();
 void tx_deinit_rfid();
 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(bool value, uint8_t* out, uint16_t count);
 uint16_t add_bit_manchester(bool value, uint8_t* out, uint16_t count);
 uint16_t add_bit_manchester(bool value, uint8_t* out, uint16_t count);

+ 32 - 11
magspoof/helpers/mag_types.h

@@ -1,19 +1,9 @@
 #pragma once
 #pragma once
 
 
-#define MAG_VERSION_APP "0.06"
+#define MAG_VERSION_APP "0.07"
 #define MAG_DEVELOPER "Zachary Weiss"
 #define MAG_DEVELOPER "Zachary Weiss"
 #define MAG_GITHUB "github.com/zacharyweiss/magspoof_flipper"
 #define MAG_GITHUB "github.com/zacharyweiss/magspoof_flipper"
 
 
-typedef enum {
-    MagViewSubmenu,
-    MagViewDialogEx,
-    MagViewPopup,
-    MagViewLoading,
-    MagViewWidget,
-    MagViewVariableItemList,
-    MagViewTextInput,
-} MagView;
-
 typedef enum {
 typedef enum {
     MagReverseStateOff,
     MagReverseStateOff,
     MagReverseStateOn,
     MagReverseStateOn,
@@ -36,6 +26,37 @@ typedef enum {
     MagTxCC1101_868,
     MagTxCC1101_868,
 } MagTxState;
 } 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 {
 typedef enum {
     UART_TerminalEventRefreshConsoleOutput = 0,
     UART_TerminalEventRefreshConsoleOutput = 0,
     UART_TerminalEventStartConsole,
     UART_TerminalEventStartConsole,

+ 28 - 48
magspoof/mag.c

@@ -1,14 +1,7 @@
 #include "mag_i.h"
 #include "mag_i.h"
-#include <expansion/expansion.h>
 
 
 #define TAG "Mag"
 #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) {
 static bool mag_debug_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
     furi_assert(context);
     Mag* mag = 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);
     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() {
 static Mag* mag_alloc() {
     Mag* mag = malloc(sizeof(Mag));
     Mag* mag = malloc(sizeof(Mag));
 
 
@@ -41,6 +22,7 @@ static Mag* mag_alloc() {
 
 
     mag->file_name = furi_string_alloc();
     mag->file_name = furi_string_alloc();
     mag->file_path = furi_string_alloc_set(MAG_APP_FOLDER);
     mag->file_path = furi_string_alloc_set(MAG_APP_FOLDER);
+    mag->args = furi_string_alloc();
 
 
     mag->view_dispatcher = view_dispatcher_alloc();
     mag->view_dispatcher = view_dispatcher_alloc();
     mag->scene_manager = scene_manager_alloc(&mag_scene_handlers, mag);
     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->view_dispatcher, mag_debug_back_event_callback);
 
 
     mag->mag_dev = mag_device_alloc();
     mag->mag_dev = mag_device_alloc();
-    mag->setting = mag_setting_alloc();
+    mag_state_load(&mag->state);
 
 
     // Open GUI record
     // Open GUI record
     mag->gui = furi_record_open(RECORD_GUI);
     mag->gui = furi_record_open(RECORD_GUI);
@@ -64,11 +46,6 @@ static Mag* mag_alloc() {
     mag->submenu = submenu_alloc();
     mag->submenu = submenu_alloc();
     view_dispatcher_add_view(mag->view_dispatcher, MagViewSubmenu, submenu_get_view(mag->submenu));
     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
     // Popup
     mag->popup = popup_alloc();
     mag->popup = popup_alloc();
     view_dispatcher_add_view(mag->view_dispatcher, MagViewPopup, popup_get_view(mag->popup));
     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(
     view_dispatcher_add_view(
         mag->view_dispatcher, MagViewTextInput, text_input_get_view(mag->text_input));
         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) {
 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_name);
     furi_string_free(mag->file_path);
     furi_string_free(mag->file_path);
+    furi_string_free(mag->args);
 
 
     // Mag device
     // Mag device
     mag_device_free(mag->mag_dev);
     mag_device_free(mag->mag_dev);
     mag->mag_dev = NULL;
     mag->mag_dev = NULL;
 
 
-    // Mag setting
-    mag_setting_free(mag->setting);
-    mag->setting = NULL;
-
     // Submenu
     // Submenu
     view_dispatcher_remove_view(mag->view_dispatcher, MagViewSubmenu);
     view_dispatcher_remove_view(mag->view_dispatcher, MagViewSubmenu);
     submenu_free(mag->submenu);
     submenu_free(mag->submenu);
 
 
-    // DialogEx
-    view_dispatcher_remove_view(mag->view_dispatcher, MagViewDialogEx);
-    dialog_ex_free(mag->dialog_ex);
-
     // Popup
     // Popup
     view_dispatcher_remove_view(mag->view_dispatcher, MagViewPopup);
     view_dispatcher_remove_view(mag->view_dispatcher, MagViewPopup);
     popup_free(mag->popup);
     popup_free(mag->popup);
@@ -158,6 +128,10 @@ static void mag_free(Mag* mag) {
     furi_record_close(RECORD_NOTIFICATION);
     furi_record_close(RECORD_NOTIFICATION);
     mag->notifications = NULL;
     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_STORAGE);
     furi_record_close(RECORD_DIALOGS);
     furi_record_close(RECORD_DIALOGS);
 
 
@@ -166,14 +140,14 @@ static void mag_free(Mag* mag) {
 
 
 // entry point for app
 // entry point for app
 int32_t mag_app(void* p) {
 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();
     Mag* mag = mag_alloc();
 
 
+    if(args && strlen(args)) {
+        furi_string_set(mag->args, args);
+    }
+
     mag_make_app_folder(mag);
     mag_make_app_folder(mag);
 
 
     // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering
     // 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);
     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);
     view_dispatcher_run(mag->view_dispatcher);
 
 
@@ -196,10 +180,6 @@ int32_t mag_app(void* p) {
 
 
     mag_free(mag);
     mag_free(mag);
 
 
-    // Return previous state of expansion
-    expansion_enable(expansion);
-    furi_record_close(RECORD_EXPANSION);
-
     return 0;
     return 0;
 }
 }
 
 

+ 28 - 6
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);
     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;
     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);
     FlipperFormat* file = flipper_format_file_alloc(mag_dev->storage);
     FuriString* temp_str;
     FuriString* temp_str;
     temp_str = furi_string_alloc();
     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(temp_str);
+    furi_string_free(filename);
     flipper_format_free(file);
     flipper_format_free(file);
 
 
     return parsed;
     return parsed;
@@ -189,15 +195,10 @@ bool mag_file_select(MagDevice* mag_dev) {
 
 
     furi_string_free(mag_app_folder);
     furi_string_free(mag_app_folder);
     if(res) {
     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);
         res = mag_device_load_data(mag_dev, mag_dev->load_path, true);
         if(res) {
         if(res) {
             mag_device_set_name(mag_dev, mag_dev->dev_name);
             mag_device_set_name(mag_dev, mag_dev->dev_name);
         }
         }
-        furi_string_free(filename);
     }
     }
 
 
     return res;
     return res;
@@ -300,6 +301,27 @@ bool mag_device_parse_card_string(MagDevice* mag_dev, FuriString* f_card_str) {
     return true;
     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(
 void mag_device_set_loading_callback(
     MagDevice* mag_dev,
     MagDevice* mag_dev,
     MagLoadingCallback callback,
     MagLoadingCallback callback,

+ 5 - 0
magspoof/mag_device.h

@@ -6,6 +6,7 @@
 #include <dialogs/dialogs.h>
 #include <dialogs/dialogs.h>
 
 
 #include "mag_icons.h"
 #include "mag_icons.h"
+#include "helpers/mag_types.h"
 
 
 #include <assets_icons.h>
 #include <assets_icons.h>
 
 
@@ -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_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);
 bool mag_file_select(MagDevice* mag_dev);
 
 
 void mag_device_data_clear(MagDeviceData* dev_data);
 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);
 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(
 void mag_device_set_loading_callback(
     MagDevice* mag_dev,
     MagDevice* mag_dev,
     MagLoadingCallback callback,
     MagLoadingCallback callback,

+ 15 - 10
magspoof/mag_i.h

@@ -1,6 +1,7 @@
 #pragma once
 #pragma once
 
 
 #include "mag_device.h"
 #include "mag_device.h"
+#include "mag_state.h"
 //#include "helpers/mag_helpers.h"
 //#include "helpers/mag_helpers.h"
 #include "helpers/mag_types.h"
 #include "helpers/mag_types.h"
 
 
@@ -9,6 +10,7 @@
 #include <furi/core/log.h>
 #include <furi/core/log.h>
 #include <furi_hal_gpio.h>
 #include <furi_hal_gpio.h>
 #include <furi_hal_resources.h>
 #include <furi_hal_resources.h>
+#include <expansion/expansion.h>
 
 
 #include <gui/gui.h>
 #include <gui/gui.h>
 #include <gui/view.h>
 #include <gui/view.h>
@@ -36,20 +38,22 @@
 
 
 #define MAG_TEXT_STORE_SIZE 150
 #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 {
 enum MagCustomEvent {
     MagEventNext = 100,
     MagEventNext = 100,
     MagEventExit,
     MagEventExit,
     MagEventPopupClosed,
     MagEventPopupClosed,
 };
 };
 
 
-typedef struct {
-    MagTxState tx;
-    MagTrackState track;
-    MagReverseState reverse;
-    uint32_t us_clock;
-    uint32_t us_interpacket;
-} MagSetting;
-
 typedef struct {
 typedef struct {
     ViewDispatcher* view_dispatcher;
     ViewDispatcher* view_dispatcher;
     Gui* gui;
     Gui* gui;
@@ -62,12 +66,12 @@ typedef struct {
     char text_store[MAG_TEXT_STORE_SIZE + 1];
     char text_store[MAG_TEXT_STORE_SIZE + 1];
     FuriString* file_path;
     FuriString* file_path;
     FuriString* file_name;
     FuriString* file_name;
+    FuriString* args;
 
 
-    MagSetting* setting;
+    MagState state;
 
 
     // Common views
     // Common views
     Submenu* submenu;
     Submenu* submenu;
-    DialogEx* dialog_ex;
     Popup* popup;
     Popup* popup;
     Loading* loading;
     Loading* loading;
     TextInput* text_input;
     TextInput* text_input;
@@ -75,6 +79,7 @@ typedef struct {
     VariableItemList* variable_item_list;
     VariableItemList* variable_item_list;
 
 
     // UART
     // UART
+    Expansion* expansion;
     FuriThread* uart_rx_thread;
     FuriThread* uart_rx_thread;
     FuriStreamBuffer* uart_rx_stream;
     FuriStreamBuffer* uart_rx_stream;
     uint8_t uart_rx_buf[UART_RX_BUF_SIZE + 1];
     uint8_t uart_rx_buf[UART_RX_BUF_SIZE + 1];

+ 116 - 0
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);
+}

+ 42 - 0
magspoof/mag_state.h

@@ -0,0 +1,42 @@
+#pragma once
+
+#include <string.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <furi_hal_resources.h>
+#include <furi_hal_rtc.h>
+
+#include <storage/storage.h>
+#include <flipper_format/flipper_format.h>
+
+#include "helpers/mag_types.h"
+
+#define MAG_STATE_HEADER "Mag State"
+#define MAG_STATE_VER 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);

+ 3 - 2
magspoof/scenes/mag_scene_config.h

@@ -1,5 +1,7 @@
 ADD_SCENE(mag, start, Start)
 ADD_SCENE(mag, start, Start)
 ADD_SCENE(mag, about, About)
 ADD_SCENE(mag, about, About)
+ADD_SCENE(mag, read, Read)
+ADD_SCENE(mag, settings, Settings)
 ADD_SCENE(mag, emulate, Emulate)
 ADD_SCENE(mag, emulate, Emulate)
 ADD_SCENE(mag, emulate_config, EmulateConfig)
 ADD_SCENE(mag, emulate_config, EmulateConfig)
 ADD_SCENE(mag, file_select, FileSelect)
 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_success, DeleteSuccess)
 ADD_SCENE(mag, delete_confirm, DeleteConfirm)
 ADD_SCENE(mag, delete_confirm, DeleteConfirm)
 ADD_SCENE(mag, exit_confirm, ExitConfirm)
 ADD_SCENE(mag, exit_confirm, ExitConfirm)
-ADD_SCENE(mag, under_construction, UnderConstruction)
-ADD_SCENE(mag, read, Read)
+ADD_SCENE(mag, under_construction, UnderConstruction)

+ 2 - 2
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));
         widget, 13, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp_str));
     furi_string_reset(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
     // print relevant data
     uint8_t cat_count = 0;
     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.
         // still messy / dumb way to do this, but slightly cleaner than before.
         // will clean up more later
         // will clean up more later
-        switch(mag->setting->track) {
+        switch(mag->state.track) {
         case MagTrackStateOne:
         case MagTrackStateOne:
             if(i == 0) cat_trackstr(tmp_str, cat_count++, i, trackstr);
             if(i == 0) cat_trackstr(tmp_str, cat_count++, i, trackstr);
             break;
             break;

+ 35 - 28
magspoof/scenes/mag_scene_emulate_config.c

@@ -2,12 +2,12 @@
 
 
 #define TAG "MagSceneEmulateConfig"
 #define TAG "MagSceneEmulateConfig"
 
 
-enum MagSettingIndex {
-    MagSettingIndexTx,
-    MagSettingIndexTrack,
-    MagSettingIndexReverse,
-    MagSettingIndexClock,
-    MagSettingIndexInterpacket,
+enum MagEmulateConfigIndex {
+    MagEmulateConfigIndexTx,
+    MagEmulateConfigIndexTrack,
+    MagEmulateConfigIndexReverse,
+    MagEmulateConfigIndexClock,
+    MagEmulateConfigIndexInterpacket,
 };
 };
 
 
 #define TX_COUNT 7
 #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]);
     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) {
 static void mag_scene_emulate_config_set_track(VariableItem* item) {
     Mag* mag = variable_item_get_context(item);
     Mag* mag = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(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]);
         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(
         variable_item_set_current_value_index(
             item, value_index_uint32(MagTrackStateOneAndTwo, track_value, TRACK_COUNT));
             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);
     Mag* mag = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(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
         // only allow reverse track to be set when playing both 1 and 2
         variable_item_set_current_value_text(item, reverse_text[index]);
         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, "%s", reverse_text[index]);
         //FURI_LOG_D(TAG, "%d", mag->setting->reverse);
         //FURI_LOG_D(TAG, "%d", mag->setting->reverse);
     } else {
     } 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]);
     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) {
 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]);
     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) {
 void mag_scene_emulate_config_on_enter(void* context) {
@@ -188,18 +188,18 @@ void mag_scene_emulate_config_on_enter(void* context) {
     VariableItem* item;
     VariableItem* item;
     uint8_t value_index;
     uint8_t value_index;
 
 
-    // TX
+    // Clock
     item = variable_item_list_add(
     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);
     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_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
     // Track
     item = variable_item_list_add(
     item = variable_item_list_add(
         mag->variable_item_list, "Track:", TRACK_COUNT, mag_scene_emulate_config_set_track, mag);
         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);
     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_index(item, value_index);
     variable_item_set_current_value_text(item, track_text[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,
         REVERSE_COUNT,
         mag_scene_emulate_config_set_reverse,
         mag_scene_emulate_config_set_reverse,
         mag);
         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);
     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_index(item, value_index);
     variable_item_set_current_value_text(item, reverse_text[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
     // Interpacket
     /*
     /*
     item = variable_item_list_add(
     item = variable_item_list_add(

+ 4 - 1
magspoof/scenes/mag_scene_file_select.c

@@ -3,9 +3,12 @@
 
 
 void mag_scene_file_select_on_enter(void* context) {
 void mag_scene_file_select_on_enter(void* context) {
     Mag* mag = context;
     Mag* mag = context;
-    //UNUSED(mag);
     mag_device_set_loading_callback(mag->mag_dev, mag_show_loading_popup, mag);
     mag_device_set_loading_callback(mag->mag_dev, mag_show_loading_popup, mag);
     if(mag_file_select(mag->mag_dev)) {
     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);
         scene_manager_next_scene(mag->scene_manager, MagSceneSavedMenu);
     } else {
     } else {
         scene_manager_search_and_switch_to_previous_scene(mag->scene_manager, MagSceneStart);
         scene_manager_search_and_switch_to_previous_scene(mag->scene_manager, MagSceneStart);

+ 4 - 4
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);
     bool is_empty_t3 = furi_string_empty(mag->mag_dev->dev_data.track[2].str);
 
 
     if(!is_empty_t1 && !is_empty_t2) {
     if(!is_empty_t1 && !is_empty_t2) {
-        mag->setting->track = MagTrackStateOneAndTwo;
+        mag->state.track = MagTrackStateOneAndTwo;
     } else if(!is_empty_t1) {
     } else if(!is_empty_t1) {
-        mag->setting->track = MagTrackStateOne;
+        mag->state.track = MagTrackStateOne;
     } else if(!is_empty_t2) {
     } else if(!is_empty_t2) {
-        mag->setting->track = MagTrackStateTwo;
+        mag->state.track = MagTrackStateTwo;
     } else if(!is_empty_t3) {
     } else if(!is_empty_t3) {
-        mag->setting->track = MagTrackStateThree;
+        mag->state.track = MagTrackStateThree;
     } // TODO: what happens if no track data present?
     } // TODO: what happens if no track data present?
 
 
     submenu_add_item(
     submenu_add_item(

+ 177 - 0
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);
+}

+ 16 - 1
magspoof/scenes/mag_scene_start.c

@@ -4,6 +4,7 @@ typedef enum {
     SubmenuIndexSaved,
     SubmenuIndexSaved,
     SubmenuIndexRead,
     SubmenuIndexRead,
     //SubmenuIndexAddManually,
     //SubmenuIndexAddManually,
+    SubmenuIndexSettings,
     SubmenuIndexAbout,
     SubmenuIndexAbout,
 } SubmenuIndex;
 } SubmenuIndex;
 
 
@@ -18,9 +19,19 @@ void mag_scene_start_on_enter(void* context) {
     Submenu* submenu = mag->submenu;
     Submenu* submenu = mag->submenu;
 
 
     submenu_add_item(submenu, "Saved", SubmenuIndexSaved, mag_scene_start_submenu_callback, mag);
     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_item(
     //    submenu, "Add Manually", SubmenuIndexAddManually, mag_scene_start_submenu_callback, mag);
     //    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_add_item(submenu, "About", SubmenuIndexAbout, mag_scene_start_submenu_callback, mag);
 
 
     submenu_set_selected_item(
     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);
         //    scene_manager_next_scene(mag->scene_manager, MagSceneInputValue);
         //    consumed = true;
         //    consumed = true;
         //    break;
         //    break;
+        case SubmenuIndexSettings:
+            scene_manager_next_scene(mag->scene_manager, MagSceneSettings);
+            consumed = true;
+            break;
         case SubmenuIndexAbout:
         case SubmenuIndexAbout:
             scene_manager_next_scene(mag->scene_manager, MagSceneAbout);
             scene_manager_next_scene(mag->scene_manager, MagSceneAbout);
             consumed = true;
             consumed = true;