366 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			366 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#include "application_assets.h"
 | 
						|
#include <toolbox/path.h>
 | 
						|
#include <storage/storage_i.h>
 | 
						|
 | 
						|
// #define ELF_ASSETS_DEBUG_LOG 1
 | 
						|
 | 
						|
#ifndef ELF_ASSETS_DEBUG_LOG
 | 
						|
#undef FURI_LOG_D
 | 
						|
#define FURI_LOG_D(...)
 | 
						|
#undef FURI_LOG_E
 | 
						|
#define FURI_LOG_E(...)
 | 
						|
#endif
 | 
						|
 | 
						|
#define FLIPPER_APPLICATION_ASSETS_MAGIC 0x4F4C5A44
 | 
						|
#define FLIPPER_APPLICATION_ASSETS_VERSION 1
 | 
						|
#define FLIPPER_APPLICATION_ASSETS_SIGNATURE_FILENAME ".assets.signature"
 | 
						|
 | 
						|
#define BUFFER_SIZE 512
 | 
						|
 | 
						|
#define TAG "fap_assets"
 | 
						|
 | 
						|
#pragma pack(push, 1)
 | 
						|
 | 
						|
typedef struct {
 | 
						|
    uint32_t magic;
 | 
						|
    uint32_t version;
 | 
						|
    uint32_t dirs_count;
 | 
						|
    uint32_t files_count;
 | 
						|
} FlipperApplicationAssetsHeader;
 | 
						|
 | 
						|
#pragma pack(pop)
 | 
						|
 | 
						|
typedef enum {
 | 
						|
    AssetsSignatureResultEqual,
 | 
						|
    AssetsSignatureResultNotEqual,
 | 
						|
    AssetsSignatureResultError,
 | 
						|
} AssetsSignatureResult;
 | 
						|
 | 
						|
static FuriString* flipper_application_assets_alloc_app_full_path(FuriString* app_name) {
 | 
						|
    furi_assert(app_name);
 | 
						|
    FuriString* full_path = furi_string_alloc_set(APPS_ASSETS_PATH "/");
 | 
						|
    furi_string_cat(full_path, app_name);
 | 
						|
    return full_path;
 | 
						|
}
 | 
						|
 | 
						|
static FuriString* flipper_application_assets_alloc_signature_file_path(FuriString* app_name) {
 | 
						|
    furi_assert(app_name);
 | 
						|
    FuriString* signature_file_path = flipper_application_assets_alloc_app_full_path(app_name);
 | 
						|
    furi_string_cat(signature_file_path, "/" FLIPPER_APPLICATION_ASSETS_SIGNATURE_FILENAME);
 | 
						|
 | 
						|
    return signature_file_path;
 | 
						|
}
 | 
						|
 | 
						|
static uint8_t* flipper_application_assets_alloc_and_load_data(File* file, size_t* size) {
 | 
						|
    furi_assert(file);
 | 
						|
 | 
						|
    uint8_t* data = NULL;
 | 
						|
    uint32_t length = 0;
 | 
						|
 | 
						|
    // read data length
 | 
						|
    if(storage_file_read(file, &length, sizeof(length)) != sizeof(length)) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    data = malloc(length);
 | 
						|
 | 
						|
    // read data
 | 
						|
    if(storage_file_read(file, (void*)data, length) != length) {
 | 
						|
        free((void*)data);
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    if(size != NULL) {
 | 
						|
        *size = length;
 | 
						|
    }
 | 
						|
 | 
						|
    return data;
 | 
						|
}
 | 
						|
 | 
						|
static bool flipper_application_assets_process_files(
 | 
						|
    Storage* storage,
 | 
						|
    File* file,
 | 
						|
    FuriString* app_name,
 | 
						|
    uint32_t files_count) {
 | 
						|
    furi_assert(storage);
 | 
						|
    furi_assert(file);
 | 
						|
    furi_assert(app_name);
 | 
						|
 | 
						|
    UNUSED(storage);
 | 
						|
 | 
						|
    bool success = false;
 | 
						|
    uint32_t length = 0;
 | 
						|
    char* path = NULL;
 | 
						|
    FuriString* file_path = furi_string_alloc();
 | 
						|
    File* destination = storage_file_alloc(storage);
 | 
						|
 | 
						|
    FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name);
 | 
						|
 | 
						|
    for(uint32_t i = 0; i < files_count; i++) {
 | 
						|
        path = (char*)flipper_application_assets_alloc_and_load_data(file, NULL);
 | 
						|
 | 
						|
        if(path == NULL) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        // read file size
 | 
						|
        if(storage_file_read(file, &length, sizeof(length)) != sizeof(length)) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        furi_string_set(file_path, full_path);
 | 
						|
        furi_string_cat(file_path, "/");
 | 
						|
        furi_string_cat(file_path, path);
 | 
						|
 | 
						|
        if(!storage_file_open(
 | 
						|
               destination, furi_string_get_cstr(file_path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
 | 
						|
            FURI_LOG_E(TAG, "Can't create file: %s", furi_string_get_cstr(file_path));
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        // copy data to file
 | 
						|
        if(!storage_file_copy_to_file(file, destination, length)) {
 | 
						|
            FURI_LOG_E(TAG, "Can't copy data to file: %s", furi_string_get_cstr(file_path));
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        storage_file_close(destination);
 | 
						|
 | 
						|
        free(path);
 | 
						|
        path = NULL;
 | 
						|
 | 
						|
        if(i == files_count - 1) {
 | 
						|
            success = true;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if(path != NULL) {
 | 
						|
        free(path);
 | 
						|
    }
 | 
						|
 | 
						|
    storage_file_free(destination);
 | 
						|
    furi_string_free(file_path);
 | 
						|
 | 
						|
    return success;
 | 
						|
}
 | 
						|
 | 
						|
static bool flipper_application_assets_process_dirs(
 | 
						|
    Storage* storage,
 | 
						|
    File* file,
 | 
						|
    FuriString* app_name,
 | 
						|
    uint32_t dirs_count) {
 | 
						|
    furi_assert(storage);
 | 
						|
    furi_assert(file);
 | 
						|
    furi_assert(app_name);
 | 
						|
 | 
						|
    bool success = false;
 | 
						|
    FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name);
 | 
						|
 | 
						|
    do {
 | 
						|
        FuriString* dir_path = furi_string_alloc();
 | 
						|
        char* path = NULL;
 | 
						|
 | 
						|
        for(uint32_t i = 0; i < dirs_count; i++) {
 | 
						|
            path = (char*)flipper_application_assets_alloc_and_load_data(file, NULL);
 | 
						|
 | 
						|
            if(path == NULL) {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            furi_string_set(dir_path, full_path);
 | 
						|
            furi_string_cat(dir_path, "/");
 | 
						|
            furi_string_cat(dir_path, path);
 | 
						|
 | 
						|
            if(!storage_simply_mkdir(storage, furi_string_get_cstr(dir_path))) {
 | 
						|
                FURI_LOG_E(TAG, "Can't create directory: %s", furi_string_get_cstr(dir_path));
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            free(path);
 | 
						|
            path = NULL;
 | 
						|
 | 
						|
            if(i == dirs_count - 1) {
 | 
						|
                success = true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if(path != NULL) {
 | 
						|
            free(path);
 | 
						|
        }
 | 
						|
 | 
						|
        furi_string_free(dir_path);
 | 
						|
    } while(false);
 | 
						|
 | 
						|
    furi_string_free(full_path);
 | 
						|
 | 
						|
    return success;
 | 
						|
}
 | 
						|
 | 
						|
static AssetsSignatureResult flipper_application_assets_process_signature(
 | 
						|
    Storage* storage,
 | 
						|
    File* file,
 | 
						|
    FuriString* app_name,
 | 
						|
    uint8_t** signature_data,
 | 
						|
    size_t* signature_data_size) {
 | 
						|
    furi_assert(storage);
 | 
						|
    furi_assert(file);
 | 
						|
    furi_assert(app_name);
 | 
						|
    furi_assert(signature_data);
 | 
						|
    furi_assert(signature_data_size);
 | 
						|
 | 
						|
    AssetsSignatureResult result = AssetsSignatureResultError;
 | 
						|
    File* signature_file = storage_file_alloc(storage);
 | 
						|
    FuriString* signature_file_path =
 | 
						|
        flipper_application_assets_alloc_signature_file_path(app_name);
 | 
						|
 | 
						|
    do {
 | 
						|
        // read signature
 | 
						|
        *signature_data =
 | 
						|
            flipper_application_assets_alloc_and_load_data(file, signature_data_size);
 | 
						|
 | 
						|
        if(*signature_data == NULL) { //-V547
 | 
						|
            FURI_LOG_E(TAG, "Can't read signature");
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        result = AssetsSignatureResultNotEqual;
 | 
						|
 | 
						|
        if(!storage_file_open(
 | 
						|
               signature_file,
 | 
						|
               furi_string_get_cstr(signature_file_path),
 | 
						|
               FSAM_READ_WRITE,
 | 
						|
               FSOM_OPEN_EXISTING)) {
 | 
						|
            FURI_LOG_E(TAG, "Can't open signature file");
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        size_t signature_size = storage_file_size(signature_file);
 | 
						|
        uint8_t* signature_file_data = malloc(signature_size);
 | 
						|
        if(storage_file_read(signature_file, signature_file_data, signature_size) !=
 | 
						|
           signature_size) {
 | 
						|
            FURI_LOG_E(TAG, "Can't read signature file");
 | 
						|
            free(signature_file_data);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        if(memcmp(*signature_data, signature_file_data, signature_size) == 0) {
 | 
						|
            FURI_LOG_D(TAG, "Assets signature is equal");
 | 
						|
            result = AssetsSignatureResultEqual;
 | 
						|
        }
 | 
						|
 | 
						|
        free(signature_file_data);
 | 
						|
    } while(0);
 | 
						|
 | 
						|
    storage_file_free(signature_file);
 | 
						|
    furi_string_free(signature_file_path);
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
bool flipper_application_assets_load(File* file, const char* elf_path, size_t offset, size_t size) {
 | 
						|
    UNUSED(size);
 | 
						|
    furi_assert(file);
 | 
						|
    furi_assert(elf_path);
 | 
						|
    FlipperApplicationAssetsHeader header;
 | 
						|
    bool result = false;
 | 
						|
    Storage* storage = furi_record_open(RECORD_STORAGE);
 | 
						|
    uint8_t* signature_data = NULL;
 | 
						|
    size_t signature_data_size = 0;
 | 
						|
    FuriString* app_name = furi_string_alloc();
 | 
						|
    path_extract_filename_no_ext(elf_path, app_name);
 | 
						|
 | 
						|
    FURI_LOG_D(TAG, "Loading assets for %s", furi_string_get_cstr(app_name));
 | 
						|
 | 
						|
    FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name);
 | 
						|
 | 
						|
    do {
 | 
						|
        if(!storage_file_seek(file, offset, true)) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        // read header
 | 
						|
        if(storage_file_read(file, &header, sizeof(header)) != sizeof(header)) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        if(header.magic != FLIPPER_APPLICATION_ASSETS_MAGIC) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        if(header.version != FLIPPER_APPLICATION_ASSETS_VERSION) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        // process signature
 | 
						|
        AssetsSignatureResult signature_result = flipper_application_assets_process_signature(
 | 
						|
            storage, file, app_name, &signature_data, &signature_data_size);
 | 
						|
 | 
						|
        if(signature_result == AssetsSignatureResultError) {
 | 
						|
            FURI_LOG_E(TAG, "Assets signature error");
 | 
						|
            break;
 | 
						|
        } else if(signature_result == AssetsSignatureResultEqual) {
 | 
						|
            FURI_LOG_D(TAG, "Assets signature equal, skip loading");
 | 
						|
            result = true;
 | 
						|
            break;
 | 
						|
        } else {
 | 
						|
            FURI_LOG_D(TAG, "Assets signature not equal, loading");
 | 
						|
 | 
						|
            // remove old assets
 | 
						|
            FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name);
 | 
						|
            storage_simply_remove_recursive(storage, furi_string_get_cstr(full_path));
 | 
						|
            furi_string_free(full_path);
 | 
						|
 | 
						|
            FURI_LOG_D(TAG, "Assets removed");
 | 
						|
        }
 | 
						|
 | 
						|
        if(!storage_simply_mkdir(storage, APPS_ASSETS_PATH)) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        if(!storage_simply_mkdir(storage, furi_string_get_cstr(full_path))) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        // process directories
 | 
						|
        if(header.dirs_count &&
 | 
						|
           !flipper_application_assets_process_dirs(storage, file, app_name, header.dirs_count)) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        // process files
 | 
						|
        if(header.files_count && !flipper_application_assets_process_files(
 | 
						|
                                     storage, file, app_name, header.files_count)) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        // write signature
 | 
						|
        FuriString* signature_file_path =
 | 
						|
            flipper_application_assets_alloc_signature_file_path(app_name);
 | 
						|
        File* signature_file = storage_file_alloc(storage);
 | 
						|
 | 
						|
        if(storage_file_open(
 | 
						|
               signature_file,
 | 
						|
               furi_string_get_cstr(signature_file_path),
 | 
						|
               FSAM_WRITE,
 | 
						|
               FSOM_CREATE_ALWAYS)) {
 | 
						|
            storage_file_write(signature_file, signature_data, signature_data_size);
 | 
						|
        }
 | 
						|
 | 
						|
        storage_file_free(signature_file);
 | 
						|
        furi_string_free(signature_file_path);
 | 
						|
 | 
						|
        result = true;
 | 
						|
    } while(false);
 | 
						|
 | 
						|
    if(signature_data != NULL) {
 | 
						|
        free(signature_data);
 | 
						|
    }
 | 
						|
 | 
						|
    furi_record_close(RECORD_STORAGE);
 | 
						|
    furi_string_free(full_path);
 | 
						|
    furi_string_free(app_name);
 | 
						|
 | 
						|
    FURI_LOG_D(TAG, "Assets loading %s", result ? "success" : "failed");
 | 
						|
 | 
						|
    return result;
 | 
						|
} |