mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
Merge remote-tracking branch 'OFW/dev' into dev
This commit is contained in:
@@ -432,6 +432,23 @@ bool mf_classic_is_sector_trailer(uint8_t block) {
|
||||
return block == mf_classic_get_sector_trailer_num_by_block(block);
|
||||
}
|
||||
|
||||
void mf_classic_set_sector_trailer_read(
|
||||
MfClassicData* data,
|
||||
uint8_t block_num,
|
||||
MfClassicSectorTrailer* sec_tr) {
|
||||
furi_check(data);
|
||||
furi_check(sec_tr);
|
||||
furi_check(mf_classic_is_sector_trailer(block_num));
|
||||
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
MfClassicSectorTrailer* sec_trailer =
|
||||
mf_classic_get_sector_trailer_by_sector(data, sector_num);
|
||||
memcpy(sec_trailer, sec_tr, sizeof(MfClassicSectorTrailer));
|
||||
FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32);
|
||||
FURI_BIT_SET(data->key_a_mask, sector_num);
|
||||
FURI_BIT_SET(data->key_b_mask, sector_num);
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_sector_by_block(uint8_t block) {
|
||||
uint8_t sector = 0;
|
||||
|
||||
|
||||
@@ -184,6 +184,11 @@ MfClassicSectorTrailer*
|
||||
|
||||
bool mf_classic_is_sector_trailer(uint8_t block);
|
||||
|
||||
void mf_classic_set_sector_trailer_read(
|
||||
MfClassicData* data,
|
||||
uint8_t block_num,
|
||||
MfClassicSectorTrailer* sec_tr);
|
||||
|
||||
uint8_t mf_classic_get_sector_by_block(uint8_t block);
|
||||
|
||||
bool mf_classic_block_to_value(const MfClassicBlock* block, int32_t* value, uint8_t* addr);
|
||||
|
||||
@@ -13,9 +13,15 @@
|
||||
/** Defines encoder and decoder lookahead buffer size */
|
||||
#define COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG (4u)
|
||||
|
||||
/** Buffer size for input data */
|
||||
#define COMPRESS_ICON_ENCODED_BUFF_SIZE (256u)
|
||||
|
||||
const CompressConfigHeatshrink compress_config_heatshrink_default = {
|
||||
.window_sz2 = COMPRESS_EXP_BUFF_SIZE_LOG,
|
||||
.lookahead_sz2 = COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG,
|
||||
.input_buffer_sz = COMPRESS_ICON_ENCODED_BUFF_SIZE,
|
||||
};
|
||||
|
||||
/** Buffer size for input data */
|
||||
static bool compress_decode_internal(
|
||||
heatshrink_decoder* decoder,
|
||||
const uint8_t* data_in,
|
||||
@@ -83,16 +89,19 @@ void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint
|
||||
}
|
||||
|
||||
struct Compress {
|
||||
const void* config;
|
||||
heatshrink_encoder* encoder;
|
||||
heatshrink_decoder* decoder;
|
||||
};
|
||||
|
||||
Compress* compress_alloc(uint16_t compress_buff_size) {
|
||||
Compress* compress_alloc(CompressType type, const void* config) {
|
||||
furi_check(type == CompressTypeHeatshrink);
|
||||
furi_check(config);
|
||||
|
||||
Compress* compress = malloc(sizeof(Compress));
|
||||
compress->encoder =
|
||||
heatshrink_encoder_alloc(COMPRESS_EXP_BUFF_SIZE_LOG, COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
|
||||
compress->decoder = heatshrink_decoder_alloc(
|
||||
compress_buff_size, COMPRESS_EXP_BUFF_SIZE_LOG, COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
|
||||
compress->config = config;
|
||||
compress->encoder = NULL;
|
||||
compress->decoder = NULL;
|
||||
|
||||
return compress;
|
||||
}
|
||||
@@ -100,8 +109,12 @@ Compress* compress_alloc(uint16_t compress_buff_size) {
|
||||
void compress_free(Compress* compress) {
|
||||
furi_check(compress);
|
||||
|
||||
heatshrink_encoder_free(compress->encoder);
|
||||
heatshrink_decoder_free(compress->decoder);
|
||||
if(compress->encoder) {
|
||||
heatshrink_encoder_free(compress->encoder);
|
||||
}
|
||||
if(compress->decoder) {
|
||||
heatshrink_decoder_free(compress->decoder);
|
||||
}
|
||||
free(compress);
|
||||
}
|
||||
|
||||
@@ -125,6 +138,7 @@ static bool compress_encode_internal(
|
||||
size_t sunk = 0;
|
||||
size_t res_buff_size = sizeof(CompressHeader);
|
||||
|
||||
heatshrink_encoder_reset(encoder);
|
||||
/* Sink data to encoding buffer */
|
||||
while((sunk < data_in_size) && !encode_failed) {
|
||||
sink_res =
|
||||
@@ -179,10 +193,116 @@ static bool compress_encode_internal(
|
||||
*data_res_size = 0;
|
||||
result = false;
|
||||
}
|
||||
heatshrink_encoder_reset(encoder);
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline bool compress_decoder_poll(
|
||||
heatshrink_decoder* decoder,
|
||||
uint8_t* decompressed_chunk,
|
||||
size_t decomp_buffer_size,
|
||||
CompressIoCallback write_cb,
|
||||
void* write_context) {
|
||||
HSD_poll_res poll_res;
|
||||
size_t poll_size;
|
||||
|
||||
do {
|
||||
poll_res =
|
||||
heatshrink_decoder_poll(decoder, decompressed_chunk, decomp_buffer_size, &poll_size);
|
||||
if(poll_res < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t write_size = write_cb(write_context, decompressed_chunk, poll_size);
|
||||
if(write_size != poll_size) {
|
||||
return false;
|
||||
}
|
||||
} while(poll_res == HSDR_POLL_MORE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool compress_decode_stream_internal(
|
||||
heatshrink_decoder* decoder,
|
||||
const size_t work_buffer_size,
|
||||
CompressIoCallback read_cb,
|
||||
void* read_context,
|
||||
CompressIoCallback write_cb,
|
||||
void* write_context) {
|
||||
bool decode_failed = false;
|
||||
HSD_sink_res sink_res;
|
||||
HSD_finish_res finish_res;
|
||||
size_t read_size = 0;
|
||||
size_t sink_size = 0;
|
||||
|
||||
uint8_t* compressed_chunk = malloc(work_buffer_size);
|
||||
uint8_t* decompressed_chunk = malloc(work_buffer_size);
|
||||
|
||||
/* Sink data to decoding buffer */
|
||||
do {
|
||||
read_size = read_cb(read_context, compressed_chunk, work_buffer_size);
|
||||
|
||||
size_t sunk = 0;
|
||||
while(sunk < read_size && !decode_failed) {
|
||||
sink_res = heatshrink_decoder_sink(
|
||||
decoder, &compressed_chunk[sunk], read_size - sunk, &sink_size);
|
||||
if(sink_res < 0) {
|
||||
decode_failed = true;
|
||||
break;
|
||||
}
|
||||
sunk += sink_size;
|
||||
|
||||
if(!compress_decoder_poll(
|
||||
decoder, decompressed_chunk, work_buffer_size, write_cb, write_context)) {
|
||||
decode_failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(!decode_failed && read_size);
|
||||
|
||||
/* Notify sinking complete and poll decoded data */
|
||||
if(!decode_failed) {
|
||||
while((finish_res = heatshrink_decoder_finish(decoder)) != HSDR_FINISH_DONE) {
|
||||
if(finish_res < 0) {
|
||||
decode_failed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!compress_decoder_poll(
|
||||
decoder, decompressed_chunk, work_buffer_size, write_cb, write_context)) {
|
||||
decode_failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(compressed_chunk);
|
||||
free(decompressed_chunk);
|
||||
|
||||
return !decode_failed;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t* data_ptr;
|
||||
size_t data_size;
|
||||
bool is_source;
|
||||
} MemoryStreamState;
|
||||
|
||||
static int32_t memory_stream_io_callback(void* context, uint8_t* ptr, size_t size) {
|
||||
MemoryStreamState* state = (MemoryStreamState*)context;
|
||||
|
||||
if(size > state->data_size) {
|
||||
size = state->data_size;
|
||||
}
|
||||
if(state->is_source) {
|
||||
memcpy(ptr, state->data_ptr, size);
|
||||
} else {
|
||||
memcpy(state->data_ptr, ptr, size);
|
||||
}
|
||||
state->data_ptr += size;
|
||||
state->data_size -= size;
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool compress_decode_internal(
|
||||
heatshrink_decoder* decoder,
|
||||
const uint8_t* data_in,
|
||||
@@ -196,59 +316,29 @@ static bool compress_decode_internal(
|
||||
furi_check(data_res_size);
|
||||
|
||||
bool result = false;
|
||||
bool decode_failed = false;
|
||||
HSD_sink_res sink_res;
|
||||
HSD_poll_res poll_res;
|
||||
HSD_finish_res finish_res;
|
||||
size_t sink_size = 0;
|
||||
size_t res_buff_size = 0;
|
||||
size_t poll_size = 0;
|
||||
|
||||
CompressHeader* header = (CompressHeader*)data_in;
|
||||
if(header->is_compressed) {
|
||||
/* Sink data to decoding buffer */
|
||||
size_t compressed_size = header->compressed_buff_size;
|
||||
size_t sunk = 0;
|
||||
while(sunk < compressed_size && !decode_failed) {
|
||||
sink_res = heatshrink_decoder_sink(
|
||||
MemoryStreamState compressed_context = {
|
||||
.data_ptr = (uint8_t*)&data_in[sizeof(CompressHeader)],
|
||||
.data_size = header->compressed_buff_size,
|
||||
.is_source = true,
|
||||
};
|
||||
MemoryStreamState decompressed_context = {
|
||||
.data_ptr = data_out,
|
||||
.data_size = data_out_size,
|
||||
.is_source = false,
|
||||
};
|
||||
heatshrink_decoder_reset(decoder);
|
||||
if((result = compress_decode_stream_internal(
|
||||
decoder,
|
||||
(uint8_t*)&data_in[sizeof(CompressHeader) + sunk],
|
||||
compressed_size - sunk,
|
||||
&sink_size);
|
||||
if(sink_res < 0) {
|
||||
decode_failed = true;
|
||||
break;
|
||||
}
|
||||
sunk += sink_size;
|
||||
do {
|
||||
poll_res = heatshrink_decoder_poll(
|
||||
decoder, &data_out[res_buff_size], data_out_size - res_buff_size, &poll_size);
|
||||
if((poll_res < 0) || ((data_out_size - res_buff_size) == 0)) {
|
||||
decode_failed = true;
|
||||
break;
|
||||
}
|
||||
res_buff_size += poll_size;
|
||||
} while(poll_res == HSDR_POLL_MORE);
|
||||
COMPRESS_ICON_ENCODED_BUFF_SIZE,
|
||||
memory_stream_io_callback,
|
||||
&compressed_context,
|
||||
memory_stream_io_callback,
|
||||
&decompressed_context))) {
|
||||
*data_res_size = data_out_size - decompressed_context.data_size;
|
||||
}
|
||||
/* Notify sinking complete and poll decoded data */
|
||||
if(!decode_failed) {
|
||||
finish_res = heatshrink_decoder_finish(decoder);
|
||||
if(finish_res < 0) {
|
||||
decode_failed = true;
|
||||
} else {
|
||||
do {
|
||||
poll_res = heatshrink_decoder_poll(
|
||||
decoder,
|
||||
&data_out[res_buff_size],
|
||||
data_out_size - res_buff_size,
|
||||
&poll_size);
|
||||
res_buff_size += poll_size;
|
||||
finish_res = heatshrink_decoder_finish(decoder);
|
||||
} while(finish_res != HSDR_FINISH_DONE);
|
||||
}
|
||||
}
|
||||
*data_res_size = res_buff_size;
|
||||
result = !decode_failed;
|
||||
} else if(data_out_size >= data_in_size - 1) {
|
||||
memcpy(data_out, &data_in[1], data_in_size);
|
||||
*data_res_size = data_in_size - 1;
|
||||
@@ -257,7 +347,6 @@ static bool compress_decode_internal(
|
||||
/* Not enough space in output buffer */
|
||||
result = false;
|
||||
}
|
||||
heatshrink_decoder_reset(decoder);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -268,6 +357,11 @@ bool compress_encode(
|
||||
uint8_t* data_out,
|
||||
size_t data_out_size,
|
||||
size_t* data_res_size) {
|
||||
if(!compress->encoder) {
|
||||
CompressConfigHeatshrink* hs_config = (CompressConfigHeatshrink*)compress->config;
|
||||
compress->encoder =
|
||||
heatshrink_encoder_alloc(hs_config->window_sz2, hs_config->lookahead_sz2);
|
||||
}
|
||||
return compress_encode_internal(
|
||||
compress->encoder, data_in, data_in_size, data_out, data_out_size, data_res_size);
|
||||
}
|
||||
@@ -279,6 +373,201 @@ bool compress_decode(
|
||||
uint8_t* data_out,
|
||||
size_t data_out_size,
|
||||
size_t* data_res_size) {
|
||||
if(!compress->decoder) {
|
||||
CompressConfigHeatshrink* hs_config = (CompressConfigHeatshrink*)compress->config;
|
||||
compress->decoder = heatshrink_decoder_alloc(
|
||||
hs_config->input_buffer_sz, hs_config->window_sz2, hs_config->lookahead_sz2);
|
||||
}
|
||||
return compress_decode_internal(
|
||||
compress->decoder, data_in, data_in_size, data_out, data_out_size, data_res_size);
|
||||
}
|
||||
|
||||
bool compress_decode_streamed(
|
||||
Compress* compress,
|
||||
CompressIoCallback read_cb,
|
||||
void* read_context,
|
||||
CompressIoCallback write_cb,
|
||||
void* write_context) {
|
||||
CompressConfigHeatshrink* hs_config = (CompressConfigHeatshrink*)compress->config;
|
||||
if(!compress->decoder) {
|
||||
compress->decoder = heatshrink_decoder_alloc(
|
||||
hs_config->input_buffer_sz, hs_config->window_sz2, hs_config->lookahead_sz2);
|
||||
}
|
||||
|
||||
heatshrink_decoder_reset(compress->decoder);
|
||||
return compress_decode_stream_internal(
|
||||
compress->decoder,
|
||||
hs_config->input_buffer_sz,
|
||||
read_cb,
|
||||
read_context,
|
||||
write_cb,
|
||||
write_context);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct CompressStreamDecoder {
|
||||
heatshrink_decoder* decoder;
|
||||
size_t stream_position;
|
||||
size_t decode_buffer_size;
|
||||
size_t decode_buffer_position;
|
||||
uint8_t* decode_buffer;
|
||||
CompressIoCallback read_cb;
|
||||
void* read_context;
|
||||
};
|
||||
|
||||
CompressStreamDecoder* compress_stream_decoder_alloc(
|
||||
CompressType type,
|
||||
const void* config,
|
||||
CompressIoCallback read_cb,
|
||||
void* read_context) {
|
||||
furi_check(type == CompressTypeHeatshrink);
|
||||
furi_check(config);
|
||||
|
||||
const CompressConfigHeatshrink* hs_config = (const CompressConfigHeatshrink*)config;
|
||||
CompressStreamDecoder* instance = malloc(sizeof(CompressStreamDecoder));
|
||||
instance->decoder = heatshrink_decoder_alloc(
|
||||
hs_config->input_buffer_sz, hs_config->window_sz2, hs_config->lookahead_sz2);
|
||||
instance->stream_position = 0;
|
||||
instance->decode_buffer_size = hs_config->input_buffer_sz;
|
||||
instance->decode_buffer_position = 0;
|
||||
instance->decode_buffer = malloc(hs_config->input_buffer_sz);
|
||||
instance->read_cb = read_cb;
|
||||
instance->read_context = read_context;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void compress_stream_decoder_free(CompressStreamDecoder* instance) {
|
||||
furi_check(instance);
|
||||
heatshrink_decoder_free(instance->decoder);
|
||||
free(instance->decode_buffer);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static bool compress_decode_stream_chunk(
|
||||
CompressStreamDecoder* sd,
|
||||
CompressIoCallback read_cb,
|
||||
void* read_context,
|
||||
uint8_t* decompressed_chunk,
|
||||
size_t decomp_chunk_size) {
|
||||
HSD_sink_res sink_res;
|
||||
HSD_poll_res poll_res;
|
||||
|
||||
/*
|
||||
First, try to output data from decoder to the output buffer.
|
||||
If the we could fill the output buffer, return
|
||||
If the output buffer is not full, keep polling the decoder
|
||||
until it has no more data to output.
|
||||
Then, read more data from the input and sink it to the decoder.
|
||||
Repeat until the input is exhausted or output buffer is full.
|
||||
*/
|
||||
|
||||
bool failed = false;
|
||||
bool can_sink_more = true;
|
||||
bool can_read_more = true;
|
||||
|
||||
do {
|
||||
do {
|
||||
size_t poll_size = 0;
|
||||
poll_res = heatshrink_decoder_poll(
|
||||
sd->decoder, decompressed_chunk, decomp_chunk_size, &poll_size);
|
||||
if(poll_res < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
decomp_chunk_size -= poll_size;
|
||||
decompressed_chunk += poll_size;
|
||||
} while((poll_res == HSDR_POLL_MORE) && decomp_chunk_size);
|
||||
|
||||
if(!decomp_chunk_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(can_read_more && (sd->decode_buffer_position < sd->decode_buffer_size)) {
|
||||
size_t read_size = read_cb(
|
||||
read_context,
|
||||
&sd->decode_buffer[sd->decode_buffer_position],
|
||||
sd->decode_buffer_size - sd->decode_buffer_position);
|
||||
sd->decode_buffer_position += read_size;
|
||||
can_read_more = read_size > 0;
|
||||
}
|
||||
|
||||
while(sd->decode_buffer_position && can_sink_more) {
|
||||
size_t sink_size = 0;
|
||||
sink_res = heatshrink_decoder_sink(
|
||||
sd->decoder, sd->decode_buffer, sd->decode_buffer_position, &sink_size);
|
||||
can_sink_more = sink_res == HSDR_SINK_OK;
|
||||
if(sink_res < 0) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
sd->decode_buffer_position -= sink_size;
|
||||
|
||||
/* If some data was left in the buffer, move it to the beginning */
|
||||
if(sink_size && sd->decode_buffer_position) {
|
||||
memmove(
|
||||
sd->decode_buffer, &sd->decode_buffer[sink_size], sd->decode_buffer_position);
|
||||
}
|
||||
}
|
||||
} while(!failed);
|
||||
|
||||
return decomp_chunk_size == 0;
|
||||
}
|
||||
|
||||
bool compress_stream_decoder_read(
|
||||
CompressStreamDecoder* instance,
|
||||
uint8_t* data_out,
|
||||
size_t data_out_size) {
|
||||
furi_check(instance);
|
||||
furi_check(data_out);
|
||||
|
||||
if(compress_decode_stream_chunk(
|
||||
instance, instance->read_cb, instance->read_context, data_out, data_out_size)) {
|
||||
instance->stream_position += data_out_size;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool compress_stream_decoder_seek(CompressStreamDecoder* instance, size_t position) {
|
||||
furi_check(instance);
|
||||
|
||||
/* Check if requested position is ahead of current position
|
||||
we can't rewind the input stream */
|
||||
furi_check(position >= instance->stream_position);
|
||||
|
||||
/* Read and discard data up to requested position */
|
||||
uint8_t* dummy_buffer = malloc(instance->decode_buffer_size);
|
||||
bool success = true;
|
||||
|
||||
while(instance->stream_position < position) {
|
||||
size_t bytes_to_read = position - instance->stream_position;
|
||||
if(bytes_to_read > instance->decode_buffer_size) {
|
||||
bytes_to_read = instance->decode_buffer_size;
|
||||
}
|
||||
if(!compress_stream_decoder_read(instance, dummy_buffer, bytes_to_read)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(dummy_buffer);
|
||||
return success;
|
||||
}
|
||||
|
||||
size_t compress_stream_decoder_tell(CompressStreamDecoder* instance) {
|
||||
furi_check(instance);
|
||||
return instance->stream_position;
|
||||
}
|
||||
|
||||
bool compress_stream_decoder_rewind(CompressStreamDecoder* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
/* Reset decoder and read buffer */
|
||||
heatshrink_decoder_reset(instance->decoder);
|
||||
instance->stream_position = 0;
|
||||
instance->decode_buffer_position = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -44,17 +44,34 @@ void compress_icon_free(CompressIcon* instance);
|
||||
*/
|
||||
void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** output);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** Compress control structure */
|
||||
typedef struct Compress Compress;
|
||||
|
||||
/** Supported compression types */
|
||||
typedef enum {
|
||||
CompressTypeHeatshrink = 0,
|
||||
} CompressType;
|
||||
|
||||
/** Configuration for heatshrink compression */
|
||||
typedef struct {
|
||||
uint16_t window_sz2;
|
||||
uint16_t lookahead_sz2;
|
||||
uint16_t input_buffer_sz;
|
||||
} CompressConfigHeatshrink;
|
||||
|
||||
/** Default configuration for heatshrink compression. Used for image assets. */
|
||||
extern const CompressConfigHeatshrink compress_config_heatshrink_default;
|
||||
|
||||
/** Allocate encoder and decoder
|
||||
*
|
||||
* @param compress_buff_size size of decoder and encoder buffer to
|
||||
* allocate
|
||||
* @param type Compression type
|
||||
* @param[in] config Configuration for compression, specific to type
|
||||
*
|
||||
* @return Compress instance
|
||||
*/
|
||||
Compress* compress_alloc(uint16_t compress_buff_size);
|
||||
Compress* compress_alloc(CompressType type, const void* config);
|
||||
|
||||
/** Free encoder and decoder
|
||||
*
|
||||
@@ -71,6 +88,8 @@ void compress_free(Compress* compress);
|
||||
* @param[in] data_out_size The data out size
|
||||
* @param data_res_size pointer to result output data size
|
||||
*
|
||||
* @note Prepends compressed stream with a header. If data is not compressible,
|
||||
* it will be stored as is after the header.
|
||||
* @return true on success
|
||||
*/
|
||||
bool compress_encode(
|
||||
@@ -90,6 +109,7 @@ bool compress_encode(
|
||||
* @param[in] data_out_size The data out size
|
||||
* @param data_res_size pointer to result output data size
|
||||
*
|
||||
* @note Expects compressed stream with a header, as produced by `compress_encode`.
|
||||
* @return true on success
|
||||
*/
|
||||
bool compress_decode(
|
||||
@@ -100,6 +120,100 @@ bool compress_decode(
|
||||
size_t data_out_size,
|
||||
size_t* data_res_size);
|
||||
|
||||
/** I/O callback for streamed compression/decompression
|
||||
*
|
||||
* @param context user context
|
||||
* @param buffer buffer to read/write
|
||||
* @param size size of buffer
|
||||
*
|
||||
* @return number of bytes read/written, 0 on end of stream, negative on error
|
||||
*/
|
||||
typedef int32_t (*CompressIoCallback)(void* context, uint8_t* buffer, size_t size);
|
||||
|
||||
/** Decompress streamed data
|
||||
*
|
||||
* @param compress Compress instance
|
||||
* @param read_cb read callback
|
||||
* @param read_context read callback context
|
||||
* @param write_cb write callback
|
||||
* @param write_context write callback context
|
||||
*
|
||||
* @note Does not expect a header, just compressed data stream.
|
||||
* @return true on success
|
||||
*/
|
||||
bool compress_decode_streamed(
|
||||
Compress* compress,
|
||||
CompressIoCallback read_cb,
|
||||
void* read_context,
|
||||
CompressIoCallback write_cb,
|
||||
void* write_context);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** CompressStreamDecoder control structure */
|
||||
typedef struct CompressStreamDecoder CompressStreamDecoder;
|
||||
|
||||
/** Allocate stream decoder
|
||||
*
|
||||
* @param type Compression type
|
||||
* @param[in] config Configuration for compression, specific to type
|
||||
* @param read_cb The read callback for input (compressed) data
|
||||
* @param read_context The read context
|
||||
*
|
||||
* @return CompressStreamDecoder instance
|
||||
*/
|
||||
CompressStreamDecoder* compress_stream_decoder_alloc(
|
||||
CompressType type,
|
||||
const void* config,
|
||||
CompressIoCallback read_cb,
|
||||
void* read_context);
|
||||
|
||||
/** Free stream decoder
|
||||
*
|
||||
* @param instance The CompressStreamDecoder instance
|
||||
*/
|
||||
void compress_stream_decoder_free(CompressStreamDecoder* instance);
|
||||
|
||||
/** Read uncompressed data chunk from stream decoder
|
||||
*
|
||||
* @param instance The CompressStreamDecoder instance
|
||||
* @param data_out The data out
|
||||
* @param[in] data_out_size The data out size
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool compress_stream_decoder_read(
|
||||
CompressStreamDecoder* instance,
|
||||
uint8_t* data_out,
|
||||
size_t data_out_size);
|
||||
|
||||
/** Seek to position in uncompressed data stream
|
||||
*
|
||||
* @param instance The CompressStreamDecoder instance
|
||||
* @param[in] position The position
|
||||
*
|
||||
* @return true on success
|
||||
* @warning Backward seeking is not supported
|
||||
*/
|
||||
bool compress_stream_decoder_seek(CompressStreamDecoder* instance, size_t position);
|
||||
|
||||
/** Get current position in uncompressed data stream
|
||||
*
|
||||
* @param instance The CompressStreamDecoder instance
|
||||
*
|
||||
* @return current position
|
||||
*/
|
||||
size_t compress_stream_decoder_tell(CompressStreamDecoder* instance);
|
||||
|
||||
/** Reset stream decoder to the beginning
|
||||
* @warning Read callback must be repositioned by caller separately
|
||||
*
|
||||
* @param instance The CompressStreamDecoder instance
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool compress_stream_decoder_rewind(CompressStreamDecoder* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -43,6 +43,7 @@ void path_extract_filename(FuriString* path, FuriString* name, bool trim_ext) {
|
||||
void path_extract_extension(FuriString* path, char* ext, size_t ext_len_max) {
|
||||
furi_check(path);
|
||||
furi_check(ext);
|
||||
furi_check(ext_len_max > 0);
|
||||
|
||||
size_t dot = furi_string_search_rchar(path, '.');
|
||||
size_t filename_start = furi_string_search_rchar(path, '/');
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <storage/storage.h>
|
||||
#include <furi.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <toolbox/compress.h>
|
||||
|
||||
#define TAG "TarArch"
|
||||
#define MAX_NAME_LEN 255
|
||||
@@ -12,14 +13,29 @@
|
||||
#define FILE_OPEN_NTRIES 10
|
||||
#define FILE_OPEN_RETRY_DELAY 25
|
||||
|
||||
TarOpenMode tar_archive_get_mode_for_path(const char* path) {
|
||||
char ext[8];
|
||||
|
||||
FuriString* path_str = furi_string_alloc_set_str(path);
|
||||
path_extract_extension(path_str, ext, sizeof(ext));
|
||||
furi_string_free(path_str);
|
||||
|
||||
if(strcmp(ext, ".ths") == 0) {
|
||||
return TarOpenModeReadHeatshrink;
|
||||
} else {
|
||||
return TarOpenModeRead;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct TarArchive {
|
||||
Storage* storage;
|
||||
File* stream;
|
||||
mtar_t tar;
|
||||
tar_unpack_file_cb unpack_cb;
|
||||
void* unpack_cb_context;
|
||||
} TarArchive;
|
||||
|
||||
/* API WRAPPER */
|
||||
/* Plain file backend - uncompressed, supports read and write */
|
||||
static int mtar_storage_file_write(void* stream, const void* data, unsigned size) {
|
||||
uint16_t bytes_written = storage_file_write(stream, data, size);
|
||||
return (bytes_written == size) ? bytes_written : MTAR_EWRITEFAIL;
|
||||
@@ -38,7 +54,6 @@ static int mtar_storage_file_seek(void* stream, unsigned offset) {
|
||||
static int mtar_storage_file_close(void* stream) {
|
||||
if(stream) {
|
||||
storage_file_close(stream);
|
||||
storage_file_free(stream);
|
||||
}
|
||||
return MTAR_ESUCCESS;
|
||||
}
|
||||
@@ -50,41 +65,133 @@ const struct mtar_ops filesystem_ops = {
|
||||
.close = mtar_storage_file_close,
|
||||
};
|
||||
|
||||
/* Heatshrink stream backend - compressed, read-only */
|
||||
|
||||
typedef struct {
|
||||
CompressConfigHeatshrink heatshrink_config;
|
||||
File* stream;
|
||||
CompressStreamDecoder* decoder;
|
||||
} HeatshrinkStream;
|
||||
|
||||
/* HSDS 'heatshrink data stream' header magic */
|
||||
static const uint32_t HEATSHRINK_MAGIC = 0x53445348;
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint8_t version;
|
||||
uint8_t window_sz2;
|
||||
uint8_t lookahead_sz2;
|
||||
} FURI_PACKED HeatshrinkStreamHeader;
|
||||
_Static_assert(sizeof(HeatshrinkStreamHeader) == 7, "Invalid HeatshrinkStreamHeader size");
|
||||
|
||||
static int mtar_heatshrink_file_close(void* stream) {
|
||||
HeatshrinkStream* hs_stream = stream;
|
||||
if(hs_stream) {
|
||||
if(hs_stream->decoder) {
|
||||
compress_stream_decoder_free(hs_stream->decoder);
|
||||
}
|
||||
storage_file_close(hs_stream->stream);
|
||||
storage_file_free(hs_stream->stream);
|
||||
free(hs_stream);
|
||||
}
|
||||
return MTAR_ESUCCESS;
|
||||
}
|
||||
|
||||
static int mtar_heatshrink_file_read(void* stream, void* data, unsigned size) {
|
||||
HeatshrinkStream* hs_stream = stream;
|
||||
bool read_success = compress_stream_decoder_read(hs_stream->decoder, data, size);
|
||||
return read_success ? (int)size : MTAR_EREADFAIL;
|
||||
}
|
||||
|
||||
static int mtar_heatshrink_file_seek(void* stream, unsigned offset) {
|
||||
HeatshrinkStream* hs_stream = stream;
|
||||
bool success = false;
|
||||
if(offset == 0) {
|
||||
success = storage_file_seek(hs_stream->stream, sizeof(HeatshrinkStreamHeader), true) &&
|
||||
compress_stream_decoder_rewind(hs_stream->decoder);
|
||||
} else {
|
||||
success = compress_stream_decoder_seek(hs_stream->decoder, offset);
|
||||
}
|
||||
return success ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
|
||||
}
|
||||
|
||||
const struct mtar_ops heatshrink_ops = {
|
||||
.read = mtar_heatshrink_file_read,
|
||||
.write = NULL, // not supported
|
||||
.seek = mtar_heatshrink_file_seek,
|
||||
.close = mtar_heatshrink_file_close,
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TarArchive* tar_archive_alloc(Storage* storage) {
|
||||
furi_check(storage);
|
||||
TarArchive* archive = malloc(sizeof(TarArchive));
|
||||
archive->storage = storage;
|
||||
archive->stream = storage_file_alloc(archive->storage);
|
||||
archive->unpack_cb = NULL;
|
||||
return archive;
|
||||
}
|
||||
|
||||
static int32_t file_read_cb(void* context, uint8_t* buffer, size_t buffer_size) {
|
||||
File* file = context;
|
||||
return storage_file_read(file, buffer, buffer_size);
|
||||
}
|
||||
|
||||
bool tar_archive_open(TarArchive* archive, const char* path, TarOpenMode mode) {
|
||||
furi_check(archive);
|
||||
FS_AccessMode access_mode;
|
||||
FS_OpenMode open_mode;
|
||||
bool compressed = false;
|
||||
int mtar_access = 0;
|
||||
|
||||
switch(mode) {
|
||||
case TAR_OPEN_MODE_READ:
|
||||
case TarOpenModeRead:
|
||||
mtar_access = MTAR_READ;
|
||||
access_mode = FSAM_READ;
|
||||
open_mode = FSOM_OPEN_EXISTING;
|
||||
break;
|
||||
case TAR_OPEN_MODE_WRITE:
|
||||
case TarOpenModeWrite:
|
||||
mtar_access = MTAR_WRITE;
|
||||
access_mode = FSAM_WRITE;
|
||||
open_mode = FSOM_CREATE_ALWAYS;
|
||||
break;
|
||||
case TarOpenModeReadHeatshrink:
|
||||
mtar_access = MTAR_READ;
|
||||
access_mode = FSAM_READ;
|
||||
open_mode = FSOM_OPEN_EXISTING;
|
||||
compressed = true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
File* stream = storage_file_alloc(archive->storage);
|
||||
File* stream = archive->stream;
|
||||
if(!storage_file_open(stream, path, access_mode, open_mode)) {
|
||||
storage_file_free(stream);
|
||||
return false;
|
||||
}
|
||||
mtar_init(&archive->tar, mtar_access, &filesystem_ops, stream);
|
||||
|
||||
if(compressed) {
|
||||
/* Read and validate stream header */
|
||||
HeatshrinkStreamHeader header;
|
||||
if(storage_file_read(stream, &header, sizeof(HeatshrinkStreamHeader)) !=
|
||||
sizeof(HeatshrinkStreamHeader) ||
|
||||
header.magic != HEATSHRINK_MAGIC) {
|
||||
storage_file_close(stream);
|
||||
return false;
|
||||
}
|
||||
|
||||
HeatshrinkStream* hs_stream = malloc(sizeof(HeatshrinkStream));
|
||||
hs_stream->stream = stream;
|
||||
hs_stream->heatshrink_config.window_sz2 = header.window_sz2;
|
||||
hs_stream->heatshrink_config.lookahead_sz2 = header.lookahead_sz2;
|
||||
hs_stream->heatshrink_config.input_buffer_sz = FILE_BLOCK_SIZE;
|
||||
hs_stream->decoder = compress_stream_decoder_alloc(
|
||||
CompressTypeHeatshrink, &hs_stream->heatshrink_config, file_read_cb, stream);
|
||||
mtar_init(&archive->tar, mtar_access, &heatshrink_ops, hs_stream);
|
||||
} else {
|
||||
mtar_init(&archive->tar, mtar_access, &filesystem_ops, stream);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -94,6 +201,7 @@ void tar_archive_free(TarArchive* archive) {
|
||||
if(mtar_is_open(&archive->tar)) {
|
||||
mtar_close(&archive->tar);
|
||||
}
|
||||
storage_file_free(archive->stream);
|
||||
free(archive);
|
||||
}
|
||||
|
||||
@@ -121,6 +229,21 @@ int32_t tar_archive_get_entries_count(TarArchive* archive) {
|
||||
return counter;
|
||||
}
|
||||
|
||||
bool tar_archive_get_read_progress(TarArchive* archive, int32_t* processed, int32_t* total) {
|
||||
furi_check(archive);
|
||||
if(mtar_access_mode(&archive->tar) != MTAR_READ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(processed) {
|
||||
*processed = storage_file_tell(archive->stream);
|
||||
}
|
||||
if(total) {
|
||||
*total = storage_file_size(archive->stream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tar_archive_dir_add_element(TarArchive* archive, const char* dirpath) {
|
||||
furi_check(archive);
|
||||
return (mtar_write_dir_header(&archive->tar, dirpath) == MTAR_ESUCCESS);
|
||||
@@ -258,7 +381,7 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header,
|
||||
|
||||
furi_string_free(converted_fname);
|
||||
furi_string_free(full_extracted_fname);
|
||||
return success ? 0 : -1;
|
||||
return success ? 0 : MTAR_EFAILURE;
|
||||
}
|
||||
|
||||
bool tar_archive_unpack_to(
|
||||
|
||||
@@ -12,62 +12,197 @@ typedef struct TarArchive TarArchive;
|
||||
|
||||
typedef struct Storage Storage;
|
||||
|
||||
/** Tar archive open mode
|
||||
*/
|
||||
typedef enum {
|
||||
TAR_OPEN_MODE_READ = 'r',
|
||||
TAR_OPEN_MODE_WRITE = 'w',
|
||||
TAR_OPEN_MODE_STDOUT = 's' /* to be implemented */
|
||||
TarOpenModeRead = 'r',
|
||||
TarOpenModeWrite = 'w',
|
||||
/* read-only heatshrink compressed tar */
|
||||
TarOpenModeReadHeatshrink = 'h',
|
||||
} TarOpenMode;
|
||||
|
||||
/** Get expected open mode for archive at the path.
|
||||
* Used for automatic mode detection based on the file extension.
|
||||
*
|
||||
* @param[in] path Path to the archive
|
||||
*
|
||||
* @return open mode from TarOpenMode enum
|
||||
*/
|
||||
TarOpenMode tar_archive_get_mode_for_path(const char* path);
|
||||
|
||||
/** Tar archive constructor
|
||||
*
|
||||
* @param storage Storage API pointer
|
||||
*
|
||||
* @return allocated object
|
||||
*/
|
||||
TarArchive* tar_archive_alloc(Storage* storage);
|
||||
|
||||
/** Open tar archive
|
||||
*
|
||||
* @param archive Tar archive object
|
||||
* @param[in] path Path to the tar archive
|
||||
* @param mode Open mode
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_open(TarArchive* archive, const char* path, TarOpenMode mode);
|
||||
|
||||
/** Tar archive destructor
|
||||
*
|
||||
* @param archive Tar archive object
|
||||
*/
|
||||
void tar_archive_free(TarArchive* archive);
|
||||
|
||||
/* High-level API - assumes archive is open */
|
||||
|
||||
/** Unpack tar archive to destination
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in read mode
|
||||
* @param[in] destination Destination path
|
||||
* @param converter Storage name converter
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_unpack_to(
|
||||
TarArchive* archive,
|
||||
const char* destination,
|
||||
Storage_name_converter converter);
|
||||
|
||||
/** Add file to tar archive
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in write mode
|
||||
* @param[in] fs_file_path Path to the file on the filesystem
|
||||
* @param[in] archive_fname Name of the file in the archive
|
||||
* @param file_size Size of the file
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_add_file(
|
||||
TarArchive* archive,
|
||||
const char* fs_file_path,
|
||||
const char* archive_fname,
|
||||
const int32_t file_size);
|
||||
|
||||
/** Add directory to tar archive
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in write mode
|
||||
* @param fs_full_path Path to the directory on the filesystem
|
||||
* @param path_prefix Prefix to add to the directory name in the archive
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const char* path_prefix);
|
||||
|
||||
/** Get number of entries in the archive
|
||||
*
|
||||
* @param archive Tar archive object
|
||||
*
|
||||
* @return number of entries. -1 on error
|
||||
*/
|
||||
int32_t tar_archive_get_entries_count(TarArchive* archive);
|
||||
|
||||
/** Get read progress
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in read mode
|
||||
* @param[in] processed Number of processed entries
|
||||
* @param[in] total Total number of entries
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_get_read_progress(TarArchive* archive, int32_t* processed, int32_t* total);
|
||||
|
||||
/** Unpack single file from tar archive
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in read mode
|
||||
* @param[in] archive_fname Name of the file in the archive
|
||||
* @param[in] destination Destination path
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_unpack_file(
|
||||
TarArchive* archive,
|
||||
const char* archive_fname,
|
||||
const char* destination);
|
||||
|
||||
/* Optional per-entry callback on unpacking - return false to skip entry */
|
||||
/** Optional per-entry callback on unpacking
|
||||
* @param name Name of the file or directory
|
||||
* @param is_directory True if the entry is a directory
|
||||
* @param[in] context User context
|
||||
* @return true to process the entry, false to skip
|
||||
*/
|
||||
typedef bool (*tar_unpack_file_cb)(const char* name, bool is_directory, void* context);
|
||||
|
||||
/** Set per-entry callback on unpacking
|
||||
* @param archive Tar archive object
|
||||
* @param callback Callback function
|
||||
* @param[in] context User context
|
||||
*/
|
||||
void tar_archive_set_file_callback(TarArchive* archive, tar_unpack_file_cb callback, void* context);
|
||||
|
||||
/* Low-level API */
|
||||
|
||||
/** Add tar archive directory header
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in write mode
|
||||
* @param[in] dirpath Path to the directory
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_dir_add_element(TarArchive* archive, const char* dirpath);
|
||||
|
||||
/** Add tar archive file header
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in write mode
|
||||
* @param[in] path Path to the file
|
||||
* @param data_len Size of the file
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_file_add_header(TarArchive* archive, const char* path, const int32_t data_len);
|
||||
|
||||
/** Add tar archive file data block
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in write mode
|
||||
* @param[in] data_block Data block
|
||||
* @param block_len Size of the data block
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_file_add_data_block(
|
||||
TarArchive* archive,
|
||||
const uint8_t* data_block,
|
||||
const int32_t block_len);
|
||||
|
||||
/** Finalize tar archive file
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in write mode
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_file_finalize(TarArchive* archive);
|
||||
|
||||
/** Store data in tar archive
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in write mode
|
||||
* @param[in] path Path to the file
|
||||
* @param[in] data Data to store
|
||||
* @param data_len Size of the data
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_store_data(
|
||||
TarArchive* archive,
|
||||
const char* path,
|
||||
const uint8_t* data,
|
||||
const int32_t data_len);
|
||||
|
||||
/** Finalize tar archive
|
||||
*
|
||||
* @param archive Tar archive object. Must be opened in write mode
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool tar_archive_finalize(TarArchive* archive);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -161,3 +161,9 @@ ResourceManifestEntry*
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool resource_manifest_rewind(ResourceManifestReader* resource_manifest) {
|
||||
furi_assert(resource_manifest);
|
||||
|
||||
return stream_seek(resource_manifest->stream, 0, StreamOffsetFromStart);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,13 @@ void resource_manifest_reader_free(ResourceManifestReader* resource_manifest);
|
||||
*/
|
||||
bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, const char* filename);
|
||||
|
||||
/**
|
||||
* @brief Rewind manifest to the beginning
|
||||
* @param resource_manifest allocated object
|
||||
* @return true if successful
|
||||
*/
|
||||
bool resource_manifest_rewind(ResourceManifestReader* resource_manifest);
|
||||
|
||||
/**
|
||||
* @brief Read next file/dir entry from manifest
|
||||
* @param resource_manifest allocated object
|
||||
|
||||
Reference in New Issue
Block a user