/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2011 Amaury Pouly
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include "misc.h"
#include "crypto.h"
#include "sb.h"

#define ALIGN_DOWN(n, a)    (((n)/(a))*(a))
#define ALIGN_UP(n, a)      ALIGN_DOWN((n)+((a)-1),a)

static void fill_gaps(struct sb_file_t *sb)
{
    for(int i = 0; i < sb->nr_sections; i++)
    {
        struct sb_section_t *sec = &sb->sections[i];
        for(int j = 0; j < sec->nr_insts; j++)
        {
            struct sb_inst_t *inst = &sec->insts[j];
            if(inst->inst != SB_INST_LOAD)
                continue;
            inst->padding_size = ROUND_UP(inst->size, BLOCK_SIZE) - inst->size;
            /* emulate elftosb2 behaviour: generate 15 bytes (that's a safe maximum) */
            inst->padding = xmalloc(15);
            generate_random_data(inst->padding, 15);
        }
    }
}

static void compute_sb_offsets(struct sb_file_t *sb, void *u, generic_printf_t cprintf)
{
    #define printf(c, ...) cprintf(u, false, c, __VA_ARGS__)
    sb->image_size = 0;
    /* sb header */
    sb->image_size += sizeof(struct sb_header_t) / BLOCK_SIZE;
    /* sections headers */
    sb->image_size += sb->nr_sections * sizeof(struct sb_section_header_t) / BLOCK_SIZE;
    /* key dictionary */
    sb->image_size += g_nr_keys * sizeof(struct sb_key_dictionary_entry_t) / BLOCK_SIZE;
    /* sections */
    for(int i = 0; i < sb->nr_sections; i++)
    {
        struct sb_section_t *sec = &sb->sections[i];
        /* we need to make sure section starts on the right alignment,
         * and since each section starts with a boot tag, we need to ensure
         * that the boot tag is at address X such that X+BLOCK_SIZE is a
         * multiple of the alignment */
        uint32_t alignment = sb->sections[i].alignment / BLOCK_SIZE;
        sb->image_size = ALIGN_UP(sb->image_size + 1, alignment) - 1;
        /* update padding of previous section */
        if(i > 0)
            sb->sections[i - 1].pad_size = sb->image_size -
                sb->sections[i - 1].file_offset - sb->sections[i - 1].sec_size;
        /* each section has a preliminary TAG command */
        sb->image_size += 1;
        sec->file_offset = sb->image_size;
        /* compute section size */
        sec->sec_size = 0;
        sec->pad_size = 0;

        char name[5];
        sb_fill_section_name(name, sec->identifier);
        printf(BLUE, "%s", sec->is_data ? "Data" : "Boot");
        printf(GREEN, " Section ");
        printf(YELLOW, "'%s'", name);
        if(sec->is_cleartext)
            printf(RED, " (cleartext)");
        printf(OFF, "\n");

        for(int j = 0; j < sec->nr_insts; j++)
        {
            struct sb_inst_t *inst = &sec->insts[j];
            if(inst->inst == SB_INST_CALL || inst->inst == SB_INST_JUMP)
            {
                printf(RED, "  %s", inst->inst == SB_INST_CALL ? "CALL" : "JUMP");
                printf(OFF, " | "); printf(BLUE, "addr=0x%08x", inst->addr);
                printf(OFF, " | "); printf(GREEN, "arg=0x%08x\n", inst->argument);
                sec->sec_size += sizeof(struct sb_instruction_call_t) / BLOCK_SIZE;
            }
            else if(inst->inst == SB_INST_FILL)
            {
                printf(RED, "  FILL");
                printf(OFF, " | "); printf(BLUE, "addr=0x%08x", inst->addr);
                printf(OFF, " | "); printf(GREEN, "len=0x%08x", inst->size);
                printf(OFF, " | "); printf(YELLOW, "pattern=0x%08x\n", inst->pattern);
                sec->sec_size += sizeof(struct sb_instruction_fill_t) / BLOCK_SIZE;
            }
            else if(inst->inst == SB_INST_LOAD)
            {
                printf(RED, "  LOAD");
                printf(OFF, " | "); printf(BLUE, "addr=0x%08x", inst->addr);
                printf(OFF, " | "); printf(GREEN, "len=0x%08x\n", inst->size);
                /* load header */
                sec->sec_size += sizeof(struct sb_instruction_load_t) / BLOCK_SIZE;
                /* data + alignment */
                sec->sec_size += (inst->size + inst->padding_size) / BLOCK_SIZE;
            }
            else if(inst->inst == SB_INST_MODE)
            {
                printf(RED, "  MODE");
                printf(OFF, " | "); printf(BLUE, "mod=0x%08x\n", inst->addr);
                sec->sec_size += sizeof(struct sb_instruction_mode_t) / BLOCK_SIZE;
            }
            else if(inst->inst == SB_INST_DATA)
            {
                printf(RED, "  DATA");
                printf(OFF, " | "); printf(BLUE, "size=0x%08x\n", inst->size);
                sec->sec_size += ROUND_UP(inst->size, BLOCK_SIZE) / BLOCK_SIZE;
            }
            else if(inst->inst == SB_INST_NOP)
            {
                printf(RED, "  NOOP\n");
                sec->sec_size += sizeof(struct sb_instruction_nop_t) / BLOCK_SIZE;
            }
            else
            {
                cprintf(u, true, GREY, "die on inst %d\n", inst->inst);
            }
        }
        sb->image_size += sec->sec_size;
    }
    /* final signature */
    sb->image_size += 2;
    #undef printf
}

uint64_t sb_generate_timestamp(void)
{
    struct tm tm_base;
    memset(&tm_base, 0, sizeof(tm_base));
    /* 2000/1/1 0:00:00 */
    tm_base.tm_mday = 1;
    tm_base.tm_year = 100;
    time_t t = time(NULL) - mktime(&tm_base);
    return (uint64_t)t * 1000000L;
}

static uint16_t swap16(uint16_t t)
{
    return (t << 8) | (t >> 8);
}

static void fix_version(struct sb_version_t *ver)
{
    ver->major = swap16(ver->major);
    ver->minor = swap16(ver->minor);
    ver->revision = swap16(ver->revision);
}

static void produce_sb_header(struct sb_file_t *sb, struct sb_header_t *sb_hdr)
{
    struct sha_1_params_t sha_1_params;

    sb_hdr->signature[0] = 'S';
    sb_hdr->signature[1] = 'T';
    sb_hdr->signature[2] = 'M';
    sb_hdr->signature[3] = 'P';
    sb_hdr->major_ver = IMAGE_MAJOR_VERSION;
    sb_hdr->minor_ver = sb->minor_version;
    sb_hdr->flags = sb->flags;
    sb_hdr->image_size = sb->image_size;
    sb_hdr->header_size = sizeof(struct sb_header_t) / BLOCK_SIZE;
    sb_hdr->first_boot_sec_id = sb->sections[sb->first_boot_sec].identifier;
    sb_hdr->nr_keys = g_nr_keys;
    sb_hdr->nr_sections = sb->nr_sections;
    sb_hdr->sec_hdr_size = sizeof(struct sb_section_header_t) / BLOCK_SIZE;
    sb_hdr->key_dict_off = sb_hdr->header_size +
        sb_hdr->sec_hdr_size * sb_hdr->nr_sections;
    sb_hdr->first_boot_tag_off = sb->sections[sb->first_boot_sec].file_offset - 1;
    generate_random_data(sb_hdr->rand_pad0, sizeof(sb_hdr->rand_pad0));
    generate_random_data(sb_hdr->rand_pad1, sizeof(sb_hdr->rand_pad1));
    /* Version 1.0 has 6 bytes of random padding,
     * Version 1.1 requires the last 4 bytes to be 'sgtl' */
    if(sb->minor_version >= 1)
        memcpy(&sb_hdr->rand_pad0[2], "sgtl", 4);

    sb_hdr->timestamp = sb->timestamp;
    sb_hdr->product_ver = sb->product_ver;
    fix_version(&sb_hdr->product_ver);
    sb_hdr->component_ver = sb->component_ver;
    fix_version(&sb_hdr->component_ver);
    sb_hdr->drive_tag = sb->drive_tag;

    sha_1_init(&sha_1_params);
    sha_1_update(&sha_1_params, &sb_hdr->signature[0],
        sizeof(struct sb_header_t) - sizeof(sb_hdr->sha1_header));
    sha_1_finish(&sha_1_params);
    sha_1_output(&sha_1_params, sb_hdr->sha1_header);
}

static void produce_sb_section_header(struct sb_section_t *sec,
    struct sb_section_header_t *sec_hdr)
{
    sec_hdr->identifier = sec->identifier;
    sec_hdr->offset = sec->file_offset;
    sec_hdr->size = sec->sec_size;
    sec_hdr->flags = (sec->is_data ? 0 : SECTION_BOOTABLE)
        | (sec->is_cleartext ? SECTION_CLEARTEXT : 0)
        | sec->other_flags;
}

static uint8_t instruction_checksum(struct sb_instruction_header_t *hdr)
{
    uint8_t sum = 90;
    uint8_t *ptr = (uint8_t *)hdr;
    for(int i = 1; i < 16; i++)
        sum += ptr[i];
    return sum;
}

static void produce_section_tag_cmd(struct sb_section_t *sec,
    struct sb_instruction_tag_t *tag, bool is_last)
{
    tag->hdr.opcode = SB_INST_TAG;
    tag->hdr.flags = is_last ? SB_INST_LAST_TAG : 0;
    tag->identifier = sec->identifier;
    /* there is a catch here: in the section header at the beginning of the SB
     * file, we put the *useful* length of the section (without padding) but
     * the bootloader will not use those and only use the TAG commande which
     * need to give the *actual* length (with padding) */
    tag->len = sec->sec_size + sec->pad_size;
    tag->flags = (sec->is_data ? 0 : SECTION_BOOTABLE)
        | (sec->is_cleartext ? SECTION_CLEARTEXT : 0)
        | sec->other_flags;
    tag->hdr.checksum = instruction_checksum(&tag->hdr);
}

void produce_sb_instruction(struct sb_inst_t *inst,
    struct sb_instruction_common_t *cmd, void *u, generic_printf_t cprintf)
{
    memset(cmd, 0, sizeof(struct sb_instruction_common_t));
    cmd->hdr.opcode = inst->inst;
    switch(inst->inst)
    {
        case SB_INST_CALL:
        case SB_INST_JUMP:
            cmd->addr = inst->addr;
            cmd->data = inst->argument;
            break;
        case SB_INST_FILL:
            cmd->addr = inst->addr;
            cmd->len = inst->size;
            cmd->data = inst->pattern;
            break;
        case SB_INST_LOAD:
            cmd->addr = inst->addr;
            cmd->len = inst->size;
            cmd->data = crc_continue(crc(inst->data, inst->size),
                inst->padding, inst->padding_size);
            break;
        case SB_INST_MODE:
            cmd->data = inst->addr;
            break;
        case SB_INST_NOP:
            break;
        default:
            if(g_debug)
                cprintf(u, true, GREY, "die on invalid inst %d\n", inst->inst);
    }
    cmd->hdr.checksum = instruction_checksum(&cmd->hdr);
}

enum sb_error_t sb_write_file(struct sb_file_t *sb, const char *filename, void *u,
    generic_printf_t cprintf)
{
    #define printf(c, ...) cprintf(u, false, c, __VA_ARGS__)
    struct crypto_key_t real_key;
    real_key.method = CRYPTO_KEY;
    uint8_t crypto_iv[16];
    uint8_t (*cbc_macs)[16] = xmalloc(16 * g_nr_keys);
    /* init CBC-MACs */
    for(int i = 0; i < g_nr_keys; i++)
        memset(cbc_macs[i], 0, 16);
    /* fill gaps */
    fill_gaps(sb);
    /* find first bootable section */
    sb->first_boot_sec = -1;
    for(int i = 0; i < sb->nr_sections; i++)
        if(!sb->sections[i].is_data)
        {
            sb->first_boot_sec = i;
            break;
        }
    if(sb->first_boot_sec == -1)
    {
        cprintf(u, true, GREY, "Image contains no bootable section, I cannot handle that.\n");
        return SB_ERROR;
    }
    /* compute section offsets */
    compute_sb_offsets(sb, u, cprintf);
    /* generate random real key */
    generate_random_data(real_key.u.key, 16);

    /* global SHA-1 */
    struct sha_1_params_t file_sha1;
    sha_1_init(&file_sha1);
    /* produce and write header */
    struct sb_header_t sb_hdr;
    produce_sb_header(sb, &sb_hdr);
    /* allocate image */
    uint8_t *buf = xmalloc(sb_hdr.image_size * BLOCK_SIZE);
    uint8_t *buf_p = buf;
    #define write(p, sz) do { memcpy(buf_p, p, sz); buf_p += sz; } while(0)
    #define check_crypto(expr) \
        do { int err = expr; \
            if(err != CRYPTO_ERROR_SUCCESS) { \
                free(cbc_macs); \
                cprintf(u, true, GREY, "Crypto error: %d\n", err); \
                return SB_CRYPTO_ERROR; } } while(0)

    sha_1_update(&file_sha1, (uint8_t *)&sb_hdr, sizeof(sb_hdr));
    write(&sb_hdr, sizeof(sb_hdr));

    memcpy(crypto_iv, &sb_hdr, 16);

    /* update CBC-MACs */
    for(int i = 0; i < g_nr_keys; i++)
    {
        check_crypto(crypto_setup(&g_key_array[i]));
        check_crypto(crypto_apply((uint8_t *)&sb_hdr, NULL, sizeof(sb_hdr) / BLOCK_SIZE,
            cbc_macs[i], &cbc_macs[i], true));
    }

    /* produce and write section headers */
    for(int i = 0; i < sb_hdr.nr_sections; i++)
    {
        struct sb_section_header_t sb_sec_hdr;
        produce_sb_section_header(&sb->sections[i], &sb_sec_hdr);
        sha_1_update(&file_sha1, (uint8_t *)&sb_sec_hdr, sizeof(sb_sec_hdr));
        write(&sb_sec_hdr, sizeof(sb_sec_hdr));
        /* update CBC-MACs */
        for(int j = 0; j < g_nr_keys; j++)
        {
            check_crypto(crypto_setup(&g_key_array[j]));
            check_crypto(crypto_apply((uint8_t *)&sb_sec_hdr, NULL,
                sizeof(sb_sec_hdr) / BLOCK_SIZE, cbc_macs[j], &cbc_macs[j], true));
        }
    }
    /* produce key dictionary */
    for(int i = 0; i < g_nr_keys; i++)
    {
        struct sb_key_dictionary_entry_t entry;
        memcpy(entry.hdr_cbc_mac, cbc_macs[i], 16);
        check_crypto(crypto_setup(&g_key_array[i]));
        check_crypto(crypto_apply(real_key.u.key, entry.key, 1, crypto_iv, NULL, true));
        write(&entry, sizeof(entry));
        sha_1_update(&file_sha1, (uint8_t *)&entry, sizeof(entry));
    }

    /* HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK */
    /* Image crafting, don't use it unless you understand what you do */
    if(sb->override_real_key)
        memcpy(real_key.u.key, sb->real_key, 16);
    if(sb->override_crypto_iv)
        memcpy(crypto_iv, sb->crypto_iv, 16);
    /* KCAH KCAH KCAH KCAH KCAH KCAH KCAH KCAH KCAH KCAH KCAH KCAH KCAH KCAH */
    if(g_debug)
    {
        printf(GREEN, "Real key: ");
        for(int j = 0; j < 16; j++)
            printf(YELLOW, "%02x", real_key.u.key[j]);
        printf(OFF, "\n");
        printf(GREEN, "IV      : ");
        for(int j = 0; j < 16; j++)
            printf(YELLOW, "%02x", crypto_iv[j]);
        printf(OFF, "\n");
    }
    /* the first section might not start right after the header, pad with
     * random data */
    unsigned init_gap = (sb->sections[0].file_offset - 1) * BLOCK_SIZE - (buf_p - buf);
    if(init_gap > 0)
    {
        uint8_t *data = xmalloc(init_gap);
        generate_random_data(data, init_gap);
        sha_1_update(&file_sha1, data, init_gap);
        write(data, init_gap);
        free(data);
    }
    /* setup real key */
    check_crypto(crypto_setup(&real_key));
    /* produce sections data */
    for(int i = 0; i< sb_hdr.nr_sections; i++)
    {
        /* produce tag command */
        struct sb_instruction_tag_t tag_cmd;
        produce_section_tag_cmd(&sb->sections[i], &tag_cmd, (i + 1) == sb_hdr.nr_sections);
        if(g_nr_keys > 0)
        {
            check_crypto(crypto_apply((uint8_t *)&tag_cmd, (uint8_t *)&tag_cmd,
                sizeof(tag_cmd) / BLOCK_SIZE, crypto_iv, NULL, true));
        }
        sha_1_update(&file_sha1, (uint8_t *)&tag_cmd, sizeof(tag_cmd));
        write(&tag_cmd, sizeof(tag_cmd));
        /* produce other commands */
        uint8_t cur_cbc_mac[16];
        memcpy(cur_cbc_mac, crypto_iv, 16);
        for(int j = 0; j < sb->sections[i].nr_insts; j++)
        {
            struct sb_inst_t *inst = &sb->sections[i].insts[j];
            /* command */
            if(inst->inst != SB_INST_DATA)
            {
                struct sb_instruction_common_t cmd;
                produce_sb_instruction(inst, &cmd, u, cprintf);
                if(g_nr_keys > 0 && !sb->sections[i].is_cleartext)
                {
                    check_crypto(crypto_apply((uint8_t *)&cmd, (uint8_t *)&cmd,
                        sizeof(cmd) / BLOCK_SIZE, cur_cbc_mac, &cur_cbc_mac, true));
                }
                sha_1_update(&file_sha1, (uint8_t *)&cmd, sizeof(cmd));
                write(&cmd, sizeof(cmd));
            }
            /* data */
            if(inst->inst == SB_INST_LOAD || inst->inst == SB_INST_DATA)
            {
                uint32_t sz = inst->size + inst->padding_size;
                uint8_t *data = xmalloc(sz);
                memcpy(data, inst->data, inst->size);
                memcpy(data + inst->size, inst->padding, inst->padding_size);
                if(g_nr_keys > 0 && !sb->sections[i].is_cleartext)
                {
                    check_crypto(crypto_apply(data, data, sz / BLOCK_SIZE,
                        cur_cbc_mac, &cur_cbc_mac, true));
                }
                sha_1_update(&file_sha1, data, sz);
                write(data, sz);
                free(data);
            }
        }
        /* pad section with random data or NOP */
        uint32_t pad_size = sb->sections[i].pad_size;
        if(sb->sections[i].is_data)
        {
            uint8_t *data = xmalloc(pad_size * BLOCK_SIZE);
            generate_random_data(data, pad_size * BLOCK_SIZE);
            sha_1_update(&file_sha1, data, pad_size * BLOCK_SIZE);
            write(data, pad_size * BLOCK_SIZE);
            free(data);
        }
        else
        {
            for(unsigned j = 0; j < pad_size; j++)
            {
                struct sb_instruction_nop_t cmd;
                memset(&cmd, 0, sizeof(cmd));
                cmd.hdr.opcode = SB_INST_NOP;
                cmd.hdr.checksum = instruction_checksum(&cmd.hdr);
                if(g_nr_keys > 0 && !sb->sections[i].is_cleartext)
                {
                    check_crypto(crypto_apply((uint8_t *)&cmd, (uint8_t *)&cmd,
                        sizeof(cmd) / BLOCK_SIZE, cur_cbc_mac, &cur_cbc_mac, true));
                }
                sha_1_update(&file_sha1, (uint8_t *)&cmd, sizeof(cmd));
                write(&cmd, sizeof(cmd));
            }
        }
    }
    /* write file SHA-1 */
    uint8_t final_sig[32];
    sha_1_finish(&file_sha1);
    sha_1_output(&file_sha1, final_sig);
    generate_random_data(final_sig + 20, 12);
    if(g_nr_keys > 0)
        check_crypto(crypto_apply(final_sig, final_sig, 2, crypto_iv, NULL, true));
    write(final_sig, 32);

    free(cbc_macs);

    if(buf_p - buf != sb_hdr.image_size * BLOCK_SIZE)
    {
        printf(GREY, "Internal error: SB image buffer was not entirely filled !\n");
        printf(GREY, "Internal error: expected %u blocks, got %u\n",
            (buf_p - buf) / BLOCK_SIZE, sb_hdr.image_size);
        cprintf(u, true, GREY, "Internal error\n");
        free(buf);
        return SB_ERROR;
    }

    FILE *fd = fopen(filename, "wb");
    if(fd == NULL)
        return SB_OPEN_ERROR;
    int cnt = fwrite(buf, sb_hdr.image_size * BLOCK_SIZE, 1, fd);
    if(cnt != 1)
        printf(GREY, "Write error: %m\n");
    free(buf);
    fclose(fd);
    if(cnt != 1)
        return SB_WRITE_ERROR;

    return SB_SUCCESS;
    #undef check_crypto
    #undef printf
}

static struct sb_section_t *read_section(bool data_sec, uint32_t id, uint8_t *buf,
    int size, const char *indent, void *u, generic_printf_t cprintf, enum sb_error_t *err)
{
    #define printf(c, ...) cprintf(u, false, c, __VA_ARGS__)
    #define fatal(e, ...) \
        do { if(err) *err = e; \
            cprintf(u, true, GREY, __VA_ARGS__); \
            sb_free_section(*sec); \
            free(sec); \
            return NULL; } while(0)

    struct sb_section_t *sec = xmalloc(sizeof(struct sb_section_t));
    memset(sec, 0, sizeof(struct sb_section_t));
    sec->identifier = id;
    sec->is_data = data_sec;
    sec->sec_size = ROUND_UP(size, BLOCK_SIZE) / BLOCK_SIZE;

    if(data_sec)
    {
        sec->nr_insts = 1;
        sec->insts = xmalloc(sizeof(struct sb_inst_t));
        memset(sec->insts, 0, sizeof(struct sb_inst_t));
        sec->insts->inst = SB_INST_DATA;
        sec->insts->size = size;
        sec->insts->data = memdup(buf, size);
        return sec;
    }

    /* Pretty print the content */
    int pos = 0;
    while(pos < size)
    {
        struct sb_inst_t inst;
        memset(&inst, 0, sizeof(inst));

        struct sb_instruction_header_t *hdr = (struct sb_instruction_header_t *)&buf[pos];
        inst.inst = hdr->opcode;

        printf(OFF, "%s", indent);
        uint8_t checksum = instruction_checksum(hdr);
        if(checksum != hdr->checksum)
            fatal(SB_CHECKSUM_ERROR, "Bad instruction checksum\n");
        if(hdr->flags != 0)
        {
            printf(GREY, "[");
            printf(BLUE, "f=%x", hdr->flags);
            printf(GREY, "] ");
        }
        if(hdr->opcode == SB_INST_LOAD)
        {
            struct sb_instruction_load_t *load = (struct sb_instruction_load_t *)&buf[pos];
            inst.size = load->len;
            inst.addr = load->addr;
            inst.data = memdup(load + 1, load->len);

            printf(RED, "LOAD");
            printf(OFF, " | ");
            printf(BLUE, "addr=0x%08x", load->addr);
            printf(OFF, " | ");
            printf(GREEN, "len=0x%08x", load->len);
            printf(OFF, " | ");
            printf(YELLOW, "crc=0x%08x", load->crc);
            /* data is padded to 16-byte boundary with random data and crc'ed with it */
            uint32_t computed_crc = crc(&buf[pos + sizeof(struct sb_instruction_load_t)],
                ROUND_UP(load->len, 16));
            if(load->crc == computed_crc)
                printf(RED, "  Ok\n");
            else
            {
                printf(RED, "  Failed (crc=0x%08x)\n", computed_crc);
                fatal(SB_CHECKSUM_ERROR, "Instruction data crc error\n");
            }

            pos += load->len + sizeof(struct sb_instruction_load_t);
        }
        else if(hdr->opcode == SB_INST_FILL)
        {
            struct sb_instruction_fill_t *fill = (struct sb_instruction_fill_t *)&buf[pos];
            inst.pattern = fill->pattern;
            inst.size = fill->len;
            inst.addr = fill->addr;

            printf(RED, "FILL");
            printf(OFF, " | ");
            printf(BLUE, "addr=0x%08x", fill->addr);
            printf(OFF, " | ");
            printf(GREEN, "len=0x%08x", fill->len);
            printf(OFF, " | ");
            printf(YELLOW, "pattern=0x%08x\n", fill->pattern);

            pos += sizeof(struct sb_instruction_fill_t);
        }
        else if(hdr->opcode == SB_INST_CALL ||
                hdr->opcode == SB_INST_JUMP)
        {
            int is_call = (hdr->opcode == SB_INST_CALL);
            struct sb_instruction_call_t *call = (struct sb_instruction_call_t *)&buf[pos];
            inst.addr = call->addr;
            inst.argument = call->arg;

            if(is_call)
                printf(RED, "CALL");
            else
                printf(RED, "JUMP");
            printf(OFF, " | ");
            printf(BLUE, "addr=0x%08x", call->addr);
            printf(OFF, " | ");
            printf(GREEN, "arg=0x%08x\n", call->arg);

            pos += sizeof(struct sb_instruction_call_t);
        }
        else if(hdr->opcode == SB_INST_MODE)
        {
            struct sb_instruction_mode_t *mode = (struct sb_instruction_mode_t *)hdr;
            inst.argument = mode->mode;

            printf(RED, "MODE");
            printf(OFF, " | ");
            printf(BLUE, "mod=0x%08x\n", mode->mode);

            pos += sizeof(struct sb_instruction_mode_t);
        }
        else if(hdr->opcode == SB_INST_NOP)
        {
            printf(RED, "NOOP\n");
            pos += sizeof(struct sb_instruction_mode_t);
        }
        else
        {
            fatal(SB_FORMAT_ERROR, "Unknown instruction %d at address 0x%08lx\n", hdr->opcode, (unsigned long)pos);
            break;
        }

        sec->insts = augment_array(sec->insts, sizeof(struct sb_inst_t), sec->nr_insts++, &inst, 1);
        pos = ROUND_UP(pos, BLOCK_SIZE);
    }

    return sec;
    #undef printf
    #undef fatal
}

void sb_fill_section_name(char name[5], uint32_t identifier)
{
    name[0] = (identifier >> 24) & 0xff;
    name[1] = (identifier >> 16) & 0xff;
    name[2] = (identifier >> 8) & 0xff;
    name[3] = identifier & 0xff;
    for(int i = 0; i < 4; i++)
        if(!isprint(name[i]))
            name[i] = '_';
    name[4] = 0;
}

static uint32_t guess_alignment(uint32_t off)
{
    /* find greatest power of two which divides the offset */
    if(off == 0)
        return 1;
    uint32_t a = 1;
    while(off % (2 * a) == 0)
        a *= 2;
    return a;
}

struct sb_file_t *sb_read_file(const char *filename, unsigned flags, void *u,
    generic_printf_t cprintf, enum sb_error_t *err)
{
    return sb_read_file_ex(filename, 0, -1, flags, u, cprintf, err);
}

struct sb_file_t *sb_read_file_ex(const char *filename, size_t offset, size_t size,
    unsigned flags, void *u, generic_printf_t cprintf, enum sb_error_t *err)
{
    #define fatal(e, ...) \
        do { if(err) *err = e; \
            cprintf(u, true, GREY, __VA_ARGS__); \
            free(buf); \
            return NULL; } while(0)

    FILE *f = fopen(filename, "rb");
    void *buf = NULL;
    if(f == NULL)
        fatal(SB_OPEN_ERROR, "Cannot open file for reading\n");
    fseek(f, 0, SEEK_END);
    size_t read_size = ftell(f);
    fseek(f, offset, SEEK_SET);
    if(size != (size_t)-1)
        read_size = size;
    buf = xmalloc(read_size);
    if(fread(buf, read_size, 1, f) != 1)
    {
        fclose(f);
        fatal(SB_READ_ERROR, "Cannot read file\n");
    }
    fclose(f);

    struct sb_file_t *ret = sb_read_memory(buf, read_size, flags, u, cprintf, err);
    free(buf);
    return ret;

    #undef fatal
}

struct printer_t
{
    void *user;
    generic_printf_t cprintf;
    const char *color;
    bool error;
};

static void sb_printer(void *user, const char *fmt, ...)
{
    struct printer_t *p = user;
    va_list args;
    va_start(args, fmt);
    char buffer[1024];
    vsnprintf(buffer, sizeof(buffer), fmt, args);
    p->cprintf(p->user, p->error, p->color, "%s", buffer);
    va_end(args);
}

struct sb_file_t *sb_read_memory(void *_buf, size_t filesize, unsigned flags, void *u,
    generic_printf_t cprintf, enum sb_error_t *out_err)
{
    struct sb_file_t *sb_file = NULL;
    uint8_t *buf = _buf;

    #define printf(c, ...) cprintf(u, false, c, __VA_ARGS__)
    #define fatal(e, ...) \
        do { if(out_err) *out_err = e; \
            cprintf(u, true, GREY, __VA_ARGS__); \
            free(cbcmacs); \
            sb_free(sb_file); \
            return NULL; } while(0)
    struct printer_t printer = {.user = u, .cprintf = cprintf, .color = OFF, .error = false };
    #define print_hex(c, p, len, nl) \
        do { printer.color = c; print_hex(&printer, sb_printer, p, len, nl); } while(0)
    #define check_crypto(expr) \
        do { int err = expr; \
            if(err != CRYPTO_ERROR_SUCCESS) \
                fatal(SB_CRYPTO_ERROR, "Crypto error: %d\n", err); } while(0)

    struct sha_1_params_t sha_1_params;
    uint8_t (*cbcmacs)[16] = xmalloc(16 * g_nr_keys);
    sb_file = xmalloc(sizeof(struct sb_file_t));
    memset(sb_file, 0, sizeof(struct sb_file_t));
    struct sb_header_t *sb_header = (struct sb_header_t *)buf;

    sb_file->image_size = sb_header->image_size;
    sb_file->minor_version = sb_header->minor_ver;
    sb_file->flags = sb_header->flags;
    sb_file->drive_tag = sb_header->drive_tag;
    sb_file->first_boot_sec_id = sb_header->first_boot_sec_id;

    if(memcmp(sb_header->signature, "STMP", 4) != 0)
        fatal(SB_FORMAT_ERROR, "Bad signature\n");
    if(sb_header->image_size * BLOCK_SIZE > filesize)
        fatal(SB_FORMAT_ERROR, "File too small (should be at least %d bytes)\n",
            sb_header->image_size * BLOCK_SIZE);
    if(sb_header->header_size * BLOCK_SIZE != sizeof(struct sb_header_t))
        fatal(SB_FORMAT_ERROR, "Bad header size\n");
    if(sb_header->sec_hdr_size * BLOCK_SIZE != sizeof(struct sb_section_header_t))
        fatal(SB_FORMAT_ERROR, "Bad section header size\n");

    if(filesize > sb_header->image_size * BLOCK_SIZE)
    {
        printf(GREY, "[Restrict file size from %lu to %d bytes]\n", filesize,
            sb_header->image_size * BLOCK_SIZE);
        filesize = sb_header->image_size * BLOCK_SIZE;
    }

    printf(BLUE, "Basic info:\n");
    printf(GREEN, "  SB version: ");
    printf(YELLOW, "%d.%d\n", sb_header->major_ver, sb_header->minor_ver);
    printf(GREEN, "  Header SHA-1: ");
    uint8_t *hdr_sha1 = sb_header->sha1_header;
    print_hex(YELLOW, hdr_sha1, 20, false);
    /* Check SHA1 sum */
    uint8_t computed_sha1[20];
    sha_1_init(&sha_1_params);
    sha_1_update(&sha_1_params, &sb_header->signature[0],
        sizeof(struct sb_header_t) - sizeof(sb_header->sha1_header));
    sha_1_finish(&sha_1_params);
    sha_1_output(&sha_1_params, computed_sha1);
    if(memcmp(hdr_sha1, computed_sha1, 20) == 0)
        printf(RED, " Ok\n");
    else
    {
        printf(RED, " Failed\n");
        if(!(flags & SB_IGNORE_SHA1))
            fatal(SB_FORMAT_ERROR, "Bad header checksum\n");
    }
    printf(GREEN, "  Flags: ");
    printf(YELLOW, "%x\n", sb_header->flags);
    printf(GREEN, "  Total file size : ");
    printf(YELLOW, "%ld\n", filesize);

    /* Sizes and offsets */
    printf(BLUE, "Sizes and offsets:\n");
    printf(GREEN, "  # of encryption keys = ");
    printf(YELLOW, "%d\n", sb_header->nr_keys);
    printf(GREEN, "  # of sections = ");
    printf(YELLOW, "%d\n", sb_header->nr_sections);

    /* Versions */
    printf(BLUE, "Versions\n");

    printf(GREEN, "  Random 1: ");
    print_hex(YELLOW, sb_header->rand_pad0, sizeof(sb_header->rand_pad0), true);
    printf(GREEN, "  Random 2: ");
    print_hex(YELLOW, sb_header->rand_pad1, sizeof(sb_header->rand_pad1), true);

    uint64_t micros = sb_header->timestamp;
    time_t seconds = (micros / (uint64_t)1000000L);
    struct tm tm_base;
    memset(&tm_base, 0, sizeof(tm_base));
    /* 2000/1/1 0:00:00 */
    tm_base.tm_mday = 1;
    tm_base.tm_year = 100;
    seconds += mktime(&tm_base);
    struct tm *time = gmtime(&seconds);
    printf(GREEN, "  Creation date/time = ");
    printf(YELLOW, "%s", asctime(time));
    sb_file->timestamp = sb_header->timestamp;

    struct sb_version_t product_ver = sb_header->product_ver;
    fix_version(&product_ver);
    struct sb_version_t component_ver = sb_header->component_ver;
    fix_version(&component_ver);

    memcpy(&sb_file->product_ver, &product_ver, sizeof(product_ver));
    memcpy(&sb_file->component_ver, &component_ver, sizeof(component_ver));

    printf(GREEN, "  Product version   = ");
    printf(YELLOW, "%X.%X.%X\n", product_ver.major, product_ver.minor, product_ver.revision);
    printf(GREEN, "  Component version = ");
    printf(YELLOW, "%X.%X.%X\n", component_ver.major, component_ver.minor, component_ver.revision);

    printf(GREEN, "  Drive tag = ");
    printf(YELLOW, "%x\n", sb_header->drive_tag);
    printf(GREEN, "  First boot tag offset = ");
    printf(YELLOW, "%x\n", sb_header->first_boot_tag_off);
    printf(GREEN, "  First boot section ID = ");
    printf(YELLOW, "0x%08x\n", sb_header->first_boot_sec_id);

    /* encryption cbc-mac */
    struct crypto_key_t real_key;
    real_key.method = CRYPTO_KEY;
    bool valid_key = false; /* false until a matching key was found */

    if(sb_header->nr_keys > 0)
    {
        printf(BLUE, "Encryption keys\n");
        for(int i = 0; i < g_nr_keys; i++)
        {
            printf(RED, "  Key %d\n", i),
            printf(GREEN, "    Key: ");
            printer.color = YELLOW;
            print_key(&printer, sb_printer, &g_key_array[i], true);
            printf(GREEN, "    CBC-MAC: ");
            /* check it */
            uint8_t zero[16];
            memset(zero, 0, 16);
            check_crypto(crypto_setup(&g_key_array[i]));
            check_crypto(crypto_apply(buf, NULL, sb_header->header_size +
                sb_header->nr_sections, zero, &cbcmacs[i], true));
            print_hex(YELLOW, cbcmacs[i], 16, true);
        }

        printf(BLUE, "DEK\n");
        for(int i = 0; i < sb_header->nr_keys; i++)
        {
            printf(RED, "  Entry %d\n", i);
            uint32_t ofs = sizeof(struct sb_header_t)
                + sizeof(struct sb_section_header_t) * sb_header->nr_sections
                + sizeof(struct sb_key_dictionary_entry_t) * i;
            struct sb_key_dictionary_entry_t *dict_entry =
                (struct sb_key_dictionary_entry_t *)&buf[ofs];
            /* cbc mac */
            printf(GREEN, "    Encrypted key: ");
            print_hex(YELLOW, dict_entry->key, 16, true);
            printf(GREEN, "    CBC-MAC      : ");
            print_hex(YELLOW, dict_entry->hdr_cbc_mac, 16, false);
            /* check it */
            int idx = 0;
            while(idx < g_nr_keys && memcmp(dict_entry->hdr_cbc_mac, cbcmacs[idx], 16) != 0)
                idx++;
            if(idx != g_nr_keys)
            {
                printf(RED, " Match\n");
                /* decrypt */
                uint8_t decrypted_key[16];
                uint8_t iv[16];
                memcpy(iv, buf, 16); /* uses the first 16-bytes of SHA-1 sig as IV */
                check_crypto(crypto_setup(&g_key_array[idx]));
                check_crypto(crypto_apply(dict_entry->key, decrypted_key, 1, iv, NULL, false));
                printf(GREEN, "    Decrypted key: ");
                print_hex(YELLOW, decrypted_key, 16, false);
                if(valid_key)
                {
                    if(memcmp(real_key.u.key, decrypted_key, 16) == 0)
                        printf(RED, " Cross-Check Ok");
                    else
                        printf(RED, " Cross-Check Failed");
                }
                else
                {
                    memcpy(real_key.u.key, decrypted_key, 16);
                    valid_key = true;
                }
                printf(OFF, "\n");
            }
            else
                printf(RED, " Don't Match\n");
        }

        if(!valid_key)
        {
            if(g_force)
                printf(GREY, "  No valid key found\n");
            else
                fatal(SB_NO_VALID_KEY, "No valid key found\n");
        }

        if(getenv("SB_REAL_KEY") != 0)
        {
            char *env = getenv("SB_REAL_KEY");
            if(!parse_key(&env, &real_key) || *env)
                fatal(SB_ERROR, "Invalid SB_REAL_KEY\n");
            /* assume the key is valid */
            if(valid_key)
                printf(GREY, "  Overriding real key\n");
            else
                printf(GREY, "  Assuming real key is ok\n");
            valid_key = true;
        }

        printf(RED, "  Summary:\n");
        printf(GREEN, "    Real key: ");
        print_hex(YELLOW, real_key.u.key, 16, true);
        printf(GREEN, "    IV      : ");
        print_hex(YELLOW, buf, 16, true);

        memcpy(sb_file->real_key, real_key.u.key, 16);
        memcpy(sb_file->crypto_iv, buf, 16);
        /* setup real key if needed */
        check_crypto(crypto_setup(&real_key));
    }
    else
        valid_key = true;
    /* sections */
    if(!(flags & SB_RAW_MODE))
    {
        sb_file->nr_sections = sb_header->nr_sections;
        sb_file->sections = xmalloc(sb_file->nr_sections * sizeof(struct sb_section_t));
        memset(sb_file->sections, 0, sb_file->nr_sections * sizeof(struct sb_section_t));
        printf(BLUE, "Sections\n");
        for(int i = 0; i < sb_header->nr_sections; i++)
        {
            uint32_t ofs = sb_header->header_size * BLOCK_SIZE + i * sizeof(struct sb_section_header_t);
            struct sb_section_header_t *sec_hdr = (struct sb_section_header_t *)&buf[ofs];

            char name[5];
            sb_fill_section_name(name, sec_hdr->identifier);
            int pos = sec_hdr->offset * BLOCK_SIZE;
            int size = sec_hdr->size * BLOCK_SIZE;
            int data_sec = !(sec_hdr->flags & SECTION_BOOTABLE);
            int encrypted = !(sec_hdr->flags & SECTION_CLEARTEXT) && sb_header->nr_keys > 0;

            printf(GREEN, "  Section ");
            printf(YELLOW, "'%s'\n", name);
            printf(GREEN, "    pos   = ");
            printf(YELLOW, "%8x - %8x\n", pos, pos+size);
            printf(GREEN, "    len   = ");
            printf(YELLOW, "%8x\n", size);
            printf(GREEN, "    flags = ");
            printf(YELLOW, "%8x", sec_hdr->flags);
            if(data_sec)
                printf(RED, "  Data Section");
            else
                printf(RED, "  Boot Section");
            if(encrypted)
                printf(RED, " (Encrypted)");
            printf(OFF, "\n");

            /* skip it if we cannot decrypt it */
            if(encrypted && !valid_key)
            {
                printf(GREY, "  Skipping section content (no valid key)\n");
                continue;
            }

            /* save it */
            uint8_t *sec = xmalloc(size);
            if(encrypted)
                check_crypto(crypto_apply(buf + pos, sec, size / BLOCK_SIZE, buf, NULL, false));
            else
                memcpy(sec, buf + pos, size);

            struct sb_section_t *s = read_section(data_sec, sec_hdr->identifier,
                sec, size, "      ", u, cprintf, out_err);
            free(sec);
            if(s)
            {
                s->other_flags = sec_hdr->flags & ~SECTION_STD_MASK;
                s->is_cleartext = !encrypted;
                s->alignment = guess_alignment(pos);
                memcpy(&sb_file->sections[i], s, sizeof(struct sb_section_t));
                free(s);
            }
            else
                fatal(*out_err, "Error reading section\n");
        }
    }
    else if(valid_key)
    {
        /* advanced raw mode */
        printf(BLUE, "Commands\n");
        uint32_t offset = sb_header->first_boot_tag_off * BLOCK_SIZE;
        uint8_t iv[16];
        const char *indent = "    ";
        while(true)
        {
            /* restart with IV */
            memcpy(iv, buf, 16);
            uint8_t cmd[BLOCK_SIZE];
            if(sb_header->nr_keys > 0)
                check_crypto(crypto_apply(buf + offset, cmd, 1, iv, &iv, false));
            else
                memcpy(cmd, buf + offset, BLOCK_SIZE);
            struct sb_instruction_header_t *hdr = (struct sb_instruction_header_t *)cmd;
            printf(OFF, "%s", indent);
            uint8_t checksum = instruction_checksum(hdr);
            if(checksum != hdr->checksum)
                printf(GREY, "[Bad checksum]");

            if(hdr->opcode == SB_INST_NOP)
            {
                printf(RED, "NOOP\n");
                offset += BLOCK_SIZE;
            }
            else if(hdr->opcode == SB_INST_TAG)
            {
                struct sb_instruction_tag_t *tag = (struct sb_instruction_tag_t *)hdr;
                printf(RED, "BTAG");
                printf(OFF, " | ");
                printf(BLUE, "sec=0x%08x", tag->identifier);
                printf(OFF, " | ");
                printf(GREEN, "cnt=0x%08x", tag->len);
                printf(OFF, " | ");
                printf(YELLOW, "flg=0x%08x", tag->flags);
                if(tag->hdr.flags & SB_INST_LAST_TAG)
                {
                    printf(OFF, " | ");
                    printf(RED, " Last section");
                }
                printf(OFF, "\n");
                offset += sizeof(struct sb_instruction_tag_t);

                char name[5];
                sb_fill_section_name(name, tag->identifier);
                int pos = offset;
                int size = tag->len * BLOCK_SIZE;
                int data_sec = !(tag->flags & SECTION_BOOTABLE);
                int encrypted = !(tag->flags & SECTION_CLEARTEXT) && sb_header->nr_keys > 0;

                printf(GREEN, "%sSection ", indent);
                printf(YELLOW, "'%s'\n", name);
                printf(GREEN, "%s  pos   = ", indent);
                printf(YELLOW, "%8x - %8x\n", pos, pos+size);
                printf(GREEN, "%s  len   = ", indent);
                printf(YELLOW, "%8x\n", size);
                printf(GREEN, "%s  flags = ", indent);
                printf(YELLOW, "%8x", tag->flags);
                if(data_sec)
                    printf(RED, "  Data Section");
                else
                    printf(RED, "  Boot Section");
                if(encrypted)
                    printf(RED, " (Encrypted)");
                printf(OFF, "\n");

                /* skip it if we cannot decrypt it */
                if(encrypted && !valid_key)
                {
                    printf(GREY, "  Skipping section content (no valid key)\n");
                    offset += size;
                    continue;
                }

                /* save it */
                uint8_t *sec = xmalloc(size);
                if(encrypted)
                    check_crypto(crypto_apply(buf + pos, sec, size / BLOCK_SIZE, buf, NULL, false));
                else
                    memcpy(sec, buf + pos, size);

                struct sb_section_t *s = read_section(data_sec, tag->identifier,
                    sec, size, "      ", u, cprintf, out_err);
                free(sec);
                if(s)
                {
                    s->other_flags = tag->flags & ~SECTION_STD_MASK;
                    s->is_cleartext = !encrypted;
                    s->alignment = guess_alignment(pos);
                    sb_file->sections = augment_array(sb_file->sections,
                        sizeof(struct sb_section_t), sb_file->nr_sections++,
                        s, 1);
                    free(s);
                }
                else
                    fatal(*out_err, "Error reading section\n");

                /* last one ? */
                if(tag->hdr.flags & SB_INST_LAST_TAG)
                    break;
                offset += size;
            }
            else
            {
                fatal(SB_FORMAT_ERROR, "Unknown instruction %d at address 0x%08lx\n", hdr->opcode, (long)offset);
                break;
            }
        }
    }
    else
    {
        printf(GREY, "Cannot read content in raw mode without a valid key\n");
    }

    /* final signature */
    printf(BLUE, "Final signature:\n");
    uint8_t decrypted_block[32];
    if(sb_header->nr_keys > 0)
    {
        printf(GREEN, "  Encrypted SHA-1:\n");
        uint8_t *encrypted_block = &buf[filesize - 32];
        printf(OFF, "    ");
        print_hex(YELLOW, encrypted_block, 16, true);
        printf(OFF, "    ");
        print_hex(YELLOW, encrypted_block + 16, 16, true);
        /* decrypt it */
        check_crypto(crypto_apply(encrypted_block, decrypted_block, 2, buf, NULL, false));
    }
    else
        memcpy(decrypted_block, &buf[filesize - 32], 32);
    printf(GREEN, "  File SHA-1:\n    ");
    print_hex(YELLOW, decrypted_block, 20, false);
    /* check it */
    sha_1_init(&sha_1_params);
    sha_1_update(&sha_1_params, buf, filesize - 32);
    sha_1_finish(&sha_1_params);
    sha_1_output(&sha_1_params, computed_sha1);
    if(memcmp(decrypted_block, computed_sha1, 20) == 0)
        printf(RED, " Ok\n");
    else if(flags & SB_IGNORE_SHA1)
    {
        /* some weird images produced by some buggy tools have wrong SHA-1,
         * this probably gone unnoticed because the bootloader ignores the SHA-1
         * anyway */
        printf(RED, " Failed\n");
        cprintf(u, true, GREY, "Warning: SHA-1 mismatch ignored per flags\n");
    }
    else
    {
        printf(RED, " Failed\n");
        fatal(SB_CHECKSUM_ERROR, "File SHA-1 error\n");
    }

    free(cbcmacs);
    return sb_file;
    #undef printf
    #undef fatal
    #undef print_hex
}

void sb_generate_default_version(struct sb_version_t *ver)
{
    ver->major = ver->minor = ver->revision = 0x999;
}

void sb_build_default_image(struct sb_file_t *sb)
{
    sb->minor_version = IMAGE_MINOR_VERSION;
    sb->timestamp = sb_generate_timestamp();
    sb_generate_default_version(&sb->product_ver);
    sb_generate_default_version(&sb->component_ver);
}

void sb_free_instruction(struct sb_inst_t inst)
{
    free(inst.padding);
    free(inst.data);
}

void sb_free_section(struct sb_section_t sec)
{
    for(int j = 0; j < sec.nr_insts; j++)
        sb_free_instruction(sec.insts[j]);
    free(sec.insts);
}

void sb_free(struct sb_file_t *file)
{
    if(!file) return;

    for(int i = 0; i < file->nr_sections; i++)
        sb_free_section(file->sections[i]);

    free(file->sections);
    free(file);
}

void sb_dump(struct sb_file_t *file, void *u, generic_printf_t cprintf)
{
    #define printf(c, ...) cprintf(u, false, c, __VA_ARGS__)
    struct printer_t printer = {.user = u, .cprintf = cprintf, .color = OFF, .error = false };
    #define print_hex(c, p, len, nl) \
        do { printer.color = c; print_hex(&printer, sb_printer, p, len, nl); } while(0)

    #define TREE    RED
    #define HEADER  GREEN
    #define TEXT    YELLOW
    #define TEXT2   BLUE
    #define SEP     OFF

    printf(BLUE, "SB File\n");
    printf(TREE, "+-");
    printf(HEADER, "Version: ");
    printf(TEXT, "1.%d\n", file->minor_version);
    printf(TREE, "+-");
    printf(HEADER, "Flags: ");
    printf(TEXT, "%x\n", file->flags);
    printf(TREE, "+-");
    printf(HEADER, "Drive Tag: ");
    printf(TEXT, "%x\n", file->drive_tag);
    printf(TREE, "+-");
    printf(HEADER, "First Boot Section ID: ");
    char name[5];
    sb_fill_section_name(name, file->first_boot_sec_id);
    printf(TEXT, "%08x (%s)\n", file->first_boot_sec_id, name);
    printf(TREE, "+-");
    printf(HEADER, "Timestamp: ");
    printf(TEXT, "%#llx", file->timestamp);
    {
        uint64_t micros = file->timestamp;
        time_t seconds = (micros / (uint64_t)1000000L);
        struct tm tm_base;
        memset(&tm_base, 0, sizeof(tm_base));
        /* 2000/1/1 0:00:00 */
        tm_base.tm_mday = 1;
        tm_base.tm_year = 100;
        seconds += mktime(&tm_base);
        struct tm *time = gmtime(&seconds);
        char *str = asctime(time);
        str[strlen(str) - 1] = 0;
        printf(TEXT2, " (%s)\n", str);
    }

    if(file->override_real_key)
    {
        printf(TREE, "+-");
        printf(HEADER, "Real key: ");
        print_hex(TEXT, file->real_key, 16, true);
    }
    if(file->override_crypto_iv)
    {
        printf(TREE, "+-");
        printf(HEADER, "IV      : ");
        print_hex(TEXT, file->crypto_iv, 16, true);
    }
    printf(TREE, "+-");
    printf(HEADER, "Product Version: ");
    printf(TEXT, "%X.%X.%X\n", file->product_ver.major, file->product_ver.minor,
        file->product_ver.revision);
    printf(TREE, "+-");
    printf(HEADER, "Component Version: ");
    printf(TEXT, "%X.%X.%X\n", file->component_ver.major, file->component_ver.minor,
        file->component_ver.revision);

    for(int i = 0; i < file->nr_sections; i++)
    {
        struct sb_section_t *sec = &file->sections[i];
        printf(TREE, "+-");
        printf(HEADER, "Section\n");
        printf(TREE,"|  +-");
        printf(HEADER, "Identifier: ");
        sb_fill_section_name(name, sec->identifier);
        printf(TEXT, "%08x (%s)\n", sec->identifier, name);
        printf(TREE, "|  +-");
        printf(HEADER, "Type: ");
        printf(TEXT, "%s (%s)\n", sec->is_data ? "Data Section" : "Boot Section",
            sec->is_cleartext ? "Cleartext" : "Encrypted");
        printf(TREE, "|  +-");
        printf(HEADER, "Alignment: ");
        printf(TEXT, "%d (bytes)\n", sec->alignment);
        printf(TREE, "|  +-");
        printf(HEADER, "Other Flags: ");
        printf(TEXT, "%#x\n", sec->other_flags);
        printf(TREE, "|  +-");
        printf(HEADER, "Instructions\n");
        for(int j = 0; j < sec->nr_insts; j++)
        {
            struct sb_inst_t *inst = &sec->insts[j];
            printf(TREE, "|  |  +-");
            switch(inst->inst)
            {
                case SB_INST_DATA:
                    printf(HEADER, "DATA");
                    printf(SEP, " | ");
                    printf(TEXT, "size=0x%08x\n", inst->size);
                    break;
                case SB_INST_CALL:
                case SB_INST_JUMP:
                    printf(HEADER, "%s", inst->inst == SB_INST_CALL ? "CALL" : "JUMP");
                    printf(SEP, " | ");
                    printf(TEXT, "addr=0x%08x", inst->addr);
                    printf(SEP, " | ");
                    printf(TEXT2, "arg=0x%08x\n", inst->argument);
                    break;
                case SB_INST_LOAD:
                    printf(HEADER, "LOAD");
                    printf(SEP, " | ");
                    printf(TEXT, "addr=0x%08x", inst->addr);
                    printf(SEP, " | ");
                    printf(TEXT2, "len=0x%08x\n", inst->size);
                    break;
                case SB_INST_FILL:
                    printf(HEADER, "FILL");
                    printf(SEP, " | ");
                    printf(TEXT, "addr=0x%08x", inst->addr);
                    printf(SEP, " | ");
                    printf(TEXT2, "len=0x%08x", inst->size);
                    printf(SEP, " | ");
                    printf(TEXT2, "pattern=0x%08x\n", inst->pattern);
                    break;
                case SB_INST_MODE:
                    printf(HEADER, "MODE");
                    printf(SEP, " | ");
                    printf(TEXT, "mod=0x%08x\n", inst->addr);
                    break;
                case SB_INST_NOP:
                    printf(HEADER, "NOOP\n");
                    break;
                default:
                    printf(GREY, "[Unknown instruction %x]\n", inst->inst);
            }
        }
    }

    #undef printf
    #undef print_hex
}

void sb_get_zero_key(struct crypto_key_t *key)
{
    key->method = CRYPTO_KEY;
    memset(key->u.key, 0, sizeof(key->u.key));
}
