mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-27 01:58:09 -07:00
Merge remote-tracking branch 'UFW/dev' into cc1101_ext
This commit is contained in:
@@ -15,6 +15,7 @@ env.Append(
|
||||
Dir("u8g2"),
|
||||
Dir("update_util"),
|
||||
Dir("print"),
|
||||
Dir("music_worker"),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -100,6 +101,7 @@ libs = env.BuildModules(
|
||||
"misc",
|
||||
"lfrfid",
|
||||
"flipper_application",
|
||||
"music_worker",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -51,8 +51,16 @@ struct DigitalSignalInternals {
|
||||
#define T_TIM 1562 /* 15.625 ns *100 */
|
||||
#define T_TIM_DIV2 781 /* 15.625 ns / 2 *100 */
|
||||
|
||||
/* end marker in DMA ringbuffer, will get written into timer register at the end */
|
||||
#define SEQ_TIMER_MAX 0xFFFFFFFF
|
||||
|
||||
/* time to wait in loops before returning */
|
||||
#define SEQ_LOCK_WAIT_MS 10UL
|
||||
#define SEQ_LOCK_WAIT_TICKS (SEQ_LOCK_WAIT_MS * 1000 * 64)
|
||||
|
||||
/* maximum entry count of the sequence dma ring buffer */
|
||||
#define SEQUENCE_DMA_RINGBUFFER_SIZE 32
|
||||
#define RINGBUFFER_SIZE 128
|
||||
|
||||
/* maximum number of DigitalSignals in a sequence */
|
||||
#define SEQUENCE_SIGNALS_SIZE 32
|
||||
/*
|
||||
@@ -214,12 +222,12 @@ void digital_signal_prepare_arr(DigitalSignal* signal) {
|
||||
for(size_t pos = 0; pos < signal->edge_cnt; pos++) {
|
||||
uint32_t pulse_duration = signal->edge_timings[pos] + internals->reload_reg_remainder;
|
||||
if(pulse_duration < 10 || pulse_duration > 10000000) {
|
||||
/*FURI_LOG_D(
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"[prepare] pulse_duration out of range: %lu = %lu * %llu",
|
||||
pulse_duration,
|
||||
signal->edge_timings[pos],
|
||||
internals->factor);*/
|
||||
internals->factor);
|
||||
pulse_duration = 100;
|
||||
}
|
||||
uint32_t pulse_ticks = (pulse_duration + T_TIM_DIV2) / T_TIM;
|
||||
@@ -243,20 +251,16 @@ static void digital_signal_stop_timer() {
|
||||
LL_TIM_DisableUpdateEvent(TIM2);
|
||||
LL_TIM_DisableDMAReq_UPDATE(TIM2);
|
||||
|
||||
if(furi_hal_bus_is_enabled(FuriHalBusTIM2)) {
|
||||
furi_hal_bus_disable(FuriHalBusTIM2);
|
||||
}
|
||||
furi_hal_bus_disable(FuriHalBusTIM2);
|
||||
}
|
||||
|
||||
static void digital_signal_setup_timer() {
|
||||
if(!furi_hal_bus_is_enabled(FuriHalBusTIM2)) {
|
||||
furi_hal_bus_enable(FuriHalBusTIM2);
|
||||
}
|
||||
furi_hal_bus_enable(FuriHalBusTIM2);
|
||||
|
||||
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, 0xFFFFFFFF);
|
||||
LL_TIM_SetAutoReload(TIM2, SEQ_TIMER_MAX);
|
||||
LL_TIM_SetCounter(TIM2, 0);
|
||||
}
|
||||
|
||||
@@ -339,7 +343,7 @@ DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) {
|
||||
sequence->bake = false;
|
||||
|
||||
sequence->dma_buffer = malloc(sizeof(struct ReloadBuffer));
|
||||
sequence->dma_buffer->size = SEQUENCE_DMA_RINGBUFFER_SIZE;
|
||||
sequence->dma_buffer->size = RINGBUFFER_SIZE;
|
||||
sequence->dma_buffer->buffer = malloc(sequence->dma_buffer->size * sizeof(uint32_t));
|
||||
|
||||
sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
|
||||
@@ -458,42 +462,26 @@ static DigitalSignal* digital_sequence_bake(DigitalSequence* sequence) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void digital_sequence_update_pos(DigitalSequence* sequence) {
|
||||
struct ReloadBuffer* dma_buffer = sequence->dma_buffer;
|
||||
|
||||
dma_buffer->read_pos = dma_buffer->size - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2);
|
||||
}
|
||||
|
||||
static const uint32_t wait_ms = 10;
|
||||
static const uint32_t wait_ticks = wait_ms * 1000 * 64;
|
||||
|
||||
static void digital_sequence_finish(DigitalSequence* sequence) {
|
||||
struct ReloadBuffer* dma_buffer = sequence->dma_buffer;
|
||||
|
||||
if(dma_buffer->dma_active) {
|
||||
uint32_t prev_timer = DWT->CYCCNT;
|
||||
uint32_t end_pos = (dma_buffer->write_pos + 1) % dma_buffer->size;
|
||||
do {
|
||||
uint32_t last_pos = dma_buffer->read_pos;
|
||||
|
||||
digital_sequence_update_pos(sequence);
|
||||
|
||||
/* we are finished, when the DMA transferred the 0xFFFFFFFF-timer which is the current write_pos */
|
||||
if(dma_buffer->read_pos == end_pos) {
|
||||
/* we are finished, when the DMA transferred the SEQ_TIMER_MAX marker */
|
||||
if(TIM2->ARR == SEQ_TIMER_MAX) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(last_pos != dma_buffer->read_pos) { //-V547
|
||||
prev_timer = DWT->CYCCNT;
|
||||
}
|
||||
if(DWT->CYCCNT - prev_timer > wait_ticks) {
|
||||
/*FURI_LOG_D(
|
||||
if(DWT->CYCCNT - prev_timer > SEQ_LOCK_WAIT_TICKS) {
|
||||
dma_buffer->read_pos =
|
||||
RINGBUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)",
|
||||
wait_ms,
|
||||
SEQ_LOCK_WAIT_MS,
|
||||
TIM2->ARR,
|
||||
dma_buffer->read_pos,
|
||||
dma_buffer->write_pos);*/
|
||||
dma_buffer->write_pos);
|
||||
break;
|
||||
}
|
||||
} while(1);
|
||||
@@ -508,34 +496,42 @@ static void digital_sequence_queue_pulse(DigitalSequence* sequence, uint32_t len
|
||||
|
||||
if(dma_buffer->dma_active) {
|
||||
uint32_t prev_timer = DWT->CYCCNT;
|
||||
uint32_t end_pos = (dma_buffer->write_pos + 1) % dma_buffer->size;
|
||||
do {
|
||||
uint32_t last_pos = dma_buffer->read_pos;
|
||||
digital_sequence_update_pos(sequence);
|
||||
dma_buffer->read_pos = RINGBUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2);
|
||||
|
||||
if(dma_buffer->read_pos != end_pos) {
|
||||
uint32_t free =
|
||||
(RINGBUFFER_SIZE + dma_buffer->read_pos - dma_buffer->write_pos) % RINGBUFFER_SIZE;
|
||||
|
||||
if(free > 2) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(last_pos != dma_buffer->read_pos) { //-V547
|
||||
prev_timer = DWT->CYCCNT;
|
||||
}
|
||||
if(DWT->CYCCNT - prev_timer > wait_ticks) {
|
||||
/*FURI_LOG_D(
|
||||
if(DWT->CYCCNT - prev_timer > SEQ_LOCK_WAIT_TICKS) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)",
|
||||
wait_ms,
|
||||
SEQ_LOCK_WAIT_MS,
|
||||
TIM2->ARR,
|
||||
dma_buffer->read_pos,
|
||||
dma_buffer->write_pos);*/
|
||||
dma_buffer->write_pos);
|
||||
break;
|
||||
}
|
||||
if(TIM2->ARR == SEQ_TIMER_MAX) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"[SEQ] buffer underrun in queue (ARR 0x%08lx, read %lu, write %lu)",
|
||||
TIM2->ARR,
|
||||
dma_buffer->read_pos,
|
||||
dma_buffer->write_pos);
|
||||
break;
|
||||
}
|
||||
} while(1);
|
||||
}
|
||||
|
||||
dma_buffer->buffer[dma_buffer->write_pos] = length;
|
||||
dma_buffer->write_pos = (dma_buffer->write_pos + 1) % dma_buffer->size;
|
||||
dma_buffer->buffer[dma_buffer->write_pos] = 0xFFFFFFFF;
|
||||
dma_buffer->write_pos++;
|
||||
dma_buffer->write_pos %= RINGBUFFER_SIZE;
|
||||
dma_buffer->buffer[dma_buffer->write_pos] = SEQ_TIMER_MAX;
|
||||
}
|
||||
|
||||
bool digital_sequence_send(DigitalSequence* sequence) {
|
||||
@@ -557,90 +553,97 @@ bool digital_sequence_send(DigitalSequence* sequence) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t remainder = 0;
|
||||
bool traded_first = false;
|
||||
if(!sequence->sequence_used) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
int32_t remainder = 0;
|
||||
uint32_t trade_for_next = 0;
|
||||
uint32_t seq_pos_next = 1;
|
||||
|
||||
dma_buffer->dma_active = false;
|
||||
dma_buffer->buffer[0] = 0xFFFFFFFF;
|
||||
dma_buffer->buffer[0] = SEQ_TIMER_MAX;
|
||||
dma_buffer->read_pos = 0;
|
||||
dma_buffer->write_pos = 0;
|
||||
|
||||
for(uint32_t seq_pos = 0; seq_pos < sequence->sequence_used; seq_pos++) {
|
||||
uint8_t signal_index = sequence->sequence[seq_pos];
|
||||
DigitalSignal* sig = sequence->signals[signal_index];
|
||||
bool last_signal = ((seq_pos + 1) == sequence->sequence_used);
|
||||
/* already prepare the current signal pointer */
|
||||
DigitalSignal* sig = sequence->signals[sequence->sequence[0]];
|
||||
DigitalSignal* sig_next = NULL;
|
||||
/* re-use the GPIO buffer from the first signal */
|
||||
sequence->gpio_buff = sig->internals->gpio_buff;
|
||||
|
||||
/* all signals are prepared and we can re-use the GPIO buffer from the fist signal */
|
||||
if(seq_pos == 0) {
|
||||
sequence->gpio_buff = sig->internals->gpio_buff;
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
while(sig) {
|
||||
bool last_signal = (seq_pos_next >= sequence->sequence_used);
|
||||
|
||||
if(!last_signal) {
|
||||
sig_next = sequence->signals[sequence->sequence[seq_pos_next++]];
|
||||
}
|
||||
|
||||
for(uint32_t pulse_pos = 0; pulse_pos < sig->internals->reload_reg_entries; pulse_pos++) {
|
||||
if(traded_first) {
|
||||
traded_first = false;
|
||||
continue;
|
||||
}
|
||||
uint32_t pulse_length = 0;
|
||||
bool last_pulse = ((pulse_pos + 1) == sig->internals->reload_reg_entries);
|
||||
bool last_pulse = ((pulse_pos + 1) >= sig->internals->reload_reg_entries);
|
||||
uint32_t pulse_length = sig->reload_reg_buff[pulse_pos] + trade_for_next;
|
||||
|
||||
pulse_length = sig->reload_reg_buff[pulse_pos];
|
||||
trade_for_next = 0;
|
||||
|
||||
/* when we are too late more than half a tick, make the first edge temporarily longer */
|
||||
if(remainder >= T_TIM_DIV2) {
|
||||
remainder -= T_TIM;
|
||||
pulse_length += 1;
|
||||
}
|
||||
remainder += sig->internals->reload_reg_remainder;
|
||||
|
||||
/* last pulse in that signal and have a next signal? */
|
||||
if(last_pulse) {
|
||||
if((seq_pos + 1) < sequence->sequence_used) {
|
||||
DigitalSignal* sig_next = sequence->signals[sequence->sequence[seq_pos + 1]];
|
||||
/* last pulse in current signal and have a next signal? */
|
||||
if(last_pulse && sig_next) {
|
||||
/* when a signal ends with the same level as the next signal begins, let the next signal generate the whole pulse.
|
||||
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);
|
||||
|
||||
/* when a signal ends with the same level as the next signal begins, let the fist signal generate the whole pulse */
|
||||
/* 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 current if they have the same level */
|
||||
if(end_level == sig_next->start_level) {
|
||||
pulse_length += sig_next->reload_reg_buff[0];
|
||||
traded_first = true;
|
||||
}
|
||||
/* if they have the same level, pass the duration to the next pulse(s) */
|
||||
if(end_level == sig_next->start_level) {
|
||||
trade_for_next = pulse_length;
|
||||
}
|
||||
}
|
||||
|
||||
digital_sequence_queue_pulse(sequence, pulse_length);
|
||||
/* if it was decided, that the next signal's first pulse shall also handle our "length", then do not queue here */
|
||||
if(!trade_for_next) {
|
||||
digital_sequence_queue_pulse(sequence, pulse_length);
|
||||
|
||||
/* start transmission when buffer was filled enough */
|
||||
bool start_send = sequence->dma_buffer->write_pos >= (sequence->dma_buffer->size - 4);
|
||||
if(!dma_buffer->dma_active) {
|
||||
/* start transmission when buffer was filled enough */
|
||||
bool start_send = sequence->dma_buffer->write_pos >= (RINGBUFFER_SIZE - 2);
|
||||
|
||||
/* or it was the last pulse */
|
||||
if(last_pulse && last_signal) {
|
||||
start_send = true;
|
||||
}
|
||||
/* or it was the last pulse */
|
||||
if(last_pulse && last_signal) {
|
||||
start_send = true;
|
||||
}
|
||||
|
||||
/* start transmission */
|
||||
if(start_send && !dma_buffer->dma_active) {
|
||||
digital_sequence_setup_dma(sequence);
|
||||
digital_signal_setup_timer();
|
||||
/* start transmission */
|
||||
if(start_send) {
|
||||
digital_sequence_setup_dma(sequence);
|
||||
digital_signal_setup_timer();
|
||||
|
||||
/* if the send time is specified, wait till the core timer passed beyond that time */
|
||||
if(sequence->send_time_active) {
|
||||
sequence->send_time_active = false;
|
||||
while(sequence->send_time - DWT->CYCCNT < 0x80000000) {
|
||||
/* if the send time is specified, wait till the core timer passed beyond that time */
|
||||
if(sequence->send_time_active) {
|
||||
sequence->send_time_active = false;
|
||||
while(sequence->send_time - DWT->CYCCNT < 0x80000000) {
|
||||
}
|
||||
}
|
||||
digital_signal_start_timer();
|
||||
dma_buffer->dma_active = true;
|
||||
}
|
||||
}
|
||||
digital_signal_start_timer();
|
||||
dma_buffer->dma_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
remainder += sig->internals->reload_reg_remainder;
|
||||
sig = sig_next;
|
||||
sig_next = NULL;
|
||||
}
|
||||
|
||||
/* wait until last dma transaction was finished */
|
||||
digital_sequence_finish(sequence);
|
||||
FURI_CRITICAL_EXIT();
|
||||
digital_sequence_finish(sequence);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,27 +7,22 @@
|
||||
|
||||
bool elf_resolve_from_hashtable(
|
||||
const ElfApiInterface* interface,
|
||||
const char* name,
|
||||
uint32_t hash,
|
||||
Elf32_Addr* address) {
|
||||
bool result = false;
|
||||
const HashtableApiInterface* hashtable_interface =
|
||||
static_cast<const HashtableApiInterface*>(interface);
|
||||
bool result = false;
|
||||
uint32_t gnu_sym_hash = elf_gnu_hash(name);
|
||||
|
||||
sym_entry key = {
|
||||
.hash = gnu_sym_hash,
|
||||
.hash = hash,
|
||||
.address = 0,
|
||||
};
|
||||
|
||||
auto find_res =
|
||||
std::lower_bound(hashtable_interface->table_cbegin, hashtable_interface->table_cend, key);
|
||||
if((find_res == hashtable_interface->table_cend || (find_res->hash != gnu_sym_hash))) {
|
||||
if((find_res == hashtable_interface->table_cend || (find_res->hash != hash))) {
|
||||
FURI_LOG_W(
|
||||
TAG,
|
||||
"Can't find symbol '%s' (hash %lx) @ %p!",
|
||||
name,
|
||||
gnu_sym_hash,
|
||||
hashtable_interface->table_cbegin);
|
||||
TAG, "Can't find symbol with hash %lx @ %p!", hash, hashtable_interface->table_cbegin);
|
||||
result = false;
|
||||
} else {
|
||||
result = true;
|
||||
@@ -36,3 +31,7 @@ bool elf_resolve_from_hashtable(
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t elf_symbolname_hash(const char* s) {
|
||||
return elf_gnu_hash(s);
|
||||
}
|
||||
@@ -19,15 +19,17 @@ struct sym_entry {
|
||||
/**
|
||||
* @brief Resolver for API entries using a pre-sorted table with hashes
|
||||
* @param interface pointer to HashtableApiInterface
|
||||
* @param name function name
|
||||
* @param hash gnu hash of function name
|
||||
* @param address output for function address
|
||||
* @return true if the table contains a function
|
||||
*/
|
||||
bool elf_resolve_from_hashtable(
|
||||
const ElfApiInterface* interface,
|
||||
const char* name,
|
||||
uint32_t hash,
|
||||
Elf32_Addr* address);
|
||||
|
||||
uint32_t elf_symbolname_hash(const char* s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -48,8 +50,10 @@ struct HashtableApiInterface : public ElfApiInterface {
|
||||
.hash = elf_gnu_hash(#x), .address = (uint32_t)(static_cast<ret_type(*) args_type>(x)) \
|
||||
}
|
||||
|
||||
#define API_VARIABLE(x, var_type) \
|
||||
sym_entry { .hash = elf_gnu_hash(#x), .address = (uint32_t)(&(x)), }
|
||||
#define API_VARIABLE(x, var_type) \
|
||||
sym_entry { \
|
||||
.hash = elf_gnu_hash(#x), .address = (uint32_t)(&(x)), \
|
||||
}
|
||||
|
||||
constexpr bool operator<(const sym_entry& k1, const sym_entry& k2) {
|
||||
return k1.hash < k2.hash;
|
||||
|
||||
@@ -11,6 +11,6 @@ typedef struct ElfApiInterface {
|
||||
uint16_t api_version_minor;
|
||||
bool (*resolver_callback)(
|
||||
const struct ElfApiInterface* interface,
|
||||
const char* name,
|
||||
uint32_t hash,
|
||||
Elf32_Addr* address);
|
||||
} ElfApiInterface;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "elf_file.h"
|
||||
#include "elf_file_i.h"
|
||||
#include "elf_api_interface.h"
|
||||
#include "../api_hashtable/api_hashtable.h"
|
||||
|
||||
#define TAG "elf"
|
||||
|
||||
@@ -9,6 +10,7 @@
|
||||
#define SECTION_OFFSET(e, n) ((e)->section_table + (n) * sizeof(Elf32_Shdr))
|
||||
#define IS_FLAGS_SET(v, m) (((v) & (m)) == (m))
|
||||
#define RESOLVER_THREAD_YIELD_STEP 30
|
||||
#define FAST_RELOCATION_VERSION 1
|
||||
|
||||
// #define ELF_DEBUG_LOG 1
|
||||
|
||||
@@ -71,6 +73,7 @@ static ELFSection* elf_file_get_or_put_section(ELFFile* elf, const char* name) {
|
||||
.size = 0,
|
||||
.rel_count = 0,
|
||||
.rel_offset = 0,
|
||||
.fast_rel = NULL,
|
||||
});
|
||||
section_p = elf_file_get_section(elf, name);
|
||||
}
|
||||
@@ -168,7 +171,8 @@ static ELFSection* elf_section_of(ELFFile* elf, int index) {
|
||||
static Elf32_Addr elf_address_of(ELFFile* elf, Elf32_Sym* sym, const char* sName) {
|
||||
if(sym->st_shndx == SHN_UNDEF) {
|
||||
Elf32_Addr addr = 0;
|
||||
if(elf->api_interface->resolver_callback(elf->api_interface, sName, &addr)) {
|
||||
uint32_t hash = elf_symbolname_hash(sName);
|
||||
if(elf->api_interface->resolver_callback(elf->api_interface, hash, &addr)) {
|
||||
return addr;
|
||||
}
|
||||
} else {
|
||||
@@ -424,6 +428,7 @@ typedef enum {
|
||||
SectionTypeSymTab = 1 << 3,
|
||||
SectionTypeStrTab = 1 << 4,
|
||||
SectionTypeDebugLink = 1 << 5,
|
||||
SectionTypeFastRelData = 1 << 6,
|
||||
|
||||
SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab,
|
||||
} SectionType;
|
||||
@@ -505,7 +510,8 @@ static SectionType elf_preload_section(
|
||||
// TODO: how to do it not by name?
|
||||
// .ARM: type 0x70000001, flags SHF_ALLOC | SHF_LINK_ORDER
|
||||
// .rel.ARM: type 0x9, flags SHT_REL
|
||||
if(str_prefix(name, ".ARM.") || str_prefix(name, ".rel.ARM.")) {
|
||||
if(str_prefix(name, ".ARM.") || str_prefix(name, ".rel.ARM.") ||
|
||||
str_prefix(name, ".fast.rel.ARM.")) {
|
||||
FURI_LOG_D(TAG, "Ignoring ARM section");
|
||||
return SectionTypeUnused;
|
||||
}
|
||||
@@ -536,11 +542,31 @@ static SectionType elf_preload_section(
|
||||
|
||||
// Load link info section
|
||||
if(section_header->sh_flags & SHF_INFO_LINK) {
|
||||
name = name + strlen(".rel");
|
||||
if(str_prefix(name, ".rel")) {
|
||||
name = name + strlen(".rel");
|
||||
ELFSection* section_p = elf_file_get_or_put_section(elf, name);
|
||||
section_p->rel_count = section_header->sh_size / sizeof(Elf32_Rel);
|
||||
section_p->rel_offset = section_header->sh_offset;
|
||||
return SectionTypeRelData;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Unknown link info section '%s'", name);
|
||||
return SectionTypeERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Load fast rel section
|
||||
if(str_prefix(name, ".fast.rel")) {
|
||||
name = name + strlen(".fast.rel");
|
||||
ELFSection* section_p = elf_file_get_or_put_section(elf, name);
|
||||
section_p->rel_count = section_header->sh_size / sizeof(Elf32_Rel);
|
||||
section_p->rel_offset = section_header->sh_offset;
|
||||
return SectionTypeRelData;
|
||||
section_p->fast_rel = malloc(sizeof(ELFSection));
|
||||
|
||||
if(!elf_load_section_data(elf, section_p->fast_rel, section_header)) {
|
||||
FURI_LOG_E(TAG, "Error loading section '%s'", name);
|
||||
return SectionTypeERROR;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Loaded fast rel section for '%s'", name);
|
||||
return SectionTypeFastRelData;
|
||||
}
|
||||
|
||||
// Load symbol table
|
||||
@@ -571,8 +597,90 @@ static SectionType elf_preload_section(
|
||||
return SectionTypeUnused;
|
||||
}
|
||||
|
||||
static Elf32_Addr elf_address_of_by_hash(ELFFile* elf, uint32_t hash) {
|
||||
Elf32_Addr addr = 0;
|
||||
if(elf->api_interface->resolver_callback(elf->api_interface, hash, &addr)) {
|
||||
return addr;
|
||||
}
|
||||
return ELF_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) {
|
||||
UNUSED(elf);
|
||||
const uint8_t* start = s->fast_rel->data;
|
||||
const uint8_t version = *start;
|
||||
|
||||
if(version != FAST_RELOCATION_VERSION) {
|
||||
FURI_LOG_E(TAG, "Unsupported fast relocation version %d", version);
|
||||
return false;
|
||||
}
|
||||
start += 1;
|
||||
|
||||
const uint32_t records_count = *((uint32_t*)start);
|
||||
start += 4;
|
||||
FURI_LOG_D(TAG, "Fast relocation records count: %ld", records_count);
|
||||
|
||||
for(uint32_t i = 0; i < records_count; i++) {
|
||||
bool is_section = (*start & (0x1 << 7)) ? true : false;
|
||||
uint8_t type = *start & 0x7F;
|
||||
start += 1;
|
||||
uint32_t hash_or_section_index = *((uint32_t*)start);
|
||||
start += 4;
|
||||
|
||||
uint32_t section_value = ELF_INVALID_ADDRESS;
|
||||
if(is_section) {
|
||||
section_value = *((uint32_t*)start);
|
||||
start += 4;
|
||||
}
|
||||
|
||||
const uint32_t offsets_count = *((uint32_t*)start);
|
||||
start += 4;
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Fast relocation record %ld: is_section=%d, type=%d, hash_or_section_index=%lX, offsets_count=%ld",
|
||||
i,
|
||||
is_section,
|
||||
type,
|
||||
hash_or_section_index,
|
||||
offsets_count);
|
||||
|
||||
Elf32_Addr address = 0;
|
||||
if(is_section) {
|
||||
ELFSection* symSec = elf_section_of(elf, hash_or_section_index);
|
||||
if(symSec) {
|
||||
address = ((Elf32_Addr)symSec->data) + section_value;
|
||||
}
|
||||
} else {
|
||||
address = elf_address_of_by_hash(elf, hash_or_section_index);
|
||||
}
|
||||
|
||||
if(address == ELF_INVALID_ADDRESS) {
|
||||
FURI_LOG_E(TAG, "Failed to resolve address for hash %lX", hash_or_section_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint32_t j = 0; j < offsets_count; j++) {
|
||||
uint32_t offset = *((uint32_t*)start) & 0x00FFFFFF;
|
||||
start += 3;
|
||||
// FURI_LOG_I(TAG, " Fast relocation offset %ld: %ld", j, offset);
|
||||
Elf32_Addr relAddr = ((Elf32_Addr)s->data) + offset;
|
||||
elf_relocate_symbol(elf, relAddr, type, address);
|
||||
}
|
||||
}
|
||||
|
||||
aligned_free(s->fast_rel->data);
|
||||
free(s->fast_rel);
|
||||
s->fast_rel = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool elf_relocate_section(ELFFile* elf, ELFSection* section) {
|
||||
if(section->rel_count) {
|
||||
if(section->fast_rel) {
|
||||
FURI_LOG_D(TAG, "Fast relocating section");
|
||||
return elf_relocate_fast(elf, section);
|
||||
} else if(section->rel_count) {
|
||||
FURI_LOG_D(TAG, "Relocating section");
|
||||
return elf_relocate(elf, section);
|
||||
} else {
|
||||
@@ -630,6 +738,10 @@ void elf_file_free(ELFFile* elf) {
|
||||
if(itref->value.data) {
|
||||
aligned_free(itref->value.data);
|
||||
}
|
||||
if(itref->value.fast_rel) {
|
||||
aligned_free(itref->value.fast_rel->data);
|
||||
free(itref->value.fast_rel);
|
||||
}
|
||||
free((void*)itref->key);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,18 @@ DICT_DEF2(AddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST)
|
||||
*/
|
||||
typedef int32_t(entry_t)(void*);
|
||||
|
||||
typedef struct {
|
||||
typedef struct ELFSection ELFSection;
|
||||
|
||||
struct ELFSection {
|
||||
void* data;
|
||||
uint16_t sec_idx;
|
||||
Elf32_Word size;
|
||||
|
||||
size_t rel_count;
|
||||
Elf32_Off rel_offset;
|
||||
} ELFSection;
|
||||
ELFSection* fast_rel;
|
||||
|
||||
uint16_t sec_idx;
|
||||
};
|
||||
|
||||
DICT_DEF2(ELFSectionDict, const char*, M_CSTR_OPLIST, ELFSection, M_POD_OPLIST)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "elf/elf_file.h"
|
||||
#include <notification/notification_messages.h>
|
||||
#include "application_assets.h"
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
|
||||
#include <m-list.h>
|
||||
|
||||
@@ -81,6 +82,12 @@ void flipper_application_free(FlipperApplication* app) {
|
||||
}
|
||||
|
||||
elf_file_free(app->elf);
|
||||
|
||||
if(app->ep_thread_args) {
|
||||
free(app->ep_thread_args);
|
||||
app->ep_thread_args = NULL;
|
||||
}
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
@@ -224,10 +231,19 @@ static int32_t flipper_application_thread(void* context) {
|
||||
return ret_code;
|
||||
}
|
||||
|
||||
FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) {
|
||||
FuriThread* flipper_application_alloc_thread(FlipperApplication* app, const char* args) {
|
||||
furi_check(app->thread == NULL);
|
||||
furi_check(!flipper_application_is_plugin(app));
|
||||
app->ep_thread_args = args;
|
||||
|
||||
if(app->ep_thread_args) {
|
||||
free(app->ep_thread_args);
|
||||
}
|
||||
|
||||
if(args) {
|
||||
app->ep_thread_args = strdup(args);
|
||||
} else {
|
||||
app->ep_thread_args = NULL;
|
||||
}
|
||||
|
||||
const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app);
|
||||
app->thread = furi_thread_alloc_ex(
|
||||
@@ -289,4 +305,32 @@ const FlipperAppPluginDescriptor*
|
||||
lib_descriptor->ep_api_version);
|
||||
|
||||
return lib_descriptor;
|
||||
}
|
||||
|
||||
bool flipper_application_load_name_and_icon(
|
||||
FuriString* path,
|
||||
Storage* storage,
|
||||
uint8_t** icon_ptr,
|
||||
FuriString* item_name) {
|
||||
FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface);
|
||||
|
||||
FlipperApplicationPreloadStatus preload_res =
|
||||
flipper_application_preload_manifest(app, furi_string_get_cstr(path));
|
||||
|
||||
bool load_success = false;
|
||||
|
||||
if(preload_res == FlipperApplicationPreloadStatusSuccess) {
|
||||
const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app);
|
||||
if(manifest->has_icon) {
|
||||
memcpy(*icon_ptr, manifest->icon, FAP_MANIFEST_MAX_ICON_SIZE);
|
||||
}
|
||||
furi_string_set(item_name, manifest->name);
|
||||
load_success = true;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to preload %s", furi_string_get_cstr(path));
|
||||
load_success = false;
|
||||
}
|
||||
|
||||
flipper_application_free(app);
|
||||
return load_success;
|
||||
}
|
||||
@@ -106,14 +106,14 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic
|
||||
FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app);
|
||||
|
||||
/**
|
||||
* @brief Create application thread at entry point address, using app name and
|
||||
* @brief Allocate application thread at entry point address, using app name and
|
||||
* stack size from metadata. Returned thread isn't started yet.
|
||||
* Can be only called once for application instance.
|
||||
* @param app Applicaiton pointer
|
||||
* @param args Object to pass to app's entry point
|
||||
* @param args Args to pass to app's entry point
|
||||
* @return Created thread
|
||||
*/
|
||||
FuriThread* flipper_application_spawn(FlipperApplication* app, void* args);
|
||||
FuriThread* flipper_application_alloc_thread(FlipperApplication* app, const char* args);
|
||||
|
||||
/**
|
||||
* @brief Check if application is a plugin (not a runnable standalone app)
|
||||
@@ -149,6 +149,21 @@ typedef const FlipperAppPluginDescriptor* (*FlipperApplicationPluginEntryPoint)(
|
||||
const FlipperAppPluginDescriptor*
|
||||
flipper_application_plugin_get_descriptor(FlipperApplication* app);
|
||||
|
||||
/**
|
||||
* @brief Load name and icon from FAP file.
|
||||
*
|
||||
* @param path Path to FAP file.
|
||||
* @param storage Storage instance.
|
||||
* @param icon_ptr Icon pointer.
|
||||
* @param item_name Application name.
|
||||
* @return true if icon and name were loaded successfully.
|
||||
*/
|
||||
bool flipper_application_load_name_and_icon(
|
||||
FuriString* path,
|
||||
Storage* storage,
|
||||
uint8_t** icon_ptr,
|
||||
FuriString* item_name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -13,12 +13,12 @@ struct CompositeApiResolver {
|
||||
|
||||
static bool composite_api_resolver_callback(
|
||||
const ElfApiInterface* interface,
|
||||
const char* name,
|
||||
uint32_t hash,
|
||||
Elf32_Addr* address) {
|
||||
CompositeApiResolver* resolver = (CompositeApiResolver*)interface;
|
||||
for
|
||||
M_EACH(interface, resolver->interfaces, ElfApiInterfaceList_t) {
|
||||
if((*interface)->resolver_callback(*interface, name, address)) {
|
||||
if((*interface)->resolver_callback(*interface, hash, address)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
Import("env")
|
||||
|
||||
env.Append(
|
||||
CPPPATH=[
|
||||
"#/lib/music_worker",
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
File("music_worker.h"),
|
||||
],
|
||||
)
|
||||
|
||||
libenv = env.Clone(FW_LIB_NAME="music_worker")
|
||||
libenv.ApplyLibFlags()
|
||||
|
||||
libenv.AppendUnique(
|
||||
CCFLAGS=[
|
||||
# Required for lib to be linkable with .faps
|
||||
"-mword-relocations",
|
||||
"-mlong-calls",
|
||||
],
|
||||
)
|
||||
|
||||
sources = libenv.GlobRecursive("*.c*")
|
||||
|
||||
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
|
||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
||||
Return("lib")
|
||||
@@ -0,0 +1,507 @@
|
||||
#include "music_worker.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
|
||||
#include <math.h>
|
||||
#include <m-array.h>
|
||||
|
||||
#define TAG "MusicWorker"
|
||||
|
||||
#define MUSIC_PLAYER_FILETYPE "Flipper Music Format"
|
||||
#define MUSIC_PLAYER_VERSION 0
|
||||
|
||||
#define SEMITONE_PAUSE 0xFF
|
||||
|
||||
#define NOTE_C4 261.63f
|
||||
#define NOTE_C4_SEMITONE (4.0f * 12.0f)
|
||||
#define TWO_POW_TWELTH_ROOT 1.059463094359f
|
||||
|
||||
typedef struct {
|
||||
uint8_t semitone;
|
||||
uint8_t duration;
|
||||
uint8_t dots;
|
||||
} NoteBlock;
|
||||
|
||||
ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);
|
||||
|
||||
struct MusicWorker {
|
||||
FuriThread* thread;
|
||||
bool should_work;
|
||||
|
||||
MusicWorkerCallback callback;
|
||||
void* callback_context;
|
||||
|
||||
float volume;
|
||||
uint32_t bpm;
|
||||
uint32_t duration;
|
||||
uint32_t octave;
|
||||
NoteBlockArray_t notes;
|
||||
};
|
||||
|
||||
static int32_t music_worker_thread_callback(void* context) {
|
||||
furi_assert(context);
|
||||
MusicWorker* instance = context;
|
||||
|
||||
NoteBlockArray_it_t it;
|
||||
NoteBlockArray_it(it, instance->notes);
|
||||
if(furi_hal_speaker_acquire(1000)) {
|
||||
while(instance->should_work) {
|
||||
if(NoteBlockArray_end_p(it)) {
|
||||
NoteBlockArray_it(it, instance->notes);
|
||||
furi_delay_ms(10);
|
||||
} else {
|
||||
NoteBlock* note_block = NoteBlockArray_ref(it);
|
||||
|
||||
float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
|
||||
float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
|
||||
float duration = 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm /
|
||||
note_block->duration;
|
||||
uint32_t dots = note_block->dots;
|
||||
while(dots > 0) {
|
||||
duration += duration / 2;
|
||||
dots--;
|
||||
}
|
||||
uint32_t next_tick = furi_get_tick() + duration;
|
||||
float volume = instance->volume;
|
||||
|
||||
if(instance->callback) {
|
||||
instance->callback(
|
||||
note_block->semitone,
|
||||
note_block->dots,
|
||||
note_block->duration,
|
||||
0.0,
|
||||
instance->callback_context);
|
||||
}
|
||||
|
||||
furi_hal_speaker_stop();
|
||||
furi_hal_speaker_start(frequency, volume);
|
||||
while(instance->should_work && furi_get_tick() < next_tick) {
|
||||
volume *= 0.9945679;
|
||||
furi_hal_speaker_set_volume(volume);
|
||||
furi_delay_ms(2);
|
||||
}
|
||||
NoteBlockArray_next(it);
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_speaker_stop();
|
||||
furi_hal_speaker_release();
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Speaker system is busy with another process.");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MusicWorker* music_worker_alloc() {
|
||||
MusicWorker* instance = malloc(sizeof(MusicWorker));
|
||||
|
||||
NoteBlockArray_init(instance->notes);
|
||||
|
||||
instance->thread =
|
||||
furi_thread_alloc_ex("MusicWorker", 1024, music_worker_thread_callback, instance);
|
||||
|
||||
instance->volume = 1.0f;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void music_worker_clear(MusicWorker* instance) {
|
||||
NoteBlockArray_reset(instance->notes);
|
||||
}
|
||||
|
||||
void music_worker_free(MusicWorker* instance) {
|
||||
furi_assert(instance);
|
||||
furi_thread_free(instance->thread);
|
||||
NoteBlockArray_clear(instance->notes);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static bool is_digit(const char c) {
|
||||
return isdigit(c) != 0;
|
||||
}
|
||||
|
||||
static bool is_letter(const char c) {
|
||||
return islower(c) != 0 || isupper(c) != 0;
|
||||
}
|
||||
|
||||
static bool is_space(const char c) {
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
static size_t extract_number(const char* string, uint32_t* number) {
|
||||
size_t ret = 0;
|
||||
*number = 0;
|
||||
while(is_digit(*string)) {
|
||||
*number *= 10;
|
||||
*number += (*string - '0');
|
||||
string++;
|
||||
ret++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static size_t extract_dots(const char* string, uint32_t* number) {
|
||||
size_t ret = 0;
|
||||
*number = 0;
|
||||
while(*string == '.') {
|
||||
*number += 1;
|
||||
string++;
|
||||
ret++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static size_t extract_char(const char* string, char* symbol) {
|
||||
if(is_letter(*string)) {
|
||||
*symbol = *string;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t extract_sharp(const char* string, char* symbol) {
|
||||
if(*string == '#' || *string == '_') {
|
||||
*symbol = '#';
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t skip_till(const char* string, const char symbol) {
|
||||
size_t ret = 0;
|
||||
while(*string != '\0' && *string != symbol) {
|
||||
string++;
|
||||
ret++;
|
||||
}
|
||||
if(*string != symbol) {
|
||||
ret = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
music_worker_add_note(MusicWorker* instance, uint8_t semitone, uint8_t duration, uint8_t dots) {
|
||||
NoteBlock note_block;
|
||||
|
||||
note_block.semitone = semitone;
|
||||
note_block.duration = duration;
|
||||
note_block.dots = dots;
|
||||
|
||||
NoteBlockArray_push_back(instance->notes, note_block);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int8_t note_to_semitone(const char note) {
|
||||
switch(note) {
|
||||
case 'C':
|
||||
return 0;
|
||||
// C#
|
||||
case 'D':
|
||||
return 2;
|
||||
// D#
|
||||
case 'E':
|
||||
return 4;
|
||||
case 'F':
|
||||
return 5;
|
||||
// F#
|
||||
case 'G':
|
||||
return 7;
|
||||
// G#
|
||||
case 'A':
|
||||
return 9;
|
||||
// A#
|
||||
case 'B':
|
||||
return 11;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool music_worker_parse_notes(MusicWorker* instance, const char* string) {
|
||||
const char* cursor = string;
|
||||
bool result = true;
|
||||
|
||||
while(*cursor != '\0') {
|
||||
if(!is_space(*cursor)) {
|
||||
uint32_t duration = 0;
|
||||
char note_char = '\0';
|
||||
char sharp_char = '\0';
|
||||
uint32_t octave = 0;
|
||||
uint32_t dots = 0;
|
||||
|
||||
// Parsing
|
||||
cursor += extract_number(cursor, &duration);
|
||||
cursor += extract_char(cursor, ¬e_char);
|
||||
cursor += extract_sharp(cursor, &sharp_char);
|
||||
cursor += extract_number(cursor, &octave);
|
||||
cursor += extract_dots(cursor, &dots);
|
||||
|
||||
// Post processing
|
||||
note_char = toupper(note_char);
|
||||
if(!duration) {
|
||||
duration = instance->duration;
|
||||
}
|
||||
if(!octave) {
|
||||
octave = instance->octave;
|
||||
}
|
||||
|
||||
// Validation
|
||||
bool is_valid = true;
|
||||
is_valid &= (duration >= 1 && duration <= 128);
|
||||
is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P');
|
||||
is_valid &= (sharp_char == '#' || sharp_char == '\0');
|
||||
is_valid &= (octave <= 16);
|
||||
is_valid &= (dots <= 16);
|
||||
if(!is_valid) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Invalid note: %lu%c%c%lu.%lu",
|
||||
duration,
|
||||
note_char == '\0' ? '_' : note_char,
|
||||
sharp_char == '\0' ? '_' : sharp_char,
|
||||
octave,
|
||||
dots);
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Note to semitones
|
||||
uint8_t semitone = 0;
|
||||
if(note_char == 'P') {
|
||||
semitone = SEMITONE_PAUSE;
|
||||
} else {
|
||||
semitone += octave * 12;
|
||||
semitone += note_to_semitone(note_char);
|
||||
semitone += sharp_char == '#' ? 1 : 0;
|
||||
}
|
||||
|
||||
if(music_worker_add_note(instance, semitone, duration, dots)) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Added note: %c%c%lu.%lu = %u %lu",
|
||||
note_char == '\0' ? '_' : note_char,
|
||||
sharp_char == '\0' ? '_' : sharp_char,
|
||||
octave,
|
||||
dots,
|
||||
semitone,
|
||||
duration);
|
||||
} else {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Invalid note: %c%c%lu.%lu = %u %lu",
|
||||
note_char == '\0' ? '_' : note_char,
|
||||
sharp_char == '\0' ? '_' : sharp_char,
|
||||
octave,
|
||||
dots,
|
||||
semitone,
|
||||
duration);
|
||||
}
|
||||
cursor += skip_till(cursor, ',');
|
||||
}
|
||||
|
||||
if(*cursor != '\0') cursor++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool music_worker_load(MusicWorker* instance, const char* file_path) {
|
||||
furi_assert(instance);
|
||||
furi_assert(file_path);
|
||||
|
||||
bool ret = false;
|
||||
if(strcasestr(file_path, ".fmf")) {
|
||||
ret = music_worker_load_fmf_from_file(instance, file_path);
|
||||
} else {
|
||||
ret = music_worker_load_rtttl_from_file(instance, file_path);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool music_worker_load_fmf_from_file(MusicWorker* instance, const char* file_path) {
|
||||
furi_assert(instance);
|
||||
furi_assert(file_path);
|
||||
|
||||
bool result = false;
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_existing(file, file_path)) break;
|
||||
|
||||
uint32_t version = 0;
|
||||
if(!flipper_format_read_header(file, temp_str, &version)) break;
|
||||
if(furi_string_cmp_str(temp_str, MUSIC_PLAYER_FILETYPE) ||
|
||||
(version != MUSIC_PLAYER_VERSION)) {
|
||||
FURI_LOG_E(TAG, "Incorrect file format or version");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) {
|
||||
FURI_LOG_E(TAG, "BPM is missing");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) {
|
||||
FURI_LOG_E(TAG, "Duration is missing");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) {
|
||||
FURI_LOG_E(TAG, "Octave is missing");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_string(file, "Notes", temp_str)) {
|
||||
FURI_LOG_E(TAG, "Notes is missing");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!music_worker_parse_notes(instance, furi_string_get_cstr(temp_str))) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = true;
|
||||
} while(false);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
flipper_format_free(file);
|
||||
furi_string_free(temp_str);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool music_worker_load_rtttl_from_file(MusicWorker* instance, const char* file_path) {
|
||||
furi_assert(instance);
|
||||
furi_assert(file_path);
|
||||
|
||||
bool result = false;
|
||||
FuriString* content;
|
||||
content = furi_string_alloc();
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
do {
|
||||
if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(TAG, "Unable to open file");
|
||||
break;
|
||||
};
|
||||
|
||||
uint16_t ret = 0;
|
||||
do {
|
||||
uint8_t buffer[65] = {0};
|
||||
ret = storage_file_read(file, buffer, sizeof(buffer) - 1);
|
||||
for(size_t i = 0; i < ret; i++) {
|
||||
furi_string_push_back(content, buffer[i]);
|
||||
}
|
||||
} while(ret > 0);
|
||||
|
||||
furi_string_trim(content);
|
||||
if(!furi_string_size(content)) {
|
||||
FURI_LOG_E(TAG, "Empty file");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!music_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) {
|
||||
FURI_LOG_E(TAG, "Invalid file content");
|
||||
break;
|
||||
}
|
||||
|
||||
result = true;
|
||||
} while(0);
|
||||
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_string_free(content);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool music_worker_load_rtttl_from_string(MusicWorker* instance, const char* string) {
|
||||
furi_assert(instance);
|
||||
|
||||
const char* cursor = string;
|
||||
|
||||
// Skip name
|
||||
cursor += skip_till(cursor, ':');
|
||||
if(*cursor != ':') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Duration
|
||||
cursor += skip_till(cursor, '=');
|
||||
if(*cursor != '=') {
|
||||
return false;
|
||||
}
|
||||
cursor++;
|
||||
cursor += extract_number(cursor, &instance->duration);
|
||||
|
||||
// Octave
|
||||
cursor += skip_till(cursor, '=');
|
||||
if(*cursor != '=') {
|
||||
return false;
|
||||
}
|
||||
cursor++;
|
||||
cursor += extract_number(cursor, &instance->octave);
|
||||
|
||||
// BPM
|
||||
cursor += skip_till(cursor, '=');
|
||||
if(*cursor != '=') {
|
||||
return false;
|
||||
}
|
||||
cursor++;
|
||||
cursor += extract_number(cursor, &instance->bpm);
|
||||
|
||||
// Notes
|
||||
cursor += skip_till(cursor, ':');
|
||||
if(*cursor != ':') {
|
||||
return false;
|
||||
}
|
||||
cursor++;
|
||||
if(!music_worker_parse_notes(instance, cursor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void music_worker_set_callback(MusicWorker* instance, MusicWorkerCallback callback, void* context) {
|
||||
furi_assert(instance);
|
||||
instance->callback = callback;
|
||||
instance->callback_context = context;
|
||||
}
|
||||
|
||||
void music_worker_set_volume(MusicWorker* instance, float volume) {
|
||||
furi_assert(instance);
|
||||
instance->volume = volume;
|
||||
}
|
||||
|
||||
void music_worker_start(MusicWorker* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->should_work == false);
|
||||
|
||||
instance->should_work = true;
|
||||
furi_thread_start(instance->thread);
|
||||
}
|
||||
|
||||
void music_worker_stop(MusicWorker* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->should_work == true);
|
||||
|
||||
instance->should_work = false;
|
||||
furi_thread_join(instance->thread);
|
||||
}
|
||||
|
||||
bool music_worker_is_playing(MusicWorker* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->should_work;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef void (*MusicWorkerCallback)(
|
||||
uint8_t semitone,
|
||||
uint8_t dots,
|
||||
uint8_t duration,
|
||||
float position,
|
||||
void* context);
|
||||
|
||||
typedef struct MusicWorker MusicWorker;
|
||||
|
||||
MusicWorker* music_worker_alloc();
|
||||
|
||||
void music_worker_clear(MusicWorker* instance);
|
||||
|
||||
void music_worker_free(MusicWorker* instance);
|
||||
|
||||
bool music_worker_load(MusicWorker* instance, const char* file_path);
|
||||
|
||||
bool music_worker_load_fmf_from_file(MusicWorker* instance, const char* file_path);
|
||||
|
||||
bool music_worker_load_rtttl_from_file(MusicWorker* instance, const char* file_path);
|
||||
|
||||
bool music_worker_load_rtttl_from_string(MusicWorker* instance, const char* string);
|
||||
|
||||
void music_worker_set_callback(MusicWorker* instance, MusicWorkerCallback callback, void* context);
|
||||
|
||||
void music_worker_set_volume(MusicWorker* instance, float volume);
|
||||
|
||||
void music_worker_start(MusicWorker* instance);
|
||||
|
||||
void music_worker_stop(MusicWorker* instance);
|
||||
|
||||
bool music_worker_is_playing(MusicWorker* instance);
|
||||
+186
-174
@@ -657,178 +657,167 @@ bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool nfc_device_save_slix_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
static bool nfc_device_save_slix_data(
|
||||
FlipperFormat* file,
|
||||
NfcDevice* dev,
|
||||
SlixTypeFeatures features,
|
||||
const char* type) {
|
||||
bool saved = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(file, "SLIX specific data")) break;
|
||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "%s specific data", type);
|
||||
if(!flipper_format_write_comment_cstr(file, msg)) break;
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
file, "Passwords are optional. If password is omitted, any password is accepted"))
|
||||
break;
|
||||
|
||||
if(features & SlixFeatureRead) {
|
||||
if(data->flags & NfcVSlixDataFlagsHasKeyRead) {
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Read", data->key_read, sizeof(data->key_read)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeatureWrite) {
|
||||
if(data->flags & NfcVSlixDataFlagsHasKeyWrite) {
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeaturePrivacy) {
|
||||
if(data->flags & NfcVSlixDataFlagsHasKeyPrivacy) {
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeatureDestroy) {
|
||||
if(data->flags & NfcVSlixDataFlagsHasKeyDestroy) {
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeatureEas) {
|
||||
if(data->flags & NfcVSlixDataFlagsHasKeyEas) {
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeatureSignature) {
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
file,
|
||||
"This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key."))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Signature", data->signature, sizeof(data->signature)))
|
||||
break;
|
||||
}
|
||||
if(features & SlixFeaturePrivacy) {
|
||||
bool privacy = (data->flags & NfcVSlixDataFlagsPrivacy) ? true : false;
|
||||
if(!flipper_format_write_bool(file, "Privacy Mode", &privacy, 1)) break;
|
||||
}
|
||||
if(features & SlixFeatureProtection) {
|
||||
if(!flipper_format_write_comment_cstr(file, "Protection pointer configuration")) break;
|
||||
if(!flipper_format_write_hex(file, "Protection pointer", &data->pp_pointer, 1)) break;
|
||||
if(!flipper_format_write_hex(file, "Protection condition", &data->pp_condition, 1))
|
||||
break;
|
||||
}
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev, SlixTypeFeatures features) {
|
||||
bool parsed = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
memset(data, 0, sizeof(NfcVSlixData));
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool nfc_device_save_slix_s_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool saved = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(file, "SLIX-S specific data")) break;
|
||||
if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool nfc_device_load_slix_s_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool parsed = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
memset(data, 0, sizeof(NfcVSlixData));
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool nfc_device_save_slix_l_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool saved = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(file, "SLIX-L specific data")) break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool nfc_device_load_slix_l_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool parsed = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
memset(data, 0, sizeof(NfcVSlixData));
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool nfc_device_save_slix2_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool saved = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(file, "SLIX2 specific data")) break;
|
||||
if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool nfc_device_load_slix2_data(FlipperFormat* file, NfcDevice* dev) { // -V524
|
||||
bool parsed = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
memset(data, 0, sizeof(NfcVSlixData));
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
data->flags = 0;
|
||||
|
||||
if(features & SlixFeatureRead) {
|
||||
if(flipper_format_key_exist(file, "Password Read")) {
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Read", data->key_read, sizeof(data->key_read))) {
|
||||
FURI_LOG_D(TAG, "Failed reading Password Read");
|
||||
break;
|
||||
}
|
||||
data->flags |= NfcVSlixDataFlagsHasKeyRead;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeatureWrite) {
|
||||
if(flipper_format_key_exist(file, "Password Write")) {
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Write", data->key_write, sizeof(data->key_write))) {
|
||||
FURI_LOG_D(TAG, "Failed reading Password Write");
|
||||
break;
|
||||
}
|
||||
data->flags |= NfcVSlixDataFlagsHasKeyWrite;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeaturePrivacy) {
|
||||
if(flipper_format_key_exist(file, "Password Privacy")) {
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) {
|
||||
FURI_LOG_D(TAG, "Failed reading Password Privacy");
|
||||
break;
|
||||
}
|
||||
data->flags |= NfcVSlixDataFlagsHasKeyPrivacy;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeatureDestroy) {
|
||||
if(flipper_format_key_exist(file, "Password Destroy")) {
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) {
|
||||
FURI_LOG_D(TAG, "Failed reading Password Destroy");
|
||||
break;
|
||||
}
|
||||
data->flags |= NfcVSlixDataFlagsHasKeyDestroy;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeatureEas) {
|
||||
if(flipper_format_key_exist(file, "Password EAS")) {
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password EAS", data->key_eas, sizeof(data->key_eas))) {
|
||||
FURI_LOG_D(TAG, "Failed reading Password EAS");
|
||||
break;
|
||||
}
|
||||
data->flags |= NfcVSlixDataFlagsHasKeyEas;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeatureSignature) {
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Signature", data->signature, sizeof(data->signature))) {
|
||||
FURI_LOG_D(TAG, "Failed reading Signature");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeaturePrivacy) {
|
||||
bool privacy;
|
||||
if(!flipper_format_read_bool(file, "Privacy Mode", &privacy, 1)) {
|
||||
FURI_LOG_D(TAG, "Failed reading Privacy Mode");
|
||||
break;
|
||||
}
|
||||
if(privacy) {
|
||||
data->flags |= NfcVSlixDataFlagsPrivacy;
|
||||
}
|
||||
}
|
||||
if(features & SlixFeatureProtection) {
|
||||
if(!flipper_format_read_hex(file, "Protection pointer", &(data->pp_pointer), 1)) {
|
||||
FURI_LOG_D(TAG, "Failed reading Protection pointer");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_hex(file, "Protection condition", &(data->pp_condition), 1)) {
|
||||
FURI_LOG_D(TAG, "Failed reading Protection condition");
|
||||
break;
|
||||
}
|
||||
}
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
@@ -859,7 +848,8 @@ static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
file, "Data Content", data->data, data->block_num * data->block_size))
|
||||
break;
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
file, "First byte: DSFID (0x01) / AFI (0x02) lock info, others: block lock info"))
|
||||
file,
|
||||
"First byte: DSFID (0x01) / AFI (0x02) / EAS (0x04) / PPL (0x08) lock info, others: block lock info"))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Security Status", data->security_status, 1 + data->block_num))
|
||||
@@ -877,16 +867,16 @@ static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
saved = true;
|
||||
break;
|
||||
case NfcVTypeSlix:
|
||||
saved = nfc_device_save_slix_data(file, dev);
|
||||
saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix, "SLIX");
|
||||
break;
|
||||
case NfcVTypeSlixS:
|
||||
saved = nfc_device_save_slix_s_data(file, dev);
|
||||
saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixS, "SLIX-S");
|
||||
break;
|
||||
case NfcVTypeSlixL:
|
||||
saved = nfc_device_save_slix_l_data(file, dev);
|
||||
saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixL, "SLIX-L");
|
||||
break;
|
||||
case NfcVTypeSlix2:
|
||||
saved = nfc_device_save_slix2_data(file, dev);
|
||||
saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix2, "SLIX2");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -906,23 +896,45 @@ bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
uint32_t temp_uint32 = 0;
|
||||
uint8_t temp_value = 0;
|
||||
|
||||
if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) break;
|
||||
if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) break;
|
||||
if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) break;
|
||||
if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) break;
|
||||
data->block_num = temp_uint32;
|
||||
if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Data Content", data->data, data->block_num * data->block_size))
|
||||
if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) {
|
||||
FURI_LOG_D(TAG, "Failed reading DSFID");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) {
|
||||
FURI_LOG_D(TAG, "Failed reading AFI");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) {
|
||||
FURI_LOG_D(TAG, "Failed reading IC Reference");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) {
|
||||
FURI_LOG_D(TAG, "Failed reading Block Count");
|
||||
break;
|
||||
}
|
||||
data->block_num = temp_uint32;
|
||||
if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) {
|
||||
FURI_LOG_D(TAG, "Failed reading Block Size");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Data Content", data->data, data->block_num * data->block_size)) {
|
||||
FURI_LOG_D(TAG, "Failed reading Data Content");
|
||||
break;
|
||||
}
|
||||
|
||||
/* optional, as added later */
|
||||
if(flipper_format_key_exist(file, "Security Status")) {
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Security Status", data->security_status, 1 + data->block_num))
|
||||
file, "Security Status", data->security_status, 1 + data->block_num)) {
|
||||
FURI_LOG_D(TAG, "Failed reading Security Status");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) {
|
||||
FURI_LOG_D(TAG, "Failed reading Subtype");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) break;
|
||||
data->sub_type = temp_value;
|
||||
|
||||
switch(data->sub_type) {
|
||||
@@ -930,16 +942,16 @@ bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
parsed = true;
|
||||
break;
|
||||
case NfcVTypeSlix:
|
||||
parsed = nfc_device_load_slix_data(file, dev);
|
||||
parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix);
|
||||
break;
|
||||
case NfcVTypeSlixS:
|
||||
parsed = nfc_device_load_slix_s_data(file, dev);
|
||||
parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixS);
|
||||
break;
|
||||
case NfcVTypeSlixL:
|
||||
parsed = nfc_device_load_slix_l_data(file, dev);
|
||||
parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixL);
|
||||
break;
|
||||
case NfcVTypeSlix2:
|
||||
parsed = nfc_device_load_slix2_data(file, dev);
|
||||
parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix2);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -1025,14 +1025,14 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
|
||||
deactivated = true;
|
||||
} else {
|
||||
// If the key A is marked as found and matches the searching key, invalidate it
|
||||
uint8_t found_key[6];
|
||||
memcpy(found_key, data->block[i].value, 6);
|
||||
MfClassicSectorTrailer* sec_trailer =
|
||||
mf_classic_get_sector_trailer_by_sector(data, i);
|
||||
|
||||
uint8_t current_key[6];
|
||||
memcpy(current_key, &key, 6);
|
||||
nfc_util_num2bytes(key, 6, current_key);
|
||||
|
||||
if(mf_classic_is_key_found(data, i, MfClassicKeyA) &&
|
||||
memcmp(found_key, current_key, 6) == 0) {
|
||||
memcmp(sec_trailer->key_a, current_key, 6) == 0) {
|
||||
mf_classic_set_key_not_found(data, i, MfClassicKeyA);
|
||||
is_key_a_found = false;
|
||||
FURI_LOG_D(TAG, "Key %dA not found in attack", i);
|
||||
@@ -1051,14 +1051,14 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
|
||||
deactivated = true;
|
||||
} else {
|
||||
// If the key B is marked as found and matches the searching key, invalidate it
|
||||
uint8_t found_key[6];
|
||||
memcpy(found_key, data->block[i].value + 10, 6);
|
||||
MfClassicSectorTrailer* sec_trailer =
|
||||
mf_classic_get_sector_trailer_by_sector(data, i);
|
||||
|
||||
uint8_t current_key[6];
|
||||
memcpy(current_key, &key, 6);
|
||||
nfc_util_num2bytes(key, 6, current_key);
|
||||
|
||||
if(mf_classic_is_key_found(data, i, MfClassicKeyB) &&
|
||||
memcmp(found_key, current_key, 6) == 0) {
|
||||
memcmp(sec_trailer->key_b, current_key, 6) == 0) {
|
||||
mf_classic_set_key_not_found(data, i, MfClassicKeyB);
|
||||
is_key_b_found = false;
|
||||
FURI_LOG_D(TAG, "Key %dB not found in attack", i);
|
||||
@@ -1074,7 +1074,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
|
||||
}
|
||||
if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break;
|
||||
}
|
||||
memcpy(&prev_key, &key, sizeof(key));
|
||||
prev_key = key;
|
||||
}
|
||||
if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break;
|
||||
mf_classic_read_sector(&tx_rx, data, i);
|
||||
|
||||
@@ -149,12 +149,18 @@ bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* n
|
||||
return false;
|
||||
}
|
||||
|
||||
/* clear all know sub type data before reading them */
|
||||
memset(&nfcv_data->sub_data, 0x00, sizeof(nfcv_data->sub_data));
|
||||
|
||||
if(slix_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlix;
|
||||
} else if(slix2_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX2 detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlix2;
|
||||
if(slix2_read_custom(nfc_data, nfcv_data) != ERR_NONE) {
|
||||
return false;
|
||||
}
|
||||
} else if(slix_s_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX-S detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlixS;
|
||||
@@ -612,9 +618,34 @@ void nfcv_emu_handle_packet(
|
||||
|
||||
if(ctx->flags & NFCV_REQ_FLAG_AFI) {
|
||||
uint8_t afi = nfcv_data->frame[ctx->payload_offset];
|
||||
if(afi == nfcv_data->afi) {
|
||||
respond = true;
|
||||
|
||||
uint8_t family = (afi & 0xF0);
|
||||
uint8_t subfamily = (afi & 0x0F);
|
||||
|
||||
if(family) {
|
||||
if(subfamily) {
|
||||
/* selected family and subfamily only */
|
||||
if(afi == nfcv_data->afi) {
|
||||
respond = true;
|
||||
}
|
||||
} else {
|
||||
/* selected family, any subfamily */
|
||||
if(family == (nfcv_data->afi & 0xf0)) {
|
||||
respond = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(subfamily) {
|
||||
/* proprietary subfamily only */
|
||||
if(afi == nfcv_data->afi) {
|
||||
respond = true;
|
||||
}
|
||||
} else {
|
||||
/* all families and subfamilies */
|
||||
respond = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
respond = true;
|
||||
}
|
||||
@@ -740,13 +771,19 @@ void nfcv_emu_handle_packet(
|
||||
case NFCV_CMD_READ_MULTI_BLOCK:
|
||||
case NFCV_CMD_READ_BLOCK: {
|
||||
uint8_t block = nfcv_data->frame[ctx->payload_offset];
|
||||
uint8_t blocks = 1;
|
||||
int blocks = 1;
|
||||
|
||||
if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) {
|
||||
blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1;
|
||||
}
|
||||
|
||||
if(block + blocks <= nfcv_data->block_num) {
|
||||
/* limit the maximum block count, underflow accepted */
|
||||
if(block + blocks > nfcv_data->block_num) {
|
||||
blocks = nfcv_data->block_num - block;
|
||||
}
|
||||
|
||||
/* only respond with the valid blocks, if there are any */
|
||||
if(blocks > 0) {
|
||||
uint8_t buffer_pos = 0;
|
||||
|
||||
ctx->response_buffer[buffer_pos++] = NFCV_NOERROR;
|
||||
@@ -773,10 +810,13 @@ void nfcv_emu_handle_packet(
|
||||
ctx->response_flags,
|
||||
ctx->send_time);
|
||||
} else {
|
||||
ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR;
|
||||
ctx->response_buffer[1] = NFCV_ERROR_GENERIC;
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time);
|
||||
/* reply with an error only in addressed or selected mode */
|
||||
if(ctx->addressed || ctx->selected) {
|
||||
ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR;
|
||||
ctx->response_buffer[1] = NFCV_ERROR_GENERIC;
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time);
|
||||
}
|
||||
}
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block);
|
||||
|
||||
|
||||
@@ -139,8 +139,10 @@ typedef enum {
|
||||
} NfcVErrorcodes;
|
||||
|
||||
typedef enum {
|
||||
NfcVLockBitDsfid = 1,
|
||||
NfcVLockBitAfi = 2,
|
||||
NfcVLockBitDsfid = 1 << 0,
|
||||
NfcVLockBitAfi = 1 << 1,
|
||||
NfcVLockBitEas = 1 << 2,
|
||||
NfcVLockBitPpl = 1 << 3,
|
||||
} NfcVLockBits;
|
||||
|
||||
typedef enum {
|
||||
@@ -168,14 +170,55 @@ typedef enum {
|
||||
NfcVSendFlagsHighRate = 1 << 4
|
||||
} NfcVSendFlags;
|
||||
|
||||
/* SLIX specific config flags */
|
||||
typedef enum {
|
||||
NfcVSlixDataFlagsNone = 0,
|
||||
NfcVSlixDataFlagsHasKeyRead = 1 << 0,
|
||||
NfcVSlixDataFlagsHasKeyWrite = 1 << 1,
|
||||
NfcVSlixDataFlagsHasKeyPrivacy = 1 << 2,
|
||||
NfcVSlixDataFlagsHasKeyDestroy = 1 << 3,
|
||||
NfcVSlixDataFlagsHasKeyEas = 1 << 4,
|
||||
NfcVSlixDataFlagsValidKeyRead = 1 << 8,
|
||||
NfcVSlixDataFlagsValidKeyWrite = 1 << 9,
|
||||
NfcVSlixDataFlagsValidKeyPrivacy = 1 << 10,
|
||||
NfcVSlixDataFlagsValidKeyDestroy = 1 << 11,
|
||||
NfcVSlixDataFlagsValidKeyEas = 1 << 12,
|
||||
NfcVSlixDataFlagsPrivacy = 1 << 16,
|
||||
NfcVSlixDataFlagsDestroyed = 1 << 17
|
||||
} NfcVSlixDataFlags;
|
||||
|
||||
/* abstract the file read/write operations for all SLIX types to reduce duplicated code */
|
||||
typedef enum {
|
||||
SlixFeatureRead = 1 << 0,
|
||||
SlixFeatureWrite = 1 << 1,
|
||||
SlixFeaturePrivacy = 1 << 2,
|
||||
SlixFeatureDestroy = 1 << 3,
|
||||
SlixFeatureEas = 1 << 4,
|
||||
SlixFeatureSignature = 1 << 5,
|
||||
SlixFeatureProtection = 1 << 6,
|
||||
|
||||
SlixFeatureSlix = SlixFeatureEas,
|
||||
SlixFeatureSlixS =
|
||||
(SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy |
|
||||
SlixFeatureEas),
|
||||
SlixFeatureSlixL = (SlixFeaturePrivacy | SlixFeatureDestroy | SlixFeatureEas),
|
||||
SlixFeatureSlix2 =
|
||||
(SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy |
|
||||
SlixFeatureEas | SlixFeatureSignature | SlixFeatureProtection),
|
||||
} SlixTypeFeatures;
|
||||
|
||||
typedef struct {
|
||||
uint32_t flags;
|
||||
uint8_t key_read[4];
|
||||
uint8_t key_write[4];
|
||||
uint8_t key_privacy[4];
|
||||
uint8_t key_destroy[4];
|
||||
uint8_t key_eas[4];
|
||||
uint8_t rand[2];
|
||||
bool privacy;
|
||||
uint8_t signature[32];
|
||||
/* SLIX2 options */
|
||||
uint8_t pp_pointer;
|
||||
uint8_t pp_condition;
|
||||
} NfcVSlixData;
|
||||
|
||||
typedef union {
|
||||
|
||||
+413
-41
@@ -9,6 +9,120 @@
|
||||
|
||||
#define TAG "SLIX"
|
||||
|
||||
ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data);
|
||||
|
||||
uint8_t rxBuf[32];
|
||||
uint16_t received = 0;
|
||||
ReturnCode ret = ERR_NONE;
|
||||
|
||||
FURI_LOG_D(TAG, "Read NXP SYSTEM INFORMATION...");
|
||||
|
||||
for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) {
|
||||
uint8_t cmd[] = {};
|
||||
uint8_t uid[NFCV_UID_LENGTH];
|
||||
|
||||
/* UID is stored reversed in requests */
|
||||
for(int pos = 0; pos < nfc_data->uid_len; pos++) {
|
||||
uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos];
|
||||
}
|
||||
|
||||
ReturnCode ret = rfalNfcvPollerTransceiveReq(
|
||||
NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION,
|
||||
RFAL_NFCV_REQ_FLAG_DEFAULT,
|
||||
NFCV_MANUFACTURER_NXP,
|
||||
uid,
|
||||
cmd,
|
||||
sizeof(cmd),
|
||||
rxBuf,
|
||||
sizeof(rxBuf),
|
||||
&received);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret != ERR_NONE || received != 8) { //-V560
|
||||
FURI_LOG_D(TAG, "Failed: %d, %d", ret, received);
|
||||
return ret;
|
||||
}
|
||||
FURI_LOG_D(TAG, "Success...");
|
||||
|
||||
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
||||
slix->pp_pointer = rxBuf[1];
|
||||
slix->pp_condition = rxBuf[2];
|
||||
|
||||
/* convert NXP's to our internal lock bits format */
|
||||
nfcv_data->security_status[0] = 0;
|
||||
nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitDsfid) ? NfcVLockBitDsfid : 0;
|
||||
nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitAfi) ? NfcVLockBitAfi : 0;
|
||||
nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitEas) ? NfcVLockBitEas : 0;
|
||||
nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitPpl) ? NfcVLockBitPpl : 0;
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data);
|
||||
|
||||
uint8_t rxBuf[64];
|
||||
uint16_t received = 0;
|
||||
ReturnCode ret = ERR_NONE;
|
||||
|
||||
FURI_LOG_D(TAG, "Read SIGNATURE...");
|
||||
|
||||
for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) {
|
||||
uint8_t cmd[] = {};
|
||||
uint8_t uid[NFCV_UID_LENGTH];
|
||||
|
||||
/* UID is stored reversed in requests */
|
||||
for(int pos = 0; pos < nfc_data->uid_len; pos++) {
|
||||
uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos];
|
||||
}
|
||||
|
||||
ReturnCode ret = rfalNfcvPollerTransceiveReq(
|
||||
NFCV_CMD_NXP_READ_SIGNATURE,
|
||||
RFAL_NFCV_REQ_FLAG_DEFAULT,
|
||||
NFCV_MANUFACTURER_NXP,
|
||||
uid,
|
||||
cmd,
|
||||
sizeof(cmd),
|
||||
rxBuf,
|
||||
sizeof(rxBuf),
|
||||
&received);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret != ERR_NONE || received != 33) { //-V560
|
||||
FURI_LOG_D(TAG, "Failed: %d, %d", ret, received);
|
||||
return ret;
|
||||
}
|
||||
FURI_LOG_D(TAG, "Success...");
|
||||
|
||||
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
||||
memcpy(slix->signature, &rxBuf[1], 32);
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||
ReturnCode ret = ERR_NONE;
|
||||
|
||||
ret = slix2_read_nxp_sysinfo(nfc_data, nfcv_data);
|
||||
if(ret != ERR_NONE) {
|
||||
return ret;
|
||||
}
|
||||
ret = slix2_read_signature(nfc_data, nfcv_data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t slix_read_be(uint8_t* data, uint32_t length) {
|
||||
uint32_t value = 0;
|
||||
|
||||
@@ -137,6 +251,43 @@ ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void slix_generic_pass_infos(
|
||||
uint8_t password_id,
|
||||
NfcVSlixData* slix,
|
||||
uint8_t** password,
|
||||
uint32_t* flag_valid,
|
||||
uint32_t* flag_set) {
|
||||
switch(password_id) {
|
||||
case SLIX_PASS_READ:
|
||||
*password = slix->key_read;
|
||||
*flag_valid = NfcVSlixDataFlagsValidKeyRead;
|
||||
*flag_set = NfcVSlixDataFlagsHasKeyRead;
|
||||
break;
|
||||
case SLIX_PASS_WRITE:
|
||||
*password = slix->key_write;
|
||||
*flag_valid = NfcVSlixDataFlagsValidKeyWrite;
|
||||
*flag_set = NfcVSlixDataFlagsHasKeyWrite;
|
||||
break;
|
||||
case SLIX_PASS_PRIVACY:
|
||||
*password = slix->key_privacy;
|
||||
*flag_valid = NfcVSlixDataFlagsValidKeyPrivacy;
|
||||
*flag_set = NfcVSlixDataFlagsHasKeyPrivacy;
|
||||
break;
|
||||
case SLIX_PASS_DESTROY:
|
||||
*password = slix->key_destroy;
|
||||
*flag_valid = NfcVSlixDataFlagsValidKeyDestroy;
|
||||
*flag_set = NfcVSlixDataFlagsHasKeyDestroy;
|
||||
break;
|
||||
case SLIX_PASS_EASAFI:
|
||||
*password = slix->key_eas;
|
||||
*flag_valid = NfcVSlixDataFlagsValidKeyEas;
|
||||
*flag_set = NfcVSlixDataFlagsHasKeyEas;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool slix_generic_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
@@ -150,7 +301,8 @@ bool slix_generic_protocol_filter(
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
||||
|
||||
if(slix->privacy && ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER &&
|
||||
if((slix->flags & NfcVSlixDataFlagsPrivacy) &&
|
||||
ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER &&
|
||||
ctx->command != NFCV_CMD_NXP_SET_PASSWORD) {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
@@ -186,66 +338,73 @@ bool slix_generic_protocol_filter(
|
||||
}
|
||||
|
||||
case NFCV_CMD_NXP_SET_PASSWORD: {
|
||||
/* the password to be set is the first parameter */
|
||||
uint8_t password_id = nfcv_data->frame[ctx->payload_offset];
|
||||
/* right after that is the XORed password */
|
||||
uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1];
|
||||
|
||||
/* only handle if the password type is supported */
|
||||
if(!(password_id & password_supported)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1];
|
||||
/* fetch the last RAND value */
|
||||
uint8_t* rand = slix->rand;
|
||||
uint8_t* password = NULL;
|
||||
|
||||
/* first calc the password that has been sent */
|
||||
uint8_t password_rcv[4];
|
||||
|
||||
switch(password_id) {
|
||||
case SLIX_PASS_READ:
|
||||
password = slix->key_read;
|
||||
break;
|
||||
case SLIX_PASS_WRITE:
|
||||
password = slix->key_write;
|
||||
break;
|
||||
case SLIX_PASS_PRIVACY:
|
||||
password = slix->key_privacy;
|
||||
break;
|
||||
case SLIX_PASS_DESTROY:
|
||||
password = slix->key_destroy;
|
||||
break;
|
||||
case SLIX_PASS_EASAFI:
|
||||
password = slix->key_eas;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
for(int pos = 0; pos < 4; pos++) {
|
||||
password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2];
|
||||
}
|
||||
uint32_t pass_received = slix_read_be(password_rcv, 4);
|
||||
|
||||
/* then determine the password type (or even update if not set yet) */
|
||||
uint8_t* password = NULL;
|
||||
uint32_t flag_valid = 0;
|
||||
uint32_t flag_set = 0;
|
||||
|
||||
slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set);
|
||||
|
||||
/* when the password is not supported, return silently */
|
||||
if(!password) {
|
||||
break;
|
||||
}
|
||||
|
||||
for(int pos = 0; pos < 4; pos++) {
|
||||
password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2];
|
||||
}
|
||||
uint32_t pass_expect = slix_read_be(password, 4);
|
||||
uint32_t pass_received = slix_read_be(password_rcv, 4);
|
||||
/* check if the password is known */
|
||||
bool pass_valid = false;
|
||||
uint32_t pass_expect = 0;
|
||||
|
||||
/* if the password is all-zeroes, just accept any password*/
|
||||
if(!pass_expect || pass_expect == pass_received) {
|
||||
if(slix->flags & flag_set) {
|
||||
/* if so, fetch the stored password and compare */
|
||||
pass_expect = slix_read_be(password, 4);
|
||||
pass_valid = (pass_expect == pass_received);
|
||||
} else {
|
||||
/* if not known, just accept it and store that password */
|
||||
memcpy(password, password_rcv, 4);
|
||||
nfcv_data->modified = true;
|
||||
slix->flags |= flag_set;
|
||||
|
||||
pass_valid = true;
|
||||
}
|
||||
|
||||
/* if the pass was valid or accepted for other reasons, continue */
|
||||
if(pass_valid) {
|
||||
slix->flags |= flag_valid;
|
||||
|
||||
/* handle actions when a correct password was given, aside of setting the flag */
|
||||
switch(password_id) {
|
||||
case SLIX_PASS_READ:
|
||||
break;
|
||||
case SLIX_PASS_WRITE:
|
||||
break;
|
||||
case SLIX_PASS_PRIVACY:
|
||||
slix->privacy = false;
|
||||
slix->flags &= ~NfcVSlixDataFlagsPrivacy;
|
||||
nfcv_data->modified = true;
|
||||
break;
|
||||
case SLIX_PASS_DESTROY:
|
||||
slix->flags |= NfcVSlixDataFlagsDestroyed;
|
||||
FURI_LOG_D(TAG, "Pooof! Got destroyed");
|
||||
break;
|
||||
case SLIX_PASS_EASAFI:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ctx->response_buffer[0] = NFCV_NOERROR;
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
@@ -268,6 +427,49 @@ bool slix_generic_protocol_filter(
|
||||
break;
|
||||
}
|
||||
|
||||
case NFCV_CMD_NXP_WRITE_PASSWORD: {
|
||||
uint8_t password_id = nfcv_data->frame[ctx->payload_offset];
|
||||
|
||||
if(!(password_id & password_supported)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t* new_password = &nfcv_data->frame[ctx->payload_offset + 1];
|
||||
uint8_t* password = NULL;
|
||||
uint32_t flag_valid = 0;
|
||||
uint32_t flag_set = 0;
|
||||
|
||||
slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set);
|
||||
|
||||
/* when the password is not supported, return silently */
|
||||
if(!password) {
|
||||
break;
|
||||
}
|
||||
|
||||
bool pass_valid = (slix->flags & flag_valid);
|
||||
if(!(slix->flags & flag_set)) {
|
||||
pass_valid = true;
|
||||
}
|
||||
|
||||
if(pass_valid) {
|
||||
slix->flags |= flag_valid;
|
||||
slix->flags |= flag_set;
|
||||
|
||||
memcpy(password, new_password, 4);
|
||||
|
||||
ctx->response_buffer[0] = NFCV_NOERROR;
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
snprintf(
|
||||
nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD OK");
|
||||
} else {
|
||||
snprintf(
|
||||
nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD FAIL");
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case NFCV_CMD_NXP_ENABLE_PRIVACY: {
|
||||
ctx->response_buffer[0] = NFCV_NOERROR;
|
||||
|
||||
@@ -278,7 +480,7 @@ bool slix_generic_protocol_filter(
|
||||
sizeof(nfcv_data->last_command),
|
||||
"NFCV_CMD_NXP_ENABLE_PRIVACY");
|
||||
|
||||
slix->privacy = true;
|
||||
slix->flags |= NfcVSlixDataFlagsPrivacy;
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
@@ -315,7 +517,10 @@ void slix_l_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" Privacy mode: %s",
|
||||
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix_l_protocol_filter;
|
||||
@@ -345,7 +550,10 @@ void slix_s_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" Privacy mode: %s",
|
||||
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix_s_protocol_filter;
|
||||
@@ -375,7 +583,10 @@ void slix_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" Privacy mode: %s",
|
||||
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix_protocol_filter;
|
||||
@@ -389,6 +600,10 @@ bool slix2_protocol_filter( // -V524
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
NfcVData* nfcv_data = (NfcVData*)nfcv_data_in;
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
||||
|
||||
bool handled = false;
|
||||
|
||||
/* many SLIX share some of the functions, place that in a generic handler */
|
||||
@@ -396,6 +611,160 @@ bool slix2_protocol_filter( // -V524
|
||||
return true;
|
||||
}
|
||||
|
||||
switch(ctx->command) {
|
||||
/* override WRITE BLOCK for block 79 (16 bit counter) */
|
||||
case NFCV_CMD_WRITE_BLOCK:
|
||||
case NFCV_CMD_WRITE_MULTI_BLOCK: {
|
||||
uint8_t resp_len = 1;
|
||||
uint8_t blocks = 1;
|
||||
uint8_t block = nfcv_data->frame[ctx->payload_offset];
|
||||
uint8_t data_pos = ctx->payload_offset + 1;
|
||||
|
||||
if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) {
|
||||
blocks = nfcv_data->frame[data_pos] + 1;
|
||||
data_pos++;
|
||||
}
|
||||
|
||||
uint8_t* data = &nfcv_data->frame[data_pos];
|
||||
uint32_t data_len = nfcv_data->block_size * blocks;
|
||||
|
||||
if((block + blocks) <= nfcv_data->block_num &&
|
||||
(data_pos + data_len + 2) == nfcv_data->frame_length) {
|
||||
ctx->response_buffer[0] = NFCV_NOERROR;
|
||||
|
||||
for(int block_num = block; block_num < block + blocks; block_num++) {
|
||||
/* special case, 16-bit counter */
|
||||
if(block_num == 79) {
|
||||
uint32_t dest;
|
||||
uint32_t ctr_old;
|
||||
|
||||
memcpy(&dest, &nfcv_data->frame[data_pos], 4);
|
||||
memcpy(&ctr_old, &nfcv_data->data[nfcv_data->block_size * block_num], 4);
|
||||
|
||||
uint32_t ctr_new = ctr_old;
|
||||
bool allowed = true;
|
||||
|
||||
/* increment counter */
|
||||
if(dest == 1) {
|
||||
ctr_new = (ctr_old & 0xFFFF0000) | ((ctr_old + 1) & 0xFFFF);
|
||||
|
||||
/* protection flag set? */
|
||||
if(ctr_old & 0x01000000) { //-V1051
|
||||
allowed = nfcv_data->sub_data.slix.flags &
|
||||
NfcVSlixDataFlagsValidKeyRead;
|
||||
}
|
||||
} else {
|
||||
ctr_new = dest;
|
||||
allowed = nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsValidKeyWrite;
|
||||
}
|
||||
|
||||
if(allowed) {
|
||||
memcpy( //-V1086
|
||||
&nfcv_data->data[nfcv_data->block_size * block_num],
|
||||
&ctr_new,
|
||||
4);
|
||||
} else {
|
||||
/* incorrect read or write password */
|
||||
ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR;
|
||||
ctx->response_buffer[1] = NFCV_ERROR_GENERIC;
|
||||
resp_len = 2;
|
||||
}
|
||||
} else {
|
||||
memcpy(
|
||||
&nfcv_data->data[nfcv_data->block_size * block_num],
|
||||
&nfcv_data->frame[data_pos],
|
||||
nfcv_data->block_size);
|
||||
}
|
||||
data_pos += nfcv_data->block_size;
|
||||
}
|
||||
nfcv_data->modified = true;
|
||||
|
||||
} else {
|
||||
ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR;
|
||||
ctx->response_buffer[1] = NFCV_ERROR_GENERIC;
|
||||
resp_len = 2;
|
||||
}
|
||||
|
||||
bool respond = (ctx->response_buffer[0] == NFCV_NOERROR) ||
|
||||
(ctx->addressed || ctx->selected);
|
||||
|
||||
if(respond) {
|
||||
nfcv_emu_send(
|
||||
tx_rx,
|
||||
nfcv_data,
|
||||
ctx->response_buffer,
|
||||
resp_len,
|
||||
ctx->response_flags,
|
||||
ctx->send_time);
|
||||
}
|
||||
|
||||
if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"WRITE MULTI BLOCK %d, %d blocks",
|
||||
block,
|
||||
blocks);
|
||||
} else {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"WRITE BLOCK %d <- %02X %02X %02X %02X",
|
||||
block,
|
||||
data[0],
|
||||
data[1],
|
||||
data[2],
|
||||
data[3]);
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case NFCV_CMD_NXP_READ_SIGNATURE: {
|
||||
uint32_t len = 0;
|
||||
ctx->response_buffer[len++] = NFCV_NOERROR;
|
||||
memcpy(&ctx->response_buffer[len], slix->signature, sizeof(slix->signature));
|
||||
len += sizeof(slix->signature);
|
||||
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time);
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ_SIGNATURE");
|
||||
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION: {
|
||||
uint32_t len = 0;
|
||||
uint8_t lock_bits = 0;
|
||||
|
||||
/* convert our internal lock bits format into NXP's */
|
||||
lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? SlixLockBitDsfid : 0;
|
||||
lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitAfi) ? SlixLockBitAfi : 0;
|
||||
lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitEas) ? SlixLockBitEas : 0;
|
||||
lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitPpl) ? SlixLockBitPpl : 0;
|
||||
|
||||
ctx->response_buffer[len++] = NFCV_NOERROR;
|
||||
ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_pointer;
|
||||
ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_condition;
|
||||
ctx->response_buffer[len++] = lock_bits;
|
||||
ctx->response_buffer[len++] = 0x7F; /* features LSB */
|
||||
ctx->response_buffer[len++] = 0x35; /* features */
|
||||
ctx->response_buffer[len++] = 0; /* features */
|
||||
ctx->response_buffer[len++] = 0; /* features MSB */
|
||||
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time);
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"GET_NXP_SYSTEM_INFORMATION");
|
||||
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
@@ -405,7 +774,10 @@ void slix2_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" Privacy mode: %s",
|
||||
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix2_protocol_filter;
|
||||
|
||||
+33
-13
@@ -8,19 +8,35 @@
|
||||
#define NFCV_MANUFACTURER_NXP 0x04
|
||||
|
||||
/* ISO15693-3 CUSTOM NXP COMMANDS */
|
||||
#define NFCV_CMD_NXP_SET_EAS 0xA2
|
||||
#define NFCV_CMD_NXP_RESET_EAS 0xA3
|
||||
#define NFCV_CMD_NXP_LOCK_EAS 0xA4
|
||||
#define NFCV_CMD_NXP_EAS_ALARM 0xA5
|
||||
#define NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI 0xA6
|
||||
#define NFCV_CMD_NXP_WRITE_EAS_ID 0xA7
|
||||
#define NFCV_CMD_NXP_INVENTORY_PAGE_READ 0xB0
|
||||
#define NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST 0xB1
|
||||
#define NFCV_CMD_NXP_GET_RANDOM_NUMBER 0xB2
|
||||
#define NFCV_CMD_NXP_SET_PASSWORD 0xB3
|
||||
#define NFCV_CMD_NXP_WRITE_PASSWORD 0xB4
|
||||
#define NFCV_CMD_NXP_DESTROY 0xB9
|
||||
#define NFCV_CMD_NXP_ENABLE_PRIVACY 0xBA
|
||||
typedef enum {
|
||||
NFCV_CMD_NXP_SET_EAS = 0xA2,
|
||||
NFCV_CMD_NXP_RESET_EAS = 0xA3,
|
||||
NFCV_CMD_NXP_LOCK_EAS = 0xA4,
|
||||
NFCV_CMD_NXP_EAS_ALARM = 0xA5,
|
||||
NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI = 0xA6,
|
||||
NFCV_CMD_NXP_WRITE_EAS_ID = 0xA7,
|
||||
NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION = 0xAB,
|
||||
NFCV_CMD_NXP_INVENTORY_PAGE_READ = 0xB0,
|
||||
NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST = 0xB1,
|
||||
NFCV_CMD_NXP_GET_RANDOM_NUMBER = 0xB2,
|
||||
NFCV_CMD_NXP_SET_PASSWORD = 0xB3,
|
||||
NFCV_CMD_NXP_WRITE_PASSWORD = 0xB4,
|
||||
NFCV_CMD_NXP_64_BIT_PASSWORD_PROTECTION = 0xB5,
|
||||
NFCV_CMD_NXP_PROTECT_PAGE = 0xB6,
|
||||
NFCV_CMD_NXP_LOCK_PAGE_PROTECTION_CONDITION = 0xB7,
|
||||
NFCV_CMD_NXP_DESTROY = 0xB9,
|
||||
NFCV_CMD_NXP_ENABLE_PRIVACY = 0xBA,
|
||||
NFCV_CMD_NXP_STAY_QUIET_PERSISTENT = 0xBC,
|
||||
NFCV_CMD_NXP_READ_SIGNATURE = 0xBD
|
||||
} SlixCommands;
|
||||
|
||||
/* lock bit bits used in SLIX's NXP SYSTEM INFORMATION response */
|
||||
typedef enum {
|
||||
SlixLockBitAfi = 1 << 0,
|
||||
SlixLockBitEas = 1 << 1,
|
||||
SlixLockBitDsfid = 1 << 2,
|
||||
SlixLockBitPpl = 1 << 3,
|
||||
} SlixLockBits;
|
||||
|
||||
/* available passwords */
|
||||
#define SLIX_PASS_READ 0x01
|
||||
@@ -37,6 +53,10 @@ bool slix2_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
|
||||
ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data);
|
||||
ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data);
|
||||
ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data);
|
||||
|
||||
ReturnCode slix_get_random(NfcVData* data);
|
||||
ReturnCode slix_unlock(NfcVData* data, uint32_t password_id);
|
||||
|
||||
|
||||
@@ -63,7 +63,8 @@ const SubGhzProtocolEncoder subghz_protocol_kia_encoder = {
|
||||
const SubGhzProtocol subghz_protocol_kia = {
|
||||
.name = SUBGHZ_PROTOCOL_KIA_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_AutoAlarms,
|
||||
|
||||
.decoder = &subghz_protocol_kia_decoder,
|
||||
.encoder = &subghz_protocol_kia_encoder,
|
||||
|
||||
@@ -64,7 +64,8 @@ const SubGhzProtocol subghz_protocol_magellan = {
|
||||
.name = SUBGHZ_PROTOCOL_MAGELLAN_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send |
|
||||
SubGhzProtocolFlag_Magelan,
|
||||
|
||||
.decoder = &subghz_protocol_magellan_decoder,
|
||||
.encoder = &subghz_protocol_magellan_encoder,
|
||||
|
||||
@@ -70,7 +70,7 @@ const SubGhzProtocol subghz_protocol_scher_khan = {
|
||||
.name = SUBGHZ_PROTOCOL_SCHER_KHAN_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Save,
|
||||
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_AutoAlarms,
|
||||
|
||||
.decoder = &subghz_protocol_scher_khan_decoder,
|
||||
.encoder = &subghz_protocol_scher_khan_encoder,
|
||||
|
||||
@@ -79,7 +79,8 @@ const SubGhzProtocol subghz_protocol_star_line = {
|
||||
.name = SUBGHZ_PROTOCOL_STAR_LINE_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send |
|
||||
SubGhzProtocolFlag_StarLine,
|
||||
|
||||
.decoder = &subghz_protocol_star_line_decoder,
|
||||
.encoder = &subghz_protocol_star_line_encoder,
|
||||
|
||||
@@ -483,3 +483,35 @@ uint32_t subghz_setting_get_default_frequency(SubGhzSetting* instance) {
|
||||
return subghz_setting_get_frequency(
|
||||
instance, subghz_setting_get_frequency_default_index(instance));
|
||||
}
|
||||
|
||||
uint8_t subghz_setting_customs_presets_to_log(SubGhzSetting* instance) {
|
||||
furi_assert(instance);
|
||||
#ifndef FURI_DEBUG
|
||||
FURI_LOG_I(TAG, "Logging loaded presets allow only Debug build");
|
||||
#else
|
||||
uint8_t count = 0;
|
||||
FuriString* temp = furi_string_alloc();
|
||||
|
||||
FURI_LOG_I(TAG, "Loaded presets");
|
||||
for
|
||||
M_EACH(item, instance->preset->data, SubGhzSettingCustomPresetItemArray_t) {
|
||||
furi_string_reset(temp);
|
||||
|
||||
for(uint8_t i = 0; i < item->custom_preset_data_size; i++) {
|
||||
furi_string_cat_printf(temp, "%02u ", item->custom_preset_data[i]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG, "%u - %s", count + 1, furi_string_get_cstr(item->custom_preset_name));
|
||||
FURI_LOG_I(TAG, " Size: %u", item->custom_preset_data_size);
|
||||
FURI_LOG_I(TAG, " Data: %s", furi_string_get_cstr(temp));
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
furi_string_free(temp);
|
||||
|
||||
return count;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ uint32_t subghz_setting_get_default_frequency(SubGhzSetting* instance);
|
||||
|
||||
void subghz_setting_set_default_frequency(SubGhzSetting* instance, uint32_t frequency_to_setup);
|
||||
|
||||
uint8_t subghz_setting_customs_presets_to_log(SubGhzSetting* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -123,6 +123,9 @@ typedef enum {
|
||||
SubGhzProtocolFlag_Load = (1 << 8),
|
||||
SubGhzProtocolFlag_Send = (1 << 9),
|
||||
SubGhzProtocolFlag_BinRAW = (1 << 10),
|
||||
SubGhzProtocolFlag_StarLine = (1 << 11),
|
||||
SubGhzProtocolFlag_AutoAlarms = (1 << 12),
|
||||
SubGhzProtocolFlag_Magelan = (1 << 13),
|
||||
} SubGhzProtocolFlag;
|
||||
|
||||
struct SubGhzProtocol {
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
#include <m-core.h>
|
||||
|
||||
#define M_INIT_DUP(a) ((a) = strdup(""))
|
||||
#define M_SET_DUP(a, b) (M_CHECK_DEFAULT_TYPE(a), free((void*)a), (a) = strdup(b))
|
||||
#define M_INIT_SET_DUP(a, b) ((a) = strdup(b))
|
||||
#define M_SET_DUP(a, b) (free((void*)a), (a) = strdup(b))
|
||||
#define M_CLEAR_DUP(a) (free((void*)a))
|
||||
|
||||
#define M_CSTR_DUP_OPLIST \
|
||||
(INIT(M_INIT_DUP), \
|
||||
INIT_SET(M_SET_DUP), \
|
||||
SET(M_SET_DUP), \
|
||||
CLEAR(M_CLEAR_DUP), \
|
||||
HASH(m_core_cstr_hash), \
|
||||
EQUAL(M_CSTR_EQUAL), \
|
||||
CMP(strcmp), \
|
||||
#define M_CSTR_DUP_OPLIST \
|
||||
(INIT(M_INIT_DUP), \
|
||||
INIT_SET(M_INIT_SET_DUP), \
|
||||
SET(M_SET_DUP), \
|
||||
CLEAR(M_CLEAR_DUP), \
|
||||
HASH(m_core_cstr_hash), \
|
||||
EQUAL(M_CSTR_EQUAL), \
|
||||
CMP(strcmp), \
|
||||
TYPE(const char*))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define CONTRAST_ERC 32
|
||||
#define CONTRAST_ERC 31
|
||||
#define CONTRAST_MGG 31
|
||||
|
||||
uint8_t u8g2_gpio_and_delay_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) {
|
||||
|
||||
Reference in New Issue
Block a user