mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-11 06:09:08 -07:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
@@ -229,17 +229,17 @@ typedef struct {
|
||||
size_t pos;
|
||||
} SubGhzHalAsyncTxTest;
|
||||
|
||||
#define SUBGHZ_HAL_TEST_DURATION 1
|
||||
#define SUBGHZ_HAL_TEST_DURATION 3
|
||||
|
||||
static LevelDuration subghz_hal_async_tx_test_yield(void* context) {
|
||||
SubGhzHalAsyncTxTest* test = context;
|
||||
bool is_odd = test->pos % 2;
|
||||
|
||||
if(test->type == SubGhzHalAsyncTxTestTypeNormal) {
|
||||
if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
test->pos++;
|
||||
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
|
||||
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
test->pos++;
|
||||
return level_duration_reset();
|
||||
} else {
|
||||
@@ -249,36 +249,36 @@ static LevelDuration subghz_hal_async_tx_test_yield(void* context) {
|
||||
if(test->pos == 0) {
|
||||
test->pos++;
|
||||
return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION);
|
||||
} else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
} else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
test->pos++;
|
||||
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
|
||||
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
test->pos++;
|
||||
return level_duration_reset();
|
||||
} else {
|
||||
furi_crash("Yield after reset");
|
||||
}
|
||||
} else if(test->type == SubGhzHalAsyncTxTestTypeInvalidMid) {
|
||||
if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
|
||||
if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
|
||||
test->pos++;
|
||||
return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION);
|
||||
} else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
} else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
test->pos++;
|
||||
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
|
||||
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
test->pos++;
|
||||
return level_duration_reset();
|
||||
} else {
|
||||
furi_crash("Yield after reset");
|
||||
}
|
||||
} else if(test->type == SubGhzHalAsyncTxTestTypeInvalidEnd) {
|
||||
if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
|
||||
if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
|
||||
test->pos++;
|
||||
return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION);
|
||||
} else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
} else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
test->pos++;
|
||||
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
|
||||
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
|
||||
test->pos++;
|
||||
return level_duration_reset();
|
||||
} else {
|
||||
@@ -292,20 +292,20 @@ static LevelDuration subghz_hal_async_tx_test_yield(void* context) {
|
||||
furi_crash("Yield after reset");
|
||||
}
|
||||
} else if(test->type == SubGhzHalAsyncTxTestTypeResetMid) {
|
||||
if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
|
||||
if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
|
||||
test->pos++;
|
||||
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
|
||||
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
|
||||
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
|
||||
test->pos++;
|
||||
return level_duration_reset();
|
||||
} else {
|
||||
furi_crash("Yield after reset");
|
||||
}
|
||||
} else if(test->type == SubGhzHalAsyncTxTestTypeResetEnd) {
|
||||
if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
|
||||
if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL) {
|
||||
test->pos++;
|
||||
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
|
||||
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
|
||||
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL) {
|
||||
test->pos++;
|
||||
return level_duration_reset();
|
||||
} else {
|
||||
@@ -332,6 +332,8 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) {
|
||||
|
||||
while(!furi_hal_subghz_is_async_tx_complete()) {
|
||||
if(furi_hal_cortex_timer_is_expired(timer)) {
|
||||
furi_hal_subghz_stop_async_tx();
|
||||
furi_hal_subghz_sleep();
|
||||
return false;
|
||||
}
|
||||
furi_delay_ms(10);
|
||||
|
||||
@@ -17,18 +17,18 @@
|
||||
|
||||
#define TAG "SubGhzDeviceCc1101Ext"
|
||||
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO (&gpio_ext_pb2)
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO &gpio_ext_pc3
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_FORCE_DANGEROUS_RANGE false
|
||||
|
||||
#define SUBGHZ_DEVICE_CC1101_CONFIG_VER 1
|
||||
|
||||
/* DMA Channels definition */
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA DMA2
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL LL_DMA_CHANNEL_3
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL LL_DMA_CHANNEL_4
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL LL_DMA_CHANNEL_5
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ FuriHalInterruptIdDma2Ch3
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA (DMA2)
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL (LL_DMA_CHANNEL_3)
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL (LL_DMA_CHANNEL_4)
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL (LL_DMA_CHANNEL_5)
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ (FuriHalInterruptIdDma2Ch3)
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF \
|
||||
SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF \
|
||||
@@ -37,10 +37,10 @@
|
||||
SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL
|
||||
|
||||
/** Low level buffer dimensions and guard times */
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256)
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256u)
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF \
|
||||
(SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL / 2)
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME 999 << 1
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME (999u >> 1)
|
||||
|
||||
/** SubGhz state */
|
||||
typedef enum {
|
||||
@@ -58,13 +58,25 @@ typedef enum {
|
||||
SubGhzDeviceCC1101ExtRegulationTxRx, /**TxRx*/
|
||||
} SubGhzDeviceCC1101ExtRegulation;
|
||||
|
||||
typedef enum {
|
||||
SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle,
|
||||
SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset,
|
||||
SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun,
|
||||
} SubGhzDeviceCC1101ExtAsyncTxMiddlewareState;
|
||||
|
||||
typedef struct {
|
||||
SubGhzDeviceCC1101ExtAsyncTxMiddlewareState state;
|
||||
bool is_odd_level;
|
||||
uint32_t adder_duration;
|
||||
} SubGhzDeviceCC1101ExtAsyncTxMiddleware;
|
||||
|
||||
typedef struct {
|
||||
uint32_t* buffer;
|
||||
LevelDuration carry_ld;
|
||||
SubGhzDeviceCC1101ExtCallback callback;
|
||||
void* callback_context;
|
||||
uint32_t gpio_tx_buff[2];
|
||||
uint32_t debug_gpio_buff[2];
|
||||
SubGhzDeviceCC1101ExtAsyncTxMiddleware middleware;
|
||||
} SubGhzDeviceCC1101ExtAsyncTx;
|
||||
|
||||
typedef struct {
|
||||
@@ -283,8 +295,8 @@ void subghz_device_cc1101_ext_dump_state() {
|
||||
|
||||
void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data) {
|
||||
//load config
|
||||
subghz_device_cc1101_ext_reset();
|
||||
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
uint32_t i = 0;
|
||||
uint8_t pa[8] = {0};
|
||||
while(preset_data[i]) {
|
||||
@@ -313,8 +325,8 @@ void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data) {
|
||||
}
|
||||
|
||||
void subghz_device_cc1101_ext_load_registers(const uint8_t* data) {
|
||||
subghz_device_cc1101_ext_reset();
|
||||
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
uint32_t i = 0;
|
||||
while(data[i]) {
|
||||
cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, data[i], data[i + 1]);
|
||||
@@ -396,6 +408,7 @@ void subghz_device_cc1101_ext_reset() {
|
||||
furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
// Warning: push pull cc1101 clock output on GD0
|
||||
cc1101_write_reg(
|
||||
subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance);
|
||||
furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
@@ -616,50 +629,91 @@ void subghz_device_cc1101_ext_stop_async_rx() {
|
||||
furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
}
|
||||
|
||||
static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t samples) {
|
||||
furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx);
|
||||
while(samples > 0) {
|
||||
bool is_odd = samples % 2;
|
||||
LevelDuration ld;
|
||||
if(level_duration_is_reset(subghz_device_cc1101_ext->async_tx.carry_ld)) {
|
||||
ld = subghz_device_cc1101_ext->async_tx.callback(
|
||||
subghz_device_cc1101_ext->async_tx.callback_context);
|
||||
} else {
|
||||
ld = subghz_device_cc1101_ext->async_tx.carry_ld;
|
||||
subghz_device_cc1101_ext->async_tx.carry_ld = level_duration_reset();
|
||||
void subghz_device_cc1101_ext_async_tx_middleware_idle(
|
||||
SubGhzDeviceCC1101ExtAsyncTxMiddleware* middleware) {
|
||||
middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle;
|
||||
middleware->is_odd_level = false;
|
||||
middleware->adder_duration = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME;
|
||||
}
|
||||
|
||||
static inline uint32_t subghz_device_cc1101_ext_async_tx_middleware_get_duration(
|
||||
SubGhzDeviceCC1101ExtAsyncTxMiddleware* middleware,
|
||||
SubGhzDeviceCC1101ExtCallback callback) {
|
||||
uint32_t ret = 0;
|
||||
bool is_level = false;
|
||||
|
||||
if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset) return 0;
|
||||
|
||||
while(1) {
|
||||
LevelDuration ld = callback(subghz_device_cc1101_ext->async_tx.callback_context);
|
||||
if(level_duration_is_reset(ld)) {
|
||||
middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset;
|
||||
if(!middleware->is_odd_level) {
|
||||
return 0;
|
||||
} else {
|
||||
return middleware->adder_duration;
|
||||
}
|
||||
} else if(level_duration_is_wait(ld)) {
|
||||
middleware->is_odd_level = !middleware->is_odd_level;
|
||||
ret = middleware->adder_duration + SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME;
|
||||
middleware->adder_duration = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if(level_duration_is_wait(ld)) {
|
||||
*buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME;
|
||||
buffer++;
|
||||
samples--;
|
||||
} else if(level_duration_is_reset(ld)) {
|
||||
is_level = level_duration_get_level(ld);
|
||||
|
||||
if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle) {
|
||||
if(is_level != middleware->is_odd_level) {
|
||||
middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun;
|
||||
middleware->is_odd_level = is_level;
|
||||
middleware->adder_duration = level_duration_get_duration(ld);
|
||||
return SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun) {
|
||||
if(is_level == middleware->is_odd_level) {
|
||||
middleware->adder_duration += level_duration_get_duration(ld);
|
||||
continue;
|
||||
} else {
|
||||
middleware->is_odd_level = is_level;
|
||||
ret = middleware->adder_duration;
|
||||
middleware->adder_duration = level_duration_get_duration(ld);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t samples) {
|
||||
furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx);
|
||||
|
||||
while(samples > 0) {
|
||||
volatile uint32_t duration = subghz_device_cc1101_ext_async_tx_middleware_get_duration(
|
||||
&subghz_device_cc1101_ext->async_tx.middleware,
|
||||
subghz_device_cc1101_ext->async_tx.callback);
|
||||
if(duration == 0) {
|
||||
*buffer = 0;
|
||||
buffer++;
|
||||
samples--;
|
||||
LL_DMA_DisableIT_HT(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF);
|
||||
LL_DMA_DisableIT_TC(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF);
|
||||
if(LL_DMA_IsActiveFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) {
|
||||
LL_DMA_ClearFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA);
|
||||
}
|
||||
if(LL_DMA_IsActiveFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) {
|
||||
LL_DMA_ClearFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA);
|
||||
}
|
||||
LL_TIM_EnableIT_UPDATE(TIM17);
|
||||
break;
|
||||
} else {
|
||||
bool level = level_duration_get_level(ld);
|
||||
|
||||
// Inject guard time if level is incorrect
|
||||
if(is_odd != level) {
|
||||
*buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME;
|
||||
buffer++;
|
||||
samples--;
|
||||
|
||||
// Special case: prevent buffer overflow if sample is last
|
||||
if(samples == 0) {
|
||||
subghz_device_cc1101_ext->async_tx.carry_ld = ld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t duration = level_duration_get_duration(ld);
|
||||
furi_assert(duration > 0);
|
||||
*buffer = duration >> 1;
|
||||
// Lowest possible value is 4us
|
||||
if(duration < 4) duration = 4;
|
||||
// Divide by 2 since timer resolution is 2us
|
||||
// Subtract 1 since we counting from 0
|
||||
*buffer = (duration >> 1) - 1;
|
||||
buffer++;
|
||||
samples--;
|
||||
}
|
||||
@@ -691,12 +745,14 @@ static void subghz_device_cc1101_ext_async_tx_dma_isr() {
|
||||
static void subghz_device_cc1101_ext_async_tx_timer_isr() {
|
||||
if(LL_TIM_IsActiveFlag_UPDATE(TIM17)) {
|
||||
if(LL_TIM_GetAutoReload(TIM17) == 0) {
|
||||
LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF);
|
||||
furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false);
|
||||
if(subghz_device_cc1101_ext->async_mirror_pin != NULL)
|
||||
furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, false);
|
||||
LL_TIM_DisableCounter(TIM17);
|
||||
subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncTxEnd;
|
||||
if(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx) {
|
||||
LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF);
|
||||
subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncTxEnd;
|
||||
furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false);
|
||||
if(subghz_device_cc1101_ext->async_mirror_pin != NULL)
|
||||
furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, false);
|
||||
LL_TIM_DisableCounter(TIM17);
|
||||
}
|
||||
}
|
||||
LL_TIM_ClearFlag_UPDATE(TIM17);
|
||||
}
|
||||
@@ -746,16 +802,18 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb
|
||||
|
||||
// Configure TIM
|
||||
// Set the timer resolution to 2 us
|
||||
LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1);
|
||||
LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP);
|
||||
LL_TIM_SetAutoReload(TIM17, 0xFFFF);
|
||||
LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1);
|
||||
LL_TIM_SetAutoReload(TIM17, 500);
|
||||
LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1);
|
||||
LL_TIM_SetClockSource(TIM17, LL_TIM_CLOCKSOURCE_INTERNAL);
|
||||
LL_TIM_DisableARRPreload(TIM17);
|
||||
|
||||
furi_hal_interrupt_set_isr(
|
||||
FuriHalInterruptIdTim1TrgComTim17, subghz_device_cc1101_ext_async_tx_timer_isr, NULL);
|
||||
|
||||
subghz_device_cc1101_ext_async_tx_middleware_idle(
|
||||
&subghz_device_cc1101_ext->async_tx.middleware);
|
||||
subghz_device_cc1101_ext_async_tx_refill(
|
||||
subghz_device_cc1101_ext->async_tx.buffer, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL);
|
||||
|
||||
@@ -801,7 +859,6 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb
|
||||
|
||||
// Start counter
|
||||
LL_TIM_EnableDMAReq_UPDATE(TIM17);
|
||||
LL_TIM_GenerateEvent_UPDATE(TIM17);
|
||||
|
||||
subghz_device_cc1101_ext_tx();
|
||||
|
||||
@@ -820,11 +877,15 @@ void subghz_device_cc1101_ext_stop_async_tx() {
|
||||
subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx ||
|
||||
subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd);
|
||||
|
||||
// Deinitialize GPIO
|
||||
furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false);
|
||||
furi_hal_gpio_init(
|
||||
subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
|
||||
|
||||
// Shutdown radio
|
||||
subghz_device_cc1101_ext_idle();
|
||||
|
||||
// Deinitialize Timer
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_hal_bus_disable(FuriHalBusTIM17);
|
||||
furi_hal_interrupt_set_isr(FuriHalInterruptIdTim1TrgComTim17, NULL, NULL);
|
||||
|
||||
@@ -833,17 +894,11 @@ void subghz_device_cc1101_ext_stop_async_tx() {
|
||||
LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF);
|
||||
furi_hal_interrupt_set_isr(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ, NULL, NULL);
|
||||
|
||||
// Deinitialize GPIO
|
||||
furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false);
|
||||
furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
// Stop debug
|
||||
if(subghz_device_cc1101_ext_stop_debug()) {
|
||||
LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF);
|
||||
}
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
|
||||
free(subghz_device_cc1101_ext->async_tx.buffer);
|
||||
|
||||
subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle;
|
||||
|
||||
@@ -4,27 +4,9 @@
|
||||
static void lfrfid_clear_t5577_password_and_config_to_EM(LfRfid* app) {
|
||||
Popup* popup = app->popup;
|
||||
char curr_buf[32] = {};
|
||||
//TODO: use .txt file in resources for passwords.
|
||||
const uint32_t default_passwords[] = {
|
||||
0x51243648, 0x000D8787, 0x19920427, 0x50524F58, 0xF9DCEBA0, 0x65857569, 0x05D73B9F,
|
||||
0x89A69E60, 0x314159E0, 0xAA55BBBB, 0xA5B4C3D2, 0x1C0B5848, 0x00434343, 0x444E4752,
|
||||
0x4E457854, 0x44B44CAE, 0x88661858, 0xE9920427, 0x575F4F4B, 0x50520901, 0x20206666,
|
||||
0x65857569, 0x5469616E, 0x7686962A, 0xC0F5009A, 0x07CEE75D, 0xfeedbeef, 0xdeadc0de,
|
||||
0x00000000, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666,
|
||||
0x77777777, 0x88888888, 0x99999999, 0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD,
|
||||
0xEEEEEEEE, 0xFFFFFFFF, 0xa0a1a2a3, 0xb0b1b2b3, 0x50415353, 0x00000001, 0x00000002,
|
||||
0x0000000a, 0x0000000b, 0x01020304, 0x02030405, 0x03040506, 0x04050607, 0x05060708,
|
||||
0x06070809, 0x0708090A, 0x08090A0B, 0x090A0B0C, 0x0A0B0C0D, 0x0B0C0D0E, 0x0C0D0E0F,
|
||||
0x01234567, 0x12345678, 0x10000000, 0x20000000, 0x30000000, 0x40000000, 0x50000000,
|
||||
0x60000000, 0x70000000, 0x80000000, 0x90000000, 0xA0000000, 0xB0000000, 0xC0000000,
|
||||
0xD0000000, 0xE0000000, 0xF0000000, 0x10101010, 0x01010101, 0x11223344, 0x22334455,
|
||||
0x33445566, 0x44556677, 0x55667788, 0x66778899, 0x778899AA, 0x8899AABB, 0x99AABBCC,
|
||||
0xAABBCCDD, 0xBBCCDDEE, 0xCCDDEEFF, 0x0CB7E7FC, 0xFABADA11, 0x87654321, 0x12341234,
|
||||
0x69696969, 0x12121212, 0x12344321, 0x1234ABCD, 0x11112222, 0x13131313, 0x10041004,
|
||||
0x31415926, 0xabcd1234, 0x20002000, 0x19721972, 0xaa55aa55, 0x55aa55aa, 0x4f271149,
|
||||
0x07d7bb0b, 0x9636ef8f, 0xb5f44686, 0x9E3779B9, 0xC6EF3720, 0x7854794A, 0xF1EA5EED,
|
||||
0x69314718, 0x57721566, 0x93C467E3, 0x27182818, 0x50415353};
|
||||
const uint8_t default_passwords_len = sizeof(default_passwords) / sizeof(uint32_t);
|
||||
|
||||
uint8_t default_passwords_len;
|
||||
const uint32_t* default_passwords = t5577_get_default_passwords(&default_passwords_len);
|
||||
|
||||
popup_set_header(popup, "Removing\npassword", 90, 36, AlignCenter, AlignCenter);
|
||||
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
|
||||
|
||||
@@ -6,6 +6,7 @@ ADD_SCENE(lfrfid, exit_confirm, ExitConfirm)
|
||||
ADD_SCENE(lfrfid, delete_confirm, DeleteConfirm)
|
||||
ADD_SCENE(lfrfid, read_key_menu, ReadKeyMenu)
|
||||
ADD_SCENE(lfrfid, write, Write)
|
||||
ADD_SCENE(lfrfid, write_with_pass, WriteWithPass)
|
||||
ADD_SCENE(lfrfid, write_success, WriteSuccess)
|
||||
ADD_SCENE(lfrfid, emulate, Emulate)
|
||||
ADD_SCENE(lfrfid, save_name, SaveName)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
typedef enum {
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexWrite,
|
||||
SubmenuIndexWriteWithPass,
|
||||
SubmenuIndexEdit,
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexInfo,
|
||||
@@ -23,6 +24,12 @@ void lfrfid_scene_saved_key_menu_on_enter(void* context) {
|
||||
submenu, "Emulate", SubmenuIndexEmulate, lfrfid_scene_saved_key_menu_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
submenu, "Write", SubmenuIndexWrite, lfrfid_scene_saved_key_menu_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Write with pass",
|
||||
SubmenuIndexWriteWithPass,
|
||||
lfrfid_scene_saved_key_menu_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
submenu, "Edit", SubmenuIndexEdit, lfrfid_scene_saved_key_menu_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
@@ -48,6 +55,9 @@ bool lfrfid_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event
|
||||
} else if(event.event == SubmenuIndexWrite) {
|
||||
scene_manager_next_scene(app->scene_manager, LfRfidSceneWrite);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWriteWithPass) {
|
||||
scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteWithPass);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexEdit) {
|
||||
scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveData);
|
||||
consumed = true;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "../lfrfid_i.h"
|
||||
|
||||
static void lfrfid_write_with_pass_callback(LFRFIDWorkerWriteResult result, void* context) {
|
||||
LfRfid* app = context;
|
||||
uint32_t event = 0;
|
||||
|
||||
if(result == LFRFIDWorkerWriteOK) {
|
||||
event = LfRfidEventWriteOK;
|
||||
} else if(result == LFRFIDWorkerWriteProtocolCannotBeWritten) {
|
||||
event = LfRfidEventWriteProtocolCannotBeWritten;
|
||||
} else if(result == LFRFIDWorkerWriteFobCannotBeWritten) {
|
||||
event = LfRfidEventWriteFobCannotBeWritten;
|
||||
} else if(result == LFRFIDWorkerWriteTooLongToWrite) {
|
||||
event = LfRfidEventWriteTooLongToWrite;
|
||||
}
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void lfrfid_scene_write_with_pass_on_enter(void* context) {
|
||||
LfRfid* app = context;
|
||||
Popup* popup = app->popup;
|
||||
|
||||
popup_set_header(popup, "Writing", 89, 30, AlignCenter, AlignTop);
|
||||
if(!furi_string_empty(app->file_name)) {
|
||||
popup_set_text(popup, furi_string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop);
|
||||
} else {
|
||||
popup_set_text(
|
||||
popup,
|
||||
protocol_dict_get_name(app->dict, app->protocol_id),
|
||||
89,
|
||||
43,
|
||||
AlignCenter,
|
||||
AlignTop);
|
||||
}
|
||||
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
|
||||
|
||||
size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
|
||||
protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size);
|
||||
|
||||
lfrfid_worker_start_thread(app->lfworker);
|
||||
lfrfid_worker_write_with_pass_start(
|
||||
app->lfworker, (LFRFIDProtocol)app->protocol_id, lfrfid_write_with_pass_callback, app);
|
||||
notification_message(app->notifications, &sequence_blink_start_magenta);
|
||||
}
|
||||
|
||||
bool lfrfid_scene_write_with_pass_on_event(void* context, SceneManagerEvent event) {
|
||||
LfRfid* app = context;
|
||||
Popup* popup = app->popup;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == LfRfidEventWriteOK) {
|
||||
notification_message(app->notifications, &sequence_success);
|
||||
scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSuccess);
|
||||
consumed = true;
|
||||
} else if(event.event == LfRfidEventWriteProtocolCannotBeWritten) {
|
||||
popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42);
|
||||
popup_set_header(popup, "Error", 64, 3, AlignCenter, AlignTop);
|
||||
popup_set_text(popup, "This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop);
|
||||
notification_message(app->notifications, &sequence_blink_start_red);
|
||||
consumed = true;
|
||||
} else if(
|
||||
(event.event == LfRfidEventWriteFobCannotBeWritten) ||
|
||||
(event.event == LfRfidEventWriteTooLongToWrite)) {
|
||||
popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42);
|
||||
popup_set_header(popup, "Still trying to write...", 64, 3, AlignCenter, AlignTop);
|
||||
popup_set_text(
|
||||
popup,
|
||||
"Make sure this\ncard is writable\nand not\nprotected.",
|
||||
3,
|
||||
17,
|
||||
AlignLeft,
|
||||
AlignTop);
|
||||
notification_message(app->notifications, &sequence_blink_start_yellow);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void lfrfid_scene_write_with_pass_on_exit(void* context) {
|
||||
LfRfid* app = context;
|
||||
notification_message(app->notifications, &sequence_blink_stop);
|
||||
popup_reset(app->popup);
|
||||
lfrfid_worker_stop(app->lfworker);
|
||||
lfrfid_worker_stop_thread(app->lfworker);
|
||||
|
||||
size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
|
||||
protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size);
|
||||
}
|
||||
@@ -146,6 +146,15 @@ App(
|
||||
sources=["plugins/supported_cards/zolotaya_korona.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="zolotaya_korona_online_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="zolotaya_korona_online_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/zolotaya_korona_online.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="hid_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
@@ -164,6 +173,15 @@ App(
|
||||
sources=["plugins/supported_cards/washcity.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="ndef_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="ndef_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/ndef.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="nfc_start",
|
||||
targets=["f7"],
|
||||
|
||||
@@ -328,6 +328,8 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
last_trip.minute);
|
||||
}
|
||||
|
||||
furi_string_free(tariff_name);
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
|
||||
475
applications/main/nfc/plugins/supported_cards/ndef.c
Normal file
475
applications/main/nfc/plugins/supported_cards/ndef.c
Normal file
@@ -0,0 +1,475 @@
|
||||
// Parser for NDEF format data
|
||||
// Supports multiple NDEF messages and records in same tag
|
||||
// Parsed types: URI (+ Phone, Mail), Text, BT MAC, Contact, WiFi, Empty
|
||||
// Documentation and sources indicated where relevant
|
||||
// Made by @Willy-JL
|
||||
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
||||
|
||||
#define TAG "NDEF"
|
||||
|
||||
static bool is_text(const uint8_t* buf, size_t len) {
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
const char c = buf[i];
|
||||
if((c < ' ' || c > '~') && c != '\r' && c != '\n') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
print_data(FuriString* str, const char* prefix, const uint8_t* buf, size_t len, bool force_hex) {
|
||||
if(prefix) furi_string_cat_printf(str, "%s: ", prefix);
|
||||
if(!force_hex && is_text(buf, len)) {
|
||||
char* tmp = malloc(len + 1);
|
||||
memcpy(tmp, buf, len);
|
||||
tmp[len] = '\0';
|
||||
furi_string_cat_printf(str, "%s", tmp);
|
||||
free(tmp);
|
||||
} else {
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
furi_string_cat_printf(str, "%02X ", buf[i]);
|
||||
}
|
||||
}
|
||||
furi_string_cat(str, "\n");
|
||||
}
|
||||
|
||||
static void parse_ndef_uri(FuriString* str, const uint8_t* payload, uint32_t payload_len) {
|
||||
// https://learn.adafruit.com/adafruit-pn532-rfid-nfc/ndef#uri-records-0x55-slash-u-607763
|
||||
const char* prepends[] = {
|
||||
[0x00] = "",
|
||||
[0x01] = "http://www.",
|
||||
[0x02] = "https://www.",
|
||||
[0x03] = "http://",
|
||||
[0x04] = "https://",
|
||||
[0x05] = "tel:",
|
||||
[0x06] = "mailto:",
|
||||
[0x07] = "ftp://anonymous:anonymous@",
|
||||
[0x08] = "ftp://ftp.",
|
||||
[0x09] = "ftps://",
|
||||
[0x0A] = "sftp://",
|
||||
[0x0B] = "smb://",
|
||||
[0x0C] = "nfs://",
|
||||
[0x0D] = "ftp://",
|
||||
[0x0E] = "dav://",
|
||||
[0x0F] = "news:",
|
||||
[0x10] = "telnet://",
|
||||
[0x11] = "imap:",
|
||||
[0x12] = "rtsp://",
|
||||
[0x13] = "urn:",
|
||||
[0x14] = "pop:",
|
||||
[0x15] = "sip:",
|
||||
[0x16] = "sips:",
|
||||
[0x17] = "tftp:",
|
||||
[0x18] = "btspp://",
|
||||
[0x19] = "btl2cap://",
|
||||
[0x1A] = "btgoep://",
|
||||
[0x1B] = "tcpobex://",
|
||||
[0x1C] = "irdaobex://",
|
||||
[0x1D] = "file://",
|
||||
[0x1E] = "urn:epc:id:",
|
||||
[0x1F] = "urn:epc:tag:",
|
||||
[0x20] = "urn:epc:pat:",
|
||||
[0x21] = "urn:epc:raw:",
|
||||
[0x22] = "urn:epc:",
|
||||
[0x23] = "urn:nfc:",
|
||||
};
|
||||
const char* prepend = "";
|
||||
uint8_t prepend_type = payload[0];
|
||||
if(prepend_type < COUNT_OF(prepends)) {
|
||||
prepend = prepends[prepend_type];
|
||||
}
|
||||
size_t prepend_len = strlen(prepend);
|
||||
|
||||
size_t uri_len = prepend_len + (payload_len - 1);
|
||||
char* const uri_buf = malloc(uri_len);
|
||||
memcpy(uri_buf, prepend, prepend_len);
|
||||
memcpy(uri_buf + prepend_len, payload + 1, payload_len - 1);
|
||||
char* uri = uri_buf;
|
||||
|
||||
const char* type = "URI";
|
||||
if(strncmp(uri, "http", strlen("http")) == 0) {
|
||||
type = "URL";
|
||||
} else if(strncmp(uri, "tel:", strlen("tel:")) == 0) {
|
||||
type = "Phone";
|
||||
uri += strlen("tel:");
|
||||
uri_len -= strlen("tel:");
|
||||
} else if(strncmp(uri, "mailto:", strlen("mailto:")) == 0) {
|
||||
type = "Mail";
|
||||
uri += strlen("mailto:");
|
||||
uri_len -= strlen("mailto:");
|
||||
}
|
||||
|
||||
furi_string_cat_printf(str, "%s\n", type);
|
||||
print_data(str, NULL, (uint8_t*)uri, uri_len, false);
|
||||
free(uri_buf);
|
||||
}
|
||||
|
||||
static void parse_ndef_text(FuriString* str, const uint8_t* payload, uint32_t payload_len) {
|
||||
furi_string_cat(str, "Text\n");
|
||||
print_data(str, NULL, payload + 3, payload_len - 3, false);
|
||||
}
|
||||
|
||||
static void parse_ndef_bt(FuriString* str, const uint8_t* payload, uint32_t payload_len) {
|
||||
furi_string_cat(str, "BT MAC\n");
|
||||
print_data(str, NULL, payload + 2, payload_len - 2, true);
|
||||
}
|
||||
|
||||
static void parse_ndef_vcard(FuriString* str, const uint8_t* payload, uint32_t payload_len) {
|
||||
char* tmp = malloc(payload_len + 1);
|
||||
memcpy(tmp, payload, payload_len);
|
||||
tmp[payload_len] = '\0';
|
||||
FuriString* fmt = furi_string_alloc_set(tmp);
|
||||
free(tmp);
|
||||
|
||||
furi_string_trim(fmt);
|
||||
if(furi_string_start_with(fmt, "BEGIN:VCARD")) {
|
||||
furi_string_right(fmt, furi_string_search_char(fmt, '\n'));
|
||||
if(furi_string_end_with(fmt, "END:VCARD")) {
|
||||
furi_string_left(fmt, furi_string_search_rchar(fmt, '\n'));
|
||||
}
|
||||
furi_string_trim(fmt);
|
||||
if(furi_string_start_with(fmt, "VERSION:")) {
|
||||
furi_string_right(fmt, furi_string_search_char(fmt, '\n'));
|
||||
furi_string_trim(fmt);
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_cat(str, "Contact\n");
|
||||
print_data(str, NULL, (uint8_t*)furi_string_get_cstr(fmt), furi_string_size(fmt), false);
|
||||
furi_string_free(fmt);
|
||||
}
|
||||
|
||||
static void parse_ndef_wifi(FuriString* str, const uint8_t* payload, uint32_t payload_len) {
|
||||
// https://android.googlesource.com/platform/packages/apps/Nfc/+/refs/heads/main/src/com/android/nfc/NfcWifiProtectedSetup.java
|
||||
#define CREDENTIAL_FIELD_ID (0x100E)
|
||||
#define SSID_FIELD_ID (0x1045)
|
||||
#define NETWORK_KEY_FIELD_ID (0x1027)
|
||||
#define AUTH_TYPE_FIELD_ID (0x1003)
|
||||
#define AUTH_TYPE_EXPECTED_SIZE (2)
|
||||
#define AUTH_TYPE_OPEN (0x0001)
|
||||
#define AUTH_TYPE_WPA_PSK (0x0002)
|
||||
#define AUTH_TYPE_WPA_EAP (0x0008)
|
||||
#define AUTH_TYPE_WPA2_EAP (0x0010)
|
||||
#define AUTH_TYPE_WPA2_PSK (0x0020)
|
||||
#define AUTH_TYPE_WPA_AND_WPA2_PSK (0x0022)
|
||||
#define MAX_NETWORK_KEY_SIZE_BYTES (64)
|
||||
|
||||
size_t i = 0;
|
||||
while(i < payload_len) {
|
||||
uint16_t field_id = nfc_util_bytes2num(payload + i, 2);
|
||||
i += 2;
|
||||
uint16_t field_len = nfc_util_bytes2num(payload + i, 2);
|
||||
i += 2;
|
||||
|
||||
if(field_id == CREDENTIAL_FIELD_ID) {
|
||||
furi_string_cat(str, "WiFi\n");
|
||||
size_t start_position = i;
|
||||
while(i < start_position + field_len) {
|
||||
uint16_t cfg_id = nfc_util_bytes2num(payload + i, 2);
|
||||
i += 2;
|
||||
uint16_t cfg_len = nfc_util_bytes2num(payload + i, 2);
|
||||
i += 2;
|
||||
|
||||
if(i + cfg_len > start_position + field_len) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch(cfg_id) {
|
||||
case SSID_FIELD_ID:
|
||||
print_data(str, "SSID", payload + i, cfg_len, false);
|
||||
i += cfg_len;
|
||||
break;
|
||||
case NETWORK_KEY_FIELD_ID:
|
||||
if(cfg_len > MAX_NETWORK_KEY_SIZE_BYTES) {
|
||||
return;
|
||||
}
|
||||
print_data(str, "PWD", payload + i, cfg_len, false);
|
||||
i += cfg_len;
|
||||
break;
|
||||
case AUTH_TYPE_FIELD_ID:
|
||||
if(cfg_len != AUTH_TYPE_EXPECTED_SIZE) {
|
||||
return;
|
||||
}
|
||||
short auth_type = nfc_util_bytes2num(payload + i, 2);
|
||||
i += 2;
|
||||
const char* auth;
|
||||
switch(auth_type) {
|
||||
case AUTH_TYPE_OPEN:
|
||||
auth = "Open";
|
||||
break;
|
||||
case AUTH_TYPE_WPA_PSK:
|
||||
auth = "WPA Personal";
|
||||
break;
|
||||
case AUTH_TYPE_WPA_EAP:
|
||||
auth = "WPA Enterprise";
|
||||
break;
|
||||
case AUTH_TYPE_WPA2_EAP:
|
||||
auth = "WPA2 Enterprise";
|
||||
break;
|
||||
case AUTH_TYPE_WPA2_PSK:
|
||||
auth = "WPA2 Personal";
|
||||
break;
|
||||
case AUTH_TYPE_WPA_AND_WPA2_PSK:
|
||||
auth = "WPA/WPA2 Personal";
|
||||
break;
|
||||
default:
|
||||
auth = "Unknown";
|
||||
break;
|
||||
}
|
||||
print_data(str, "AUTH", (uint8_t*)auth, strlen(auth), false);
|
||||
break;
|
||||
default:
|
||||
i += cfg_len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
i += field_len;
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_ndef_payload(
|
||||
FuriString* str,
|
||||
uint8_t tnf,
|
||||
const char* type,
|
||||
uint8_t type_len,
|
||||
const uint8_t* payload,
|
||||
uint32_t payload_len) {
|
||||
if(!payload_len) {
|
||||
furi_string_cat(str, "Empty\n");
|
||||
return;
|
||||
}
|
||||
switch(tnf) {
|
||||
case 0x01: // NFC Forum well-known type [NFC RTD]
|
||||
if(strncmp("U", type, type_len) == 0) {
|
||||
parse_ndef_uri(str, payload, payload_len);
|
||||
} else if(strncmp("T", type, type_len) == 0) {
|
||||
parse_ndef_text(str, payload, payload_len);
|
||||
} else {
|
||||
print_data(str, "Well-known type", (uint8_t*)type, type_len, false);
|
||||
print_data(str, "Payload", payload, payload_len, false);
|
||||
}
|
||||
break;
|
||||
case 0x02: // Media-type [RFC 2046]
|
||||
if(strncmp("application/vnd.bluetooth.ep.oob", type, type_len) == 0) {
|
||||
parse_ndef_bt(str, payload, payload_len);
|
||||
} else if(strncmp("text/vcard", type, type_len) == 0) {
|
||||
parse_ndef_vcard(str, payload, payload_len);
|
||||
} else if(strncmp("application/vnd.wfa.wsc", type, type_len) == 0) {
|
||||
parse_ndef_wifi(str, payload, payload_len);
|
||||
} else {
|
||||
print_data(str, "Media Type", (uint8_t*)type, type_len, false);
|
||||
print_data(str, "Payload", payload, payload_len, false);
|
||||
}
|
||||
break;
|
||||
case 0x00: // Empty
|
||||
case 0x03: // Absolute URI [RFC 3986]
|
||||
case 0x04: // NFC Forum external type [NFC RTD]
|
||||
case 0x05: // Unknown
|
||||
case 0x06: // Unchanged
|
||||
case 0x07: // Reserved
|
||||
default: // Unknown
|
||||
// Dump data without parsing
|
||||
print_data(str, "Type name format", &tnf, 1, true);
|
||||
print_data(str, "Type", (uint8_t*)type, type_len, false);
|
||||
print_data(str, "Payload", payload, payload_len, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const uint8_t* parse_ndef_message(
|
||||
FuriString* str,
|
||||
size_t message_num,
|
||||
const uint8_t* cur,
|
||||
const uint8_t* message_end) {
|
||||
// NDEF message and record documentation:
|
||||
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/protocols/nfc/index.html#ndef-message-and-record-format
|
||||
size_t record_num = 0;
|
||||
bool last_record = false;
|
||||
while(cur < message_end) {
|
||||
// Flags and TNF
|
||||
uint8_t flags_tnf = *cur++;
|
||||
// Message Begin should only be set on first record
|
||||
if(record_num++ && flags_tnf & (1 << 7)) break;
|
||||
// Message End should only be set on last record
|
||||
if(last_record) break;
|
||||
if(flags_tnf & (1 << 6)) last_record = true;
|
||||
// Chunked Flag not supported
|
||||
if(flags_tnf & (1 << 5)) break;
|
||||
// Payload Length field of 1 vs 4 bytes
|
||||
bool short_record = flags_tnf & (1 << 4);
|
||||
// Is payload ID length and value present
|
||||
bool id_present = flags_tnf & (1 << 3);
|
||||
// Type Name Format 3 bit value
|
||||
uint8_t tnf = flags_tnf & 0b00000111;
|
||||
|
||||
// Type Length
|
||||
uint8_t type_len = *cur++;
|
||||
|
||||
// Payload Length
|
||||
uint32_t payload_len;
|
||||
if(short_record) {
|
||||
payload_len = *cur++;
|
||||
} else {
|
||||
payload_len = nfc_util_bytes2num(cur, 4);
|
||||
cur += 4;
|
||||
}
|
||||
|
||||
// ID Length
|
||||
uint8_t id_len = 0;
|
||||
if(id_present) {
|
||||
id_len = *cur++;
|
||||
}
|
||||
|
||||
// Payload Type
|
||||
char* type = NULL;
|
||||
if(type_len) {
|
||||
type = malloc(type_len);
|
||||
memcpy(type, cur, type_len);
|
||||
cur += type_len;
|
||||
}
|
||||
|
||||
// Payload ID
|
||||
cur += id_len;
|
||||
|
||||
furi_string_cat_printf(str, "\e*> M:%d R:%d - ", message_num, record_num);
|
||||
parse_ndef_payload(str, tnf, type, type_len, cur, payload_len);
|
||||
cur += payload_len;
|
||||
|
||||
free(type);
|
||||
furi_string_trim(str, "\n");
|
||||
furi_string_cat(str, "\n\n");
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
static bool ndef_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
furi_assert(parsed_data);
|
||||
|
||||
const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Memory layout documentation:
|
||||
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/nfc/doc/type_2_tag.html#id2
|
||||
|
||||
// Double check static values layout
|
||||
// First 4 static reserved pages for UID, internal and lock bytes
|
||||
// (Not sure if NDEF cata can be found in cards with different layout)
|
||||
if(data->page[0].data[0] != 0x04) break;
|
||||
if(data->page[2].data[1] != 0x48) break; // Internal
|
||||
if(data->page[2].data[2] != 0x00) break; // Lock bytes
|
||||
if(data->page[2].data[3] != 0x00) break; // ...
|
||||
if(data->page[3].data[0] != 0xE1) break; // Capability container
|
||||
if(data->page[3].data[1] != 0x10) break; // ...
|
||||
|
||||
// Data content starts here at 5th page
|
||||
const uint8_t* cur = &data->page[4].data[0];
|
||||
const uint8_t* end = &data->page[0].data[0] +
|
||||
(mf_ultralight_get_pages_total(data->type) * MF_ULTRALIGHT_PAGE_SIZE);
|
||||
size_t message_num = 0;
|
||||
|
||||
// Parse as TLV (see docs above)
|
||||
while(cur < end) {
|
||||
switch(*cur++) {
|
||||
case 0x03: { // NDEF message
|
||||
if(cur >= end) break;
|
||||
uint16_t len;
|
||||
if(*cur < 0xFF) { // 1 byte length
|
||||
len = *cur++;
|
||||
} else { // 3 byte length (0xFF marker + 2 byte integer)
|
||||
if(cur + 2 >= end) {
|
||||
cur = end;
|
||||
break;
|
||||
}
|
||||
len = nfc_util_bytes2num(++cur, 2);
|
||||
cur += 2;
|
||||
}
|
||||
if(cur + len >= end) {
|
||||
cur = end;
|
||||
break;
|
||||
}
|
||||
|
||||
if(message_num++ == 0) {
|
||||
furi_string_printf(
|
||||
parsed_data,
|
||||
"\e#NDEF Format Data\nCard type: %s\n",
|
||||
mf_ultralight_get_device_name(data, NfcDeviceNameTypeFull));
|
||||
}
|
||||
|
||||
const uint8_t* message_end = cur + len;
|
||||
cur = parse_ndef_message(parsed_data, message_num, cur, message_end);
|
||||
if(cur != message_end) cur = end;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xFE: // TLV end
|
||||
cur = end;
|
||||
if(message_num != 0) parsed = true;
|
||||
break;
|
||||
|
||||
case 0x00: // Padding, has no length, skip
|
||||
break;
|
||||
|
||||
case 0x01: // Lock control
|
||||
case 0x02: // Memory control
|
||||
case 0xFD: // Proprietary
|
||||
// We don't care, skip this TLV block
|
||||
if(cur >= end) break;
|
||||
if(*cur < 0xFF) { // 1 byte length
|
||||
cur += *cur + 1; // Shift by TLV length
|
||||
} else { // 3 byte length (0xFF marker + 2 byte integer)
|
||||
if(cur + 2 >= end) {
|
||||
cur = end;
|
||||
break;
|
||||
}
|
||||
cur += nfc_util_bytes2num(cur + 1, 2) + 3; // Shift by TLV length
|
||||
}
|
||||
break;
|
||||
|
||||
default: // Unknown, bail to avoid problems
|
||||
cur = end;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(parsed) {
|
||||
furi_string_trim(parsed_data, "\n");
|
||||
furi_string_cat(parsed_data, "\n");
|
||||
} else {
|
||||
furi_string_reset(parsed_data);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin ndef_plugin = {
|
||||
.protocol = NfcProtocolMfUltralight,
|
||||
.verify = NULL,
|
||||
.read = NULL,
|
||||
.parse = ndef_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor ndef_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &ndef_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* ndef_plugin_ep() {
|
||||
return &ndef_plugin_descriptor;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
|
||||
furi_string_printf(
|
||||
parsed_data,
|
||||
"\e#WashCity\nCard number: %0*llX\nBalance: %lu.%02u USD",
|
||||
"\e#WashCity\nCard number: %0*llX\nBalance: %lu.%02u EUR",
|
||||
uid_len * 2,
|
||||
card_number,
|
||||
balance_usd,
|
||||
|
||||
@@ -34,11 +34,6 @@
|
||||
#define PURSE_SECTOR_NUM (6)
|
||||
#define INFO_SECTOR_NUM (15)
|
||||
|
||||
typedef struct {
|
||||
uint64_t a;
|
||||
uint64_t b;
|
||||
} MfClassicKeyPair;
|
||||
|
||||
// Sector 15 data. Byte [11] contains the mistake. If byte [11] was 0xEF, bytes [1-18] means "ЗАО Золотая Корона"
|
||||
static const uint8_t info_sector_signature[] = {0xE2, 0x87, 0x80, 0x8E, 0x20, 0x87, 0xAE,
|
||||
0xAB, 0xAE, 0xF2, 0xA0, 0xEF, 0x20, 0x8A,
|
||||
@@ -76,19 +71,24 @@ void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) {
|
||||
datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE;
|
||||
}
|
||||
|
||||
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes) {
|
||||
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) {
|
||||
furi_assert(src);
|
||||
furi_assert(len_bytes <= 9);
|
||||
|
||||
uint64_t res = 0;
|
||||
uint64_t result = 0;
|
||||
*is_bcd = true;
|
||||
|
||||
for(uint8_t i = 0; i < len_bytes; i++) {
|
||||
res *= 10;
|
||||
res += src[i] / 16;
|
||||
res *= 10;
|
||||
res += src[i] % 16;
|
||||
if(((src[i] / 16) > 9) || ((src[i] % 16) > 9)) *is_bcd = false;
|
||||
|
||||
result *= 10;
|
||||
result += src[i] / 16;
|
||||
|
||||
result *= 10;
|
||||
result += src[i] % 16;
|
||||
}
|
||||
|
||||
return res;
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
@@ -121,12 +121,12 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da
|
||||
|
||||
// INFO SECTOR
|
||||
// block 1
|
||||
const uint8_t region_number = bytes2num_bcd(block_start_ptr + 10, 1);
|
||||
const uint8_t region_number = bytes2num_bcd(block_start_ptr + 10, 1, &verified);
|
||||
|
||||
// block 2
|
||||
block_start_ptr = &data->block[start_info_block_number + 2].data[4];
|
||||
const uint64_t card_number =
|
||||
bytes2num_bcd(block_start_ptr, 9) * 10 + bytes2num_bcd(block_start_ptr + 9, 1) / 10;
|
||||
const uint16_t card_number_prefix = bytes2num_bcd(block_start_ptr, 2, &verified);
|
||||
const uint64_t card_number_postfix = bytes2num_bcd(block_start_ptr + 2, 8, &verified) / 10;
|
||||
|
||||
// TRIP SECTOR
|
||||
const uint8_t start_trip_block_number =
|
||||
@@ -157,7 +157,7 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da
|
||||
block_start_ptr = &data->block[start_trip_block_number + 2].data[0];
|
||||
const char validator_first_letter =
|
||||
nfc_util_bytes2num_little_endian(block_start_ptr + 1, 1);
|
||||
const uint32_t validator_id = bytes2num_bcd(block_start_ptr + 2, 3);
|
||||
const uint32_t validator_id = bytes2num_bcd(block_start_ptr + 2, 3, &verified);
|
||||
const uint32_t last_trip_timestamp =
|
||||
nfc_util_bytes2num_little_endian(block_start_ptr + 6, 4);
|
||||
const uint8_t track_number = nfc_util_bytes2num_little_endian(block_start_ptr + 10, 1);
|
||||
@@ -174,15 +174,16 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da
|
||||
block_start_ptr = &data->block[start_purse_block_number].data[0];
|
||||
|
||||
// block 0
|
||||
uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4);
|
||||
const uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4);
|
||||
|
||||
uint32_t balance_rub = balance / 100;
|
||||
uint8_t balance_kop = balance % 100;
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\e#Zolotaya korona\nCard number: %llu\nRegion: %u\nBalance: %lu.%02u RUR\nPrev. balance: %lu.%02u RUR",
|
||||
card_number,
|
||||
"\e#Zolotaya korona\nCard number: %u%015llu\nRegion: %u\nBalance: %lu.%02u RUR\nPrev. balance: %lu.%02u RUR",
|
||||
card_number_prefix,
|
||||
card_number_postfix,
|
||||
region_number,
|
||||
balance_rub,
|
||||
balance_kop,
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Parser for Zolotaya Korona Online card (Russia).
|
||||
* Tariffs research by DNZ1393
|
||||
*
|
||||
* Copyright 2023 Leptoptilos <leptoptilos@icloud.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "furi_hal_rtc.h"
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include "protocols/mf_classic/mf_classic.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
|
||||
#define TAG "Zolotaya Korona Online"
|
||||
|
||||
#define TRIP_SECTOR_NUM (4)
|
||||
#define INFO_SECTOR_NUM (15)
|
||||
|
||||
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) {
|
||||
furi_assert(src);
|
||||
furi_assert(len_bytes <= 9);
|
||||
|
||||
uint64_t result = 0;
|
||||
*is_bcd = true;
|
||||
|
||||
for(uint8_t i = 0; i < len_bytes; i++) {
|
||||
if(((src[i] / 16) > 9) || ((src[i] % 16) > 9)) *is_bcd = false;
|
||||
|
||||
result *= 10;
|
||||
result += src[i] / 16;
|
||||
|
||||
result *= 10;
|
||||
result += src[i] % 16;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool parse_online_card_tariff(uint16_t tariff_num, FuriString* tariff_name) {
|
||||
bool tariff_parsed = false;
|
||||
|
||||
switch(tariff_num) {
|
||||
case 0x0100:
|
||||
furi_string_set_str(tariff_name, "Standart (online)");
|
||||
tariff_parsed = true;
|
||||
break;
|
||||
case 0x0101:
|
||||
case 0x0121:
|
||||
furi_string_set_str(tariff_name, "Standart (airtag)");
|
||||
tariff_parsed = true;
|
||||
break;
|
||||
case 0x0401:
|
||||
furi_string_set_str(tariff_name, "Student (50%% discount)");
|
||||
tariff_parsed = true;
|
||||
break;
|
||||
case 0x0402:
|
||||
furi_string_set_str(tariff_name, "Student (travel)");
|
||||
tariff_parsed = true;
|
||||
break;
|
||||
case 0x0002:
|
||||
furi_string_set_str(tariff_name, "School (50%% discount)");
|
||||
tariff_parsed = true;
|
||||
break;
|
||||
case 0x0505:
|
||||
furi_string_set_str(tariff_name, "Social (large families)");
|
||||
tariff_parsed = true;
|
||||
break;
|
||||
case 0x0528:
|
||||
furi_string_set_str(tariff_name, "Social (handicapped)");
|
||||
tariff_parsed = true;
|
||||
break;
|
||||
default:
|
||||
furi_string_set_str(tariff_name, "Unknown");
|
||||
tariff_parsed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return tariff_parsed;
|
||||
}
|
||||
|
||||
static bool zolotaya_korona_online_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Verify info sector data (card number prefix)
|
||||
const uint8_t start_trip_block_number =
|
||||
mf_classic_get_first_block_num_of_sector(TRIP_SECTOR_NUM);
|
||||
const uint8_t start_info_block_number =
|
||||
mf_classic_get_first_block_num_of_sector(INFO_SECTOR_NUM);
|
||||
const uint8_t* block_start_ptr = &data->block[start_info_block_number].data[3];
|
||||
|
||||
// Validate card number
|
||||
bool is_bcd;
|
||||
const uint16_t card_number_prefix = bytes2num_bcd(block_start_ptr, 2, &is_bcd);
|
||||
if(!is_bcd) break;
|
||||
if(card_number_prefix != 9643) break;
|
||||
const uint64_t card_number_postfix = bytes2num_bcd(block_start_ptr + 2, 8, &is_bcd) / 10;
|
||||
if(!is_bcd) break;
|
||||
|
||||
// Parse data
|
||||
FuriString* tariff_name = furi_string_alloc();
|
||||
|
||||
block_start_ptr = &data->block[start_info_block_number].data[1];
|
||||
const uint16_t tariff = nfc_util_bytes2num(block_start_ptr, 2);
|
||||
parse_online_card_tariff(tariff, tariff_name);
|
||||
|
||||
block_start_ptr = &data->block[start_trip_block_number].data[0];
|
||||
const uint8_t region_number = nfc_util_bytes2num(block_start_ptr, 1);
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\e#Zolotaya korona\nCard number: %u%015llu\nTariff: %02X.%02X: %s\nRegion: %u\n",
|
||||
card_number_prefix,
|
||||
card_number_postfix,
|
||||
tariff / 256,
|
||||
tariff % 256,
|
||||
furi_string_get_cstr(tariff_name),
|
||||
region_number);
|
||||
|
||||
furi_string_free(tariff_name);
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin zolotaya_korona_online_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = NULL,
|
||||
.read = NULL,
|
||||
.parse = zolotaya_korona_online_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor zolotaya_korona_online_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &zolotaya_korona_online_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* zolotaya_korona_online_plugin_ep() {
|
||||
return &zolotaya_korona_online_plugin_descriptor;
|
||||
}
|
||||
@@ -173,7 +173,6 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) {
|
||||
|
||||
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic);
|
||||
nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance);
|
||||
instance->nfc_dict_context.is_card_present = true;
|
||||
}
|
||||
|
||||
static void nfc_scene_mf_classic_dict_attack_notify_read(NfcApp* instance) {
|
||||
|
||||
@@ -28,8 +28,11 @@ bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerE
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventViewExit) {
|
||||
bool was_saved =
|
||||
scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu);
|
||||
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
instance->scene_manager, NfcSceneSavedMenu);
|
||||
instance->scene_manager, was_saved ? NfcSceneSavedMenu : NfcSceneReadSuccess);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
|
||||
Reference in New Issue
Block a user