1
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:
MX
2024-07-01 22:18:36 +03:00
39 changed files with 2029 additions and 287 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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, '/');

View File

@@ -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(

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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