mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-29 02:08:10 -07:00
Merge branch 'DigitalSequence_PulseReader' into ISO15693
This commit is contained in:
@@ -8,17 +8,26 @@
|
||||
#include <stm32wbxx_ll_dma.h>
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
|
||||
|
||||
struct ReloadBuffers {
|
||||
uint32_t** buffers; /* pointers to the shadow buffers, either one or two. NULL if none */
|
||||
uint32_t count; /* number of allocated buffers, 0, 1 or 2 */
|
||||
uint32_t size; /* maximum entry count of a single buffer */
|
||||
uint32_t current; /* current buffer index, the other one is most likely being used */
|
||||
uint32_t entries; /* entries in the current buffer */
|
||||
};
|
||||
|
||||
struct DigitalSequence {
|
||||
uint8_t signals_size;
|
||||
bool bake;
|
||||
uint32_t sequence_used;
|
||||
uint32_t sequence_size;
|
||||
DigitalSignal** signals;
|
||||
bool* signals_prolonged;
|
||||
uint8_t* sequence;
|
||||
const GpioPin* gpio;
|
||||
uint32_t send_time;
|
||||
bool send_time_active;
|
||||
struct ReloadBuffers* reload;
|
||||
};
|
||||
|
||||
struct DigitalSignalInternals {
|
||||
@@ -28,6 +37,7 @@ struct DigitalSignalInternals {
|
||||
const GpioPin* gpio;
|
||||
LL_DMA_InitTypeDef dma_config_gpio;
|
||||
LL_DMA_InitTypeDef dma_config_timer;
|
||||
struct ReloadBuffers* reload;
|
||||
};
|
||||
|
||||
#define TAG "DigitalSignal"
|
||||
@@ -45,27 +55,30 @@ DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) {
|
||||
signal->reload_reg_buff = malloc(signal->edges_max_cnt * sizeof(uint32_t));
|
||||
|
||||
signal->internals = malloc(sizeof(DigitalSignalInternals));
|
||||
signal->internals->reload_reg_entries = 0;
|
||||
signal->internals->reload_reg_remainder = 0;
|
||||
signal->internals->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
|
||||
signal->internals->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR;
|
||||
signal->internals->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
||||
signal->internals->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
||||
signal->internals->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
|
||||
signal->internals->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
|
||||
signal->internals->dma_config_gpio.NbData = 2;
|
||||
signal->internals->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
|
||||
signal->internals->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH;
|
||||
DigitalSignalInternals* internals = signal->internals;
|
||||
internals->reload = NULL;
|
||||
internals->reload_reg_entries = 0;
|
||||
internals->reload_reg_remainder = 0;
|
||||
|
||||
signal->internals->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR);
|
||||
signal->internals->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
|
||||
signal->internals->dma_config_timer.Mode = LL_DMA_MODE_NORMAL;
|
||||
signal->internals->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
||||
signal->internals->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
||||
signal->internals->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
|
||||
signal->internals->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
|
||||
signal->internals->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
|
||||
signal->internals->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH;
|
||||
internals->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
|
||||
internals->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR;
|
||||
internals->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
||||
internals->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
||||
internals->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
|
||||
internals->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
|
||||
internals->dma_config_gpio.NbData = 2;
|
||||
internals->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
|
||||
internals->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH;
|
||||
|
||||
internals->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR);
|
||||
internals->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
|
||||
internals->dma_config_timer.Mode = LL_DMA_MODE_NORMAL;
|
||||
internals->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
||||
internals->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
||||
internals->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
|
||||
internals->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
|
||||
internals->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
|
||||
internals->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH;
|
||||
|
||||
return signal;
|
||||
}
|
||||
@@ -79,6 +92,12 @@ void digital_signal_free(DigitalSignal* signal) {
|
||||
|
||||
free(signal->edge_timings);
|
||||
free(signal->reload_reg_buff);
|
||||
if(signal->internals->reload) {
|
||||
if(signal->internals->reload->buffers) {
|
||||
free(signal->internals->reload->buffers);
|
||||
}
|
||||
free(signal->internals->reload);
|
||||
}
|
||||
free(signal->internals);
|
||||
free(signal);
|
||||
}
|
||||
@@ -164,33 +183,55 @@ uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) {
|
||||
void digital_signal_prepare_arr(DigitalSignal* signal) {
|
||||
furi_assert(signal);
|
||||
|
||||
DigitalSignalInternals* internals = signal->internals;
|
||||
|
||||
/* set up signal polarities */
|
||||
if(signal->internals->gpio) {
|
||||
uint32_t bit_set = signal->internals->gpio->pin;
|
||||
uint32_t bit_reset = signal->internals->gpio->pin << 16;
|
||||
if(internals->gpio) {
|
||||
uint32_t bit_set = internals->gpio->pin;
|
||||
uint32_t bit_reset = internals->gpio->pin << 16;
|
||||
|
||||
#ifdef DEBUG_OUTPUT
|
||||
bit_set |= gpio_ext_pb3.pin;
|
||||
bit_reset |= gpio_ext_pb3.pin << 16;
|
||||
#endif
|
||||
|
||||
if(signal->start_level) {
|
||||
signal->internals->gpio_buff[0] = bit_set;
|
||||
signal->internals->gpio_buff[1] = bit_reset;
|
||||
internals->gpio_buff[0] = bit_set;
|
||||
internals->gpio_buff[1] = bit_reset;
|
||||
} else {
|
||||
signal->internals->gpio_buff[0] = bit_reset;
|
||||
signal->internals->gpio_buff[1] = bit_set;
|
||||
internals->gpio_buff[0] = bit_reset;
|
||||
internals->gpio_buff[1] = bit_set;
|
||||
}
|
||||
}
|
||||
|
||||
/* set up edge timings */
|
||||
signal->internals->reload_reg_entries = 0;
|
||||
internals->reload_reg_entries = 0;
|
||||
|
||||
for(size_t pos = 0; pos < signal->edge_cnt; pos++) {
|
||||
uint32_t pulse_duration =
|
||||
signal->edge_timings[pos] + signal->internals->reload_reg_remainder;
|
||||
uint32_t pulse_duration = signal->edge_timings[pos] + internals->reload_reg_remainder;
|
||||
uint32_t pulse_ticks = (pulse_duration + T_TIM_DIV2) / T_TIM;
|
||||
signal->internals->reload_reg_remainder = pulse_duration - (pulse_ticks * T_TIM);
|
||||
internals->reload_reg_remainder = pulse_duration - (pulse_ticks * T_TIM);
|
||||
|
||||
if(pulse_ticks > 1) {
|
||||
signal->reload_reg_buff[signal->internals->reload_reg_entries++] = pulse_ticks - 1;
|
||||
signal->reload_reg_buff[internals->reload_reg_entries++] = pulse_ticks - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* in case there are no shadow buffers defined, allocate and use the precalced data */
|
||||
if(!internals->reload || !internals->reload->count) {
|
||||
if(internals->reload) {
|
||||
free(internals->reload);
|
||||
}
|
||||
internals->reload = malloc(sizeof(struct ReloadBuffers));
|
||||
internals->reload->count = 1;
|
||||
internals->reload->size = signal->edges_max_cnt;
|
||||
internals->reload->buffers = malloc(sizeof(uint32_t*));
|
||||
internals->reload->buffers[0] = malloc(internals->reload->size * sizeof(uint32_t));
|
||||
memcpy(
|
||||
internals->reload->buffers[0],
|
||||
signal->reload_reg_buff,
|
||||
internals->reload_reg_entries * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
static void digital_signal_stop_dma() {
|
||||
@@ -202,32 +243,8 @@ static void digital_signal_stop_dma() {
|
||||
|
||||
static void digital_signal_stop_timer() {
|
||||
LL_TIM_DisableCounter(TIM2);
|
||||
LL_TIM_SetCounter(TIM2, 0);
|
||||
}
|
||||
|
||||
static bool digital_signal_setup_dma(DigitalSignal* signal) {
|
||||
furi_assert(signal);
|
||||
|
||||
if(!signal->internals->reload_reg_entries) {
|
||||
return false;
|
||||
}
|
||||
|
||||
signal->internals->dma_config_gpio.MemoryOrM2MDstAddress =
|
||||
(uint32_t)signal->internals->gpio_buff;
|
||||
signal->internals->dma_config_gpio.PeriphOrM2MSrcAddress =
|
||||
(uint32_t) & (signal->internals->gpio->port->BSRR);
|
||||
signal->internals->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)signal->reload_reg_buff;
|
||||
signal->internals->dma_config_timer.NbData = signal->internals->reload_reg_entries;
|
||||
|
||||
/* set up DMA channel 1 and 2 for GPIO and timer copy operations */
|
||||
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &signal->internals->dma_config_gpio);
|
||||
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &signal->internals->dma_config_timer);
|
||||
|
||||
/* enable both DMA channels */
|
||||
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
|
||||
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
|
||||
|
||||
return true;
|
||||
LL_TIM_DisableUpdateEvent(TIM2);
|
||||
LL_TIM_DisableDMAReq_UPDATE(TIM2);
|
||||
}
|
||||
|
||||
static void digital_signal_setup_timer() {
|
||||
@@ -236,15 +253,45 @@ static void digital_signal_setup_timer() {
|
||||
LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP);
|
||||
LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1);
|
||||
LL_TIM_SetPrescaler(TIM2, 0);
|
||||
LL_TIM_SetAutoReload(TIM2, 10);
|
||||
LL_TIM_SetAutoReload(TIM2, 0xFFFFFFFF);
|
||||
LL_TIM_SetCounter(TIM2, 0);
|
||||
LL_TIM_EnableUpdateEvent(TIM2);
|
||||
LL_TIM_EnableDMAReq_UPDATE(TIM2);
|
||||
}
|
||||
|
||||
static void digital_signal_start_timer() {
|
||||
LL_TIM_GenerateEvent_UPDATE(TIM2);
|
||||
LL_TIM_EnableCounter(TIM2);
|
||||
LL_TIM_EnableUpdateEvent(TIM2);
|
||||
LL_TIM_EnableDMAReq_UPDATE(TIM2);
|
||||
LL_TIM_GenerateEvent_UPDATE(TIM2);
|
||||
}
|
||||
|
||||
static bool digital_signal_setup_dma(DigitalSignal* signal) {
|
||||
furi_assert(signal);
|
||||
DigitalSignalInternals* internals = signal->internals;
|
||||
|
||||
uint32_t buffer_entries = internals->reload->entries;
|
||||
if(!buffer_entries || !internals->reload || !internals->reload->buffers) {
|
||||
return false;
|
||||
}
|
||||
digital_signal_stop_dma();
|
||||
|
||||
internals->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)internals->gpio_buff;
|
||||
internals->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t) & (internals->gpio->port->BSRR);
|
||||
internals->dma_config_timer.MemoryOrM2MDstAddress =
|
||||
(uint32_t)internals->reload->buffers[internals->reload->current];
|
||||
internals->dma_config_timer.NbData = buffer_entries;
|
||||
|
||||
/* set up DMA channel 1 and 2 for GPIO and timer copy operations */
|
||||
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &internals->dma_config_gpio);
|
||||
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &internals->dma_config_timer);
|
||||
|
||||
/* enable both DMA channels */
|
||||
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
|
||||
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
|
||||
|
||||
/* buffer is used now by DMA, skip to next */
|
||||
internals->reload->current = (internals->reload->current + 1) % internals->reload->count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) {
|
||||
@@ -279,7 +326,6 @@ void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) {
|
||||
void digital_sequence_alloc_signals(DigitalSequence* sequence, uint32_t size) {
|
||||
sequence->signals_size = size;
|
||||
sequence->signals = malloc(sequence->signals_size * sizeof(DigitalSignal*));
|
||||
sequence->signals_prolonged = malloc(sequence->signals_size * sizeof(bool));
|
||||
}
|
||||
|
||||
void digital_sequence_alloc_sequence(DigitalSequence* sequence, uint32_t size) {
|
||||
@@ -298,6 +344,13 @@ DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) {
|
||||
sequence->gpio = gpio;
|
||||
sequence->bake = false;
|
||||
|
||||
sequence->reload = malloc(sizeof(struct ReloadBuffers));
|
||||
sequence->reload->count = 2;
|
||||
sequence->reload->size = 512;
|
||||
sequence->reload->buffers = malloc(sizeof(uint32_t*));
|
||||
sequence->reload->buffers[0] = malloc(sequence->reload->size * sizeof(uint32_t));
|
||||
sequence->reload->buffers[1] = malloc(sequence->reload->size * sizeof(uint32_t));
|
||||
|
||||
digital_sequence_alloc_signals(sequence, 32);
|
||||
digital_sequence_alloc_sequence(sequence, size);
|
||||
|
||||
@@ -311,9 +364,17 @@ void digital_sequence_free(DigitalSequence* sequence) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* de-assign the shared reload buffer */
|
||||
for(int pos = 0; pos < sequence->signals_size; pos++) {
|
||||
if(sequence->signals[pos]) {
|
||||
sequence->signals[pos]->internals->reload = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
free(sequence->signals);
|
||||
free(sequence->sequence);
|
||||
free(sequence->signals_prolonged);
|
||||
free(sequence->reload->buffers);
|
||||
free(sequence->reload);
|
||||
free(sequence);
|
||||
}
|
||||
|
||||
@@ -325,10 +386,38 @@ void digital_sequence_set_signal(
|
||||
furi_assert(signal);
|
||||
furi_assert(signal_index < sequence->signals_size);
|
||||
|
||||
/* if there is already a signal, unassign the shared reload buffer */
|
||||
if(sequence->signals[signal_index]) {
|
||||
sequence->signals[signal_index]->internals->reload = NULL;
|
||||
}
|
||||
|
||||
sequence->signals[signal_index] = signal;
|
||||
signal->internals->gpio = sequence->gpio;
|
||||
signal->internals->reload_reg_remainder = 0;
|
||||
|
||||
/* free the original reload buffer */
|
||||
if(signal->internals->reload) {
|
||||
if(signal->internals->reload->buffers) {
|
||||
for(uint32_t pos = 0; pos < signal->internals->reload->count; pos++) {
|
||||
free(signal->internals->reload->buffers[pos]);
|
||||
}
|
||||
free(signal->internals->reload->buffers);
|
||||
}
|
||||
free(signal->internals->reload);
|
||||
}
|
||||
|
||||
/* assign the sequence's shared reload buffer */
|
||||
signal->internals->reload = sequence->reload;
|
||||
|
||||
/* ensure it is big enough and reallocate if not */
|
||||
if(sequence->reload->size < signal->edges_max_cnt) {
|
||||
free(sequence->reload->buffers);
|
||||
|
||||
sequence->reload->size = signal->edges_max_cnt;
|
||||
sequence->reload->buffers[0] = malloc(sequence->reload->size * sizeof(uint32_t));
|
||||
sequence->reload->buffers[1] = malloc(sequence->reload->size * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
digital_signal_prepare_arr(signal);
|
||||
}
|
||||
|
||||
@@ -352,10 +441,11 @@ void digital_sequence_add(DigitalSequence* sequence, uint8_t signal_index) {
|
||||
}
|
||||
|
||||
static void digital_signal_update_dma(DigitalSignal* signal) {
|
||||
struct ReloadBuffers* reload = signal->internals->reload;
|
||||
/* keep them prepared in registers so there is less delay when writing */
|
||||
register bool restart_needed = false;
|
||||
register volatile uint16_t len = signal->internals->reload_reg_entries;
|
||||
register volatile uint32_t addr = (uint32_t)signal->reload_reg_buff;
|
||||
register volatile uint16_t len = reload->entries;
|
||||
register volatile uint32_t addr = (uint32_t)reload->buffers[reload->current];
|
||||
|
||||
/* first make sure it will still count down, else we will risk waiting infinitely */
|
||||
const uint32_t wait_ms = 10;
|
||||
@@ -374,6 +464,8 @@ static void digital_signal_update_dma(DigitalSignal* signal) {
|
||||
/* if transfer was already active, wait till DMA is done and the last timer ticks are running */
|
||||
while(LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2)) {
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "digital_sequence_send_signal: DMA hung, restart needed");
|
||||
}
|
||||
|
||||
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
|
||||
@@ -384,6 +476,8 @@ static void digital_signal_update_dma(DigitalSignal* signal) {
|
||||
if(restart_needed) {
|
||||
LL_TIM_GenerateEvent_UPDATE(TIM2);
|
||||
}
|
||||
|
||||
reload->current = (reload->current + 1) % reload->count;
|
||||
}
|
||||
|
||||
static bool digital_sequence_send_signal(DigitalSequence* sequence, DigitalSignal* signal) {
|
||||
@@ -437,7 +531,12 @@ DigitalSignal* digital_sequence_bake(DigitalSequence* sequence) {
|
||||
bool digital_sequence_send(DigitalSequence* sequence) {
|
||||
furi_assert(sequence);
|
||||
|
||||
struct ReloadBuffers* reload = sequence->reload;
|
||||
|
||||
furi_hal_gpio_init(sequence->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
#ifdef DEBUG_OUTPUT
|
||||
furi_hal_gpio_init(&gpio_ext_pb3, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
#endif
|
||||
|
||||
if(sequence->bake) {
|
||||
DigitalSignal* sig = digital_sequence_bake(sequence);
|
||||
@@ -450,9 +549,16 @@ bool digital_sequence_send(DigitalSequence* sequence) {
|
||||
int32_t remainder = 0;
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
bool traded_first = false;
|
||||
|
||||
for(uint32_t pos = 0; pos < sequence->sequence_used; pos++) {
|
||||
uint8_t signal_index = sequence->sequence[pos];
|
||||
DigitalSignal* sig = sequence->signals[signal_index];
|
||||
DigitalSignal* sig_next = NULL;
|
||||
|
||||
if(pos + 1 < sequence->sequence_used) {
|
||||
sig_next = sequence->signals[sequence->sequence[pos + 1]];
|
||||
}
|
||||
|
||||
if(!sig) {
|
||||
FURI_LOG_D(
|
||||
@@ -463,27 +569,39 @@ bool digital_sequence_send(DigitalSequence* sequence) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* when we are too late more than half a tick, make the first edge temporarily longer */
|
||||
bool needs_prolongation = false;
|
||||
/* if the first edge is handled by prolonging the last pulse of the previous signal, skip it here */
|
||||
reload->entries = sig->edge_cnt - (traded_first ? 1 : 0);
|
||||
|
||||
memcpy(
|
||||
reload->buffers[reload->current],
|
||||
&sig->reload_reg_buff[traded_first ? 1 : 0],
|
||||
reload->entries * sizeof(uint32_t));
|
||||
traded_first = false;
|
||||
|
||||
/* when we are too late more than half a tick, make the first edge temporarily longer */
|
||||
if(remainder >= T_TIM_DIV2) {
|
||||
remainder -= T_TIM;
|
||||
needs_prolongation = true;
|
||||
reload->buffers[reload->current][0] += 1;
|
||||
}
|
||||
|
||||
/* update the total remainder */
|
||||
remainder += sig->internals->reload_reg_remainder;
|
||||
|
||||
/* do we need to update the prolongation? */
|
||||
if(needs_prolongation != sequence->signals_prolonged[signal_index]) {
|
||||
if(needs_prolongation) {
|
||||
sig->edge_timings[0]++;
|
||||
} else {
|
||||
sig->edge_timings[0]--;
|
||||
/* when a signal ends with the same level as the next signal begins, let the fist signal generate the whole pulse */
|
||||
if(sig_next) {
|
||||
/* beware, we do not want the level after the last edge, but the last level before that edge */
|
||||
bool end_level = sig->start_level ^ ((sig->edge_cnt % 2) == 0);
|
||||
|
||||
/* take from the next, add it to the first */
|
||||
if(end_level == sig_next->start_level) {
|
||||
/* add the traded prolongation to the last pulse */
|
||||
reload->buffers[reload->current][reload->entries - 1] +=
|
||||
sig_next->reload_reg_buff[0];
|
||||
traded_first = true;
|
||||
}
|
||||
sequence->signals_prolonged[signal_index] = needs_prolongation;
|
||||
}
|
||||
|
||||
/* transmit */
|
||||
bool success = digital_sequence_send_signal(sequence, sig);
|
||||
|
||||
if(!success) {
|
||||
@@ -499,16 +617,6 @@ bool digital_sequence_send(DigitalSequence* sequence) {
|
||||
digital_signal_stop_dma();
|
||||
FURI_CRITICAL_EXIT();
|
||||
|
||||
/* undo previously prolonged edges */
|
||||
for(uint32_t pos = 0; pos < sequence->signals_size; pos++) {
|
||||
DigitalSignal* sig = sequence->signals[pos];
|
||||
|
||||
if(sig && sequence->signals_prolonged[pos]) {
|
||||
sig->edge_timings[0]--;
|
||||
sequence->signals_prolonged[pos] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "flipper_application.h"
|
||||
#include "elf/elf_file.h"
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#define TAG "fapp"
|
||||
|
||||
@@ -95,6 +96,15 @@ static int32_t flipper_application_thread(void* context) {
|
||||
elf_file_pre_run(last_loaded_app->elf);
|
||||
int32_t result = elf_file_run(last_loaded_app->elf, context);
|
||||
elf_file_post_run(last_loaded_app->elf);
|
||||
|
||||
// wait until all notifications from RAM are completed
|
||||
NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
const NotificationSequence sequence_empty = {
|
||||
NULL,
|
||||
};
|
||||
notification_message_block(notifications, &sequence_empty);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -136,17 +136,45 @@ LevelDuration protocol_paradox_encoder_yield(ProtocolParadox* protocol) {
|
||||
return level_duration_make(level, duration);
|
||||
};
|
||||
|
||||
static uint8_t protocol_paradox_calculate_checksum(uint8_t fc, uint16_t card_id) {
|
||||
uint8_t card_hi = (card_id >> 8) & 0xff;
|
||||
uint8_t card_lo = card_id & 0xff;
|
||||
|
||||
uint8_t arr[5] = {0, 0, fc, card_hi, card_lo};
|
||||
|
||||
uint8_t manchester[9];
|
||||
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
|
||||
for(uint8_t i = 6; i < 40; i += 1) {
|
||||
if(bit_lib_get_bit(arr, i) == 0b1) {
|
||||
bit_lib_push_bit(manchester, 9, true);
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
} else {
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
bit_lib_push_bit(manchester, 9, true);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t output = bit_lib_crc8(manchester, 9, 0x31, 0x00, true, true, 0x06);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void protocol_paradox_render_data(ProtocolParadox* protocol, FuriString* result) {
|
||||
uint8_t* decoded_data = protocol->data;
|
||||
uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8);
|
||||
uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16);
|
||||
uint8_t card_crc = bit_lib_get_bits_16(decoded_data, 34, 8);
|
||||
uint8_t calc_crc = protocol_paradox_calculate_checksum(fc, card_id);
|
||||
|
||||
furi_string_cat_printf(result, "Facility: %u\r\n", fc);
|
||||
furi_string_cat_printf(result, "Card: %u\r\n", card_id);
|
||||
furi_string_cat_printf(result, "Data: ");
|
||||
for(size_t i = 0; i < PARADOX_DECODED_DATA_SIZE; i++) {
|
||||
furi_string_cat_printf(result, "%02X", decoded_data[i]);
|
||||
}
|
||||
furi_string_cat_printf(result, "CRC: %u Calc CRC: %u\r\n", card_crc, calc_crc);
|
||||
if(card_crc != calc_crc) furi_string_cat_printf(result, "CRC Mismatch, Invalid Card!\r\n");
|
||||
};
|
||||
|
||||
void protocol_paradox_render_brief_data(ProtocolParadox* protocol, FuriString* result) {
|
||||
@@ -154,8 +182,15 @@ void protocol_paradox_render_brief_data(ProtocolParadox* protocol, FuriString* r
|
||||
|
||||
uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8);
|
||||
uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16);
|
||||
uint8_t card_crc = bit_lib_get_bits_16(decoded_data, 34, 8);
|
||||
uint8_t calc_crc = protocol_paradox_calculate_checksum(fc, card_id);
|
||||
|
||||
furi_string_cat_printf(result, "FC: %03u, Card: %05u", fc, card_id);
|
||||
furi_string_cat_printf(result, "FC: %03u, Card: %05u\r\n", fc, card_id);
|
||||
if(calc_crc == card_crc) {
|
||||
furi_string_cat_printf(result, "CRC : %03u", card_crc);
|
||||
} else {
|
||||
furi_string_cat_printf(result, "Card is Invalid!");
|
||||
}
|
||||
};
|
||||
|
||||
bool protocol_paradox_write_data(ProtocolParadox* protocol, void* data) {
|
||||
|
||||
@@ -352,11 +352,27 @@ void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType
|
||||
}
|
||||
// Set SAK to 08
|
||||
data->nfc_data.sak = 0x08;
|
||||
} else if(type == MfClassicTypeMini) {
|
||||
// Set every block to 0xFF
|
||||
for(uint16_t i = 1; i < MF_MINI_TOTAL_SECTORS_NUM * 4; i += 1) {
|
||||
if(mf_classic_is_sector_trailer(i)) {
|
||||
nfc_generate_mf_classic_sector_trailer(mfc, i);
|
||||
} else {
|
||||
memset(&mfc->block[i].value, 0xFF, 16);
|
||||
}
|
||||
mf_classic_set_block_read(mfc, i, &mfc->block[i]);
|
||||
}
|
||||
// Set SAK to 09
|
||||
data->nfc_data.sak = 0x09;
|
||||
}
|
||||
|
||||
mfc->type = type;
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_mini(NfcDeviceData* data) {
|
||||
nfc_generate_mf_classic(data, 4, MfClassicTypeMini);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_1k_4b_uid(NfcDeviceData* data) {
|
||||
nfc_generate_mf_classic(data, 4, MfClassicType1k);
|
||||
}
|
||||
@@ -438,6 +454,11 @@ static const NfcGenerator ntag_i2c_plus_2k_generator = {
|
||||
.generator_func = nfc_generate_ntag_i2c_plus_2k,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_mini_generator = {
|
||||
.name = "Mifare Mini",
|
||||
.generator_func = nfc_generate_mf_mini,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_classic_1k_4b_uid_generator = {
|
||||
.name = "Mifare Classic 1k 4byte UID",
|
||||
.generator_func = nfc_generate_mf_classic_1k_4b_uid,
|
||||
@@ -472,6 +493,7 @@ const NfcGenerator* const nfc_generators[] = {
|
||||
&ntag_i2c_2k_generator,
|
||||
&ntag_i2c_plus_1k_generator,
|
||||
&ntag_i2c_plus_2k_generator,
|
||||
&mifare_mini_generator,
|
||||
&mifare_classic_1k_4b_uid_generator,
|
||||
&mifare_classic_1k_7b_uid_generator,
|
||||
&mifare_classic_4k_4b_uid_generator,
|
||||
|
||||
@@ -159,6 +159,7 @@ void reader_analyzer_stop(ReaderAnalyzer* instance) {
|
||||
}
|
||||
if(instance->pcap) {
|
||||
nfc_debug_pcap_free(instance->pcap);
|
||||
instance->pcap = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-4
@@ -1072,7 +1072,10 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice*
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break;
|
||||
|
||||
if(data->type == MfClassicType1k) {
|
||||
if(data->type == MfClassicTypeMini) {
|
||||
if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break;
|
||||
blocks = 20;
|
||||
} else if(data->type == MfClassicType1k) {
|
||||
if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break;
|
||||
blocks = 64;
|
||||
} else if(data->type == MfClassicType4k) {
|
||||
@@ -1170,7 +1173,10 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice*
|
||||
do {
|
||||
// Read Mifare Classic type
|
||||
if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break;
|
||||
if(!furi_string_cmp(temp_str, "1K")) {
|
||||
if(!furi_string_cmp(temp_str, "MINI")) {
|
||||
data->type = MfClassicTypeMini;
|
||||
data_blocks = 20;
|
||||
} else if(!furi_string_cmp(temp_str, "1K")) {
|
||||
data->type = MfClassicType1k;
|
||||
data_blocks = 64;
|
||||
} else if(!furi_string_cmp(temp_str, "4K")) {
|
||||
@@ -1245,7 +1251,9 @@ static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) {
|
||||
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
|
||||
if(!flipper_format_write_header_cstr(file, nfc_keys_file_header, nfc_keys_file_version))
|
||||
break;
|
||||
if(data->type == MfClassicType1k) {
|
||||
if(data->type == MfClassicTypeMini) {
|
||||
if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break;
|
||||
} else if(data->type == MfClassicType1k) {
|
||||
if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break;
|
||||
} else if(data->type == MfClassicType4k) {
|
||||
if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break;
|
||||
@@ -1295,7 +1303,9 @@ bool nfc_device_load_key_cache(NfcDevice* dev) {
|
||||
if(furi_string_cmp_str(temp_str, nfc_keys_file_header)) break;
|
||||
if(version != nfc_keys_file_version) break;
|
||||
if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break;
|
||||
if(!furi_string_cmp(temp_str, "1K")) {
|
||||
if(!furi_string_cmp(temp_str, "MINI")) {
|
||||
data->type = MfClassicTypeMini;
|
||||
} else if(!furi_string_cmp(temp_str, "1K")) {
|
||||
data->type = MfClassicType1k;
|
||||
} else if(!furi_string_cmp(temp_str, "4K")) {
|
||||
data->type = MfClassicType4k;
|
||||
|
||||
+3
-1
@@ -55,7 +55,9 @@ const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) {
|
||||
}
|
||||
|
||||
const char* nfc_mf_classic_type(MfClassicType type) {
|
||||
if(type == MfClassicType1k) {
|
||||
if(type == MfClassicTypeMini) {
|
||||
return "Mifare Mini 0.3K";
|
||||
} else if(type == MfClassicType1k) {
|
||||
return "Mifare Classic 1K";
|
||||
} else if(type == MfClassicType4k) {
|
||||
return "Mifare Classic 4K";
|
||||
|
||||
+29
-6
@@ -142,21 +142,44 @@ static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen);
|
||||
break;
|
||||
case EMV_TAG_CARD_NUM: // Track 2 Equivalent Data. 0xD0 delimits PAN from expiry (YYMM)
|
||||
case EMV_TAG_TRACK_1_EQUIV: {
|
||||
char track_1_equiv[80];
|
||||
memcpy(track_1_equiv, &buff[i], tlen);
|
||||
track_1_equiv[tlen] = '\0';
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv);
|
||||
break;
|
||||
}
|
||||
case EMV_TAG_TRACK_2_EQUIV: {
|
||||
// 0xD0 delimits PAN from expiry (YYMM)
|
||||
for(int x = 1; x < tlen; x++) {
|
||||
if(buff[i + x + 1] > 0xD0) {
|
||||
memcpy(app->card_number, &buff[i], x + 1);
|
||||
app->card_number_len = x + 1;
|
||||
app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4);
|
||||
app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert 4-bit to ASCII representation
|
||||
char track_2_equiv[41];
|
||||
uint8_t track_2_equiv_len = 0;
|
||||
for(int x = 0; x < tlen; x++) {
|
||||
char top = (buff[i + x] >> 4) + '0';
|
||||
char bottom = (buff[i + x] & 0x0F) + '0';
|
||||
track_2_equiv[x * 2] = top;
|
||||
track_2_equiv_len++;
|
||||
if(top == '?') break;
|
||||
track_2_equiv[x * 2 + 1] = bottom;
|
||||
track_2_equiv_len++;
|
||||
if(bottom == '?') break;
|
||||
}
|
||||
track_2_equiv[track_2_equiv_len] = '\0';
|
||||
success = true;
|
||||
FURI_LOG_T(
|
||||
TAG,
|
||||
"found EMV_TAG_CARD_NUM %x (len=%d)",
|
||||
EMV_TAG_CARD_NUM,
|
||||
app->card_number_len);
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv);
|
||||
break;
|
||||
}
|
||||
case EMV_TAG_PAN:
|
||||
memcpy(app->card_number, &buff[i], tlen);
|
||||
app->card_number_len = tlen;
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
#define EMV_TAG_CARD_NAME 0x50
|
||||
#define EMV_TAG_FCI 0xBF0C
|
||||
#define EMV_TAG_LOG_CTRL 0x9F4D
|
||||
#define EMV_TAG_CARD_NUM 0x57
|
||||
#define EMV_TAG_TRACK_1_EQUIV 0x56
|
||||
#define EMV_TAG_TRACK_2_EQUIV 0x57
|
||||
#define EMV_TAG_PAN 0x5A
|
||||
#define EMV_TAG_AFL 0x94
|
||||
#define EMV_TAG_EXP_DATE 0x5F24
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
#define MF_CLASSIC_WRITE_BLOCK_CMD (0xA0)
|
||||
|
||||
const char* mf_classic_get_type_str(MfClassicType type) {
|
||||
if(type == MfClassicType1k) {
|
||||
if(type == MfClassicTypeMini) {
|
||||
return "MIFARE Mini 0.3K";
|
||||
} else if(type == MfClassicType1k) {
|
||||
return "MIFARE Classic 1K";
|
||||
} else if(type == MfClassicType4k) {
|
||||
return "MIFARE Classic 4K";
|
||||
@@ -73,7 +75,9 @@ MfClassicSectorTrailer*
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_total_sectors_num(MfClassicType type) {
|
||||
if(type == MfClassicType1k) {
|
||||
if(type == MfClassicTypeMini) {
|
||||
return MF_MINI_TOTAL_SECTORS_NUM;
|
||||
} else if(type == MfClassicType1k) {
|
||||
return MF_CLASSIC_1K_TOTAL_SECTORS_NUM;
|
||||
} else if(type == MfClassicType4k) {
|
||||
return MF_CLASSIC_4K_TOTAL_SECTORS_NUM;
|
||||
@@ -83,7 +87,9 @@ uint8_t mf_classic_get_total_sectors_num(MfClassicType type) {
|
||||
}
|
||||
|
||||
uint16_t mf_classic_get_total_block_num(MfClassicType type) {
|
||||
if(type == MfClassicType1k) {
|
||||
if(type == MfClassicTypeMini) {
|
||||
return 20;
|
||||
} else if(type == MfClassicType1k) {
|
||||
return 64;
|
||||
} else if(type == MfClassicType4k) {
|
||||
return 256;
|
||||
@@ -361,10 +367,14 @@ bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
|
||||
}
|
||||
}
|
||||
|
||||
MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
|
||||
MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
|
||||
UNUSED(ATQA1);
|
||||
if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) {
|
||||
return MfClassicType1k;
|
||||
if((ATQA0 == 0x44 || ATQA0 == 0x04)) {
|
||||
if((SAK == 0x08 || SAK == 0x88)) {
|
||||
return MfClassicType1k;
|
||||
} else if(SAK == 0x09) {
|
||||
return MfClassicTypeMini;
|
||||
}
|
||||
} else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) {
|
||||
//skylanders support
|
||||
return MfClassicType1k;
|
||||
@@ -595,6 +605,14 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
} else if(i > start_block) {
|
||||
// Try to re-auth to read block in case prevous block was protected from read
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) break;
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blocks_read++;
|
||||
@@ -607,13 +625,20 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
if(!key_b_found) break;
|
||||
FURI_LOG_D(TAG, "Try to read blocks with key B");
|
||||
key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b));
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) break;
|
||||
for(size_t i = start_block; i < start_block + total_blocks; i++) {
|
||||
if(!mf_classic_is_block_read(data, i)) {
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
} else if(i > start_block) {
|
||||
// Try to re-auth to read block in case prevous block was protected from read
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) break;
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blocks_read++;
|
||||
@@ -665,6 +690,11 @@ static bool mf_classic_read_sector_with_reader(
|
||||
|
||||
// Read blocks
|
||||
for(uint8_t i = 0; i < sector->total_blocks; i++) {
|
||||
if(mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i])) continue;
|
||||
if(i == 0) continue;
|
||||
// Try to auth to read next block in case previous is locked
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, first_block + i, key, key_type, crypto, false, 0)) continue;
|
||||
mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i]);
|
||||
}
|
||||
// Save sector keys in last block
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#define MF_CLASSIC_BLOCK_SIZE (16)
|
||||
#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256)
|
||||
#define MF_MINI_TOTAL_SECTORS_NUM (5)
|
||||
#define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16)
|
||||
#define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40)
|
||||
|
||||
@@ -20,6 +21,7 @@
|
||||
typedef enum {
|
||||
MfClassicType1k,
|
||||
MfClassicType4k,
|
||||
MfClassicTypeMini,
|
||||
} MfClassicType;
|
||||
|
||||
typedef enum {
|
||||
@@ -94,7 +96,7 @@ const char* mf_classic_get_type_str(MfClassicType type);
|
||||
|
||||
bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
|
||||
|
||||
MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
|
||||
MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
|
||||
|
||||
uint8_t mf_classic_get_total_sectors_num(MfClassicType type);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ env.Append(
|
||||
File("saved_struct.h"),
|
||||
File("version.h"),
|
||||
File("float_tools.h"),
|
||||
File("value_index.h"),
|
||||
File("tar/tar_archive.h"),
|
||||
File("stream/stream.h"),
|
||||
File("stream/file_stream.h"),
|
||||
|
||||
Reference in New Issue
Block a user