#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#define VERSION "v0.3"

/* time field stucture */
struct rktime_t
{
    uint16_t year;
    uint16_t month;
    uint16_t day;
    uint16_t hour;
    uint16_t minute;
    uint16_t second;
};

/* Rock27Boot.bin header structure */
struct rkboot_info_t
{
    char     sign[32];
    uint8_t  check_values[16];
    struct   rktime_t time;
    uint32_t ui_master_version;
    uint32_t ui_slave_version;
    uint32_t s1_offset;
    int32_t  s1_len;
    uint32_t s2_offset;
    int32_t  s2_len;
    uint32_t s3_offset;
    int32_t  s3_len;
    uint32_t s4_offset;
    int32_t  s4_len;
    uint32_t version_flag;
};

/* actions */
enum {
      NONE = 0,
      INFO = 1,
      EXTRACT = 2,
      SCRAMBLE = 4
};

/* scramble mode */
enum {
      CONTINOUS_ENC, /* scramble whole block at once */
      PAGE_ENC       /* nand bootloader is scrambled in 0x200 chunks */
};

/* scrambling/descrambling reverse engineered by AleMaxx */
static void encode_page(uint8_t *inpg, uint8_t *outpg, const int size)
{

uint8_t key[] = {
        0x7C, 0x4E, 0x03, 0x04,
        0x55, 0x05, 0x09, 0x07,
        0x2D, 0x2C, 0x7B, 0x38,
        0x17, 0x0D, 0x17, 0x11
};
        int i, i3, x, val, idx;

        uint8_t key1[0x100];
        uint8_t key2[0x100];

        for (i=0; i<0x100; i++) {
                key1[i] = i;
                key2[i] = key[i&0xf];
        }

        i3 = 0;
        for (i=0; i<0x100; i++) {
                x = key1[i];
                i3 = key1[i] + i3;
                i3 += key2[i];
                i3 &= 0xff;
                key1[i] = key1[i3];
                key1[i3] = x;
        }

        idx = 0;
        for (i=0; i<size; i++) {
                x = key1[(i+1) & 0xff];
                val = x;
                idx = (x + idx) & 0xff;
                key1[(i+1) & 0xff] = key1[idx];
                key1[idx] = (x & 0xff);
                val = (key1[(i+1)&0xff] + x) & 0xff;
                val = key1[val];
                outpg[i] = val ^ inpg[i];
        }
}

static void *binary_extract(FILE *fp, uint32_t offset, uint32_t len, int descramble, int encode_mode)
{
    void *buff, *buff_ptr;
    uint32_t ret;

    if ((fp == NULL) || len == 0)
        return NULL;

        /* allocate buff */
        if ((buff = malloc(len)) == NULL)
            return NULL;

        /* seek to the begining of the data */
        fseek(fp, offset, SEEK_SET);

        /* read into the buffer */
        ret = fread(buff, 1, len, fp);

        if (ret != len)
        {
            free(buff);
            return NULL;
        }

        /* descramble */
        if ( descramble )
        {
            buff_ptr = buff;
            if (encode_mode == PAGE_ENC)
            {
                while (len >= 0x200)
                {
                    encode_page((uint8_t *)buff_ptr,
                                (uint8_t *)buff_ptr,
                                0x200);

                    buff_ptr += 0x200;
                    len -= 0x200;
                }
            }
            encode_page((uint8_t *)buff_ptr, (uint8_t *)buff_ptr, len);
        }

    return buff;
}

static void usage(void)
{
    printf("Usage: rkboottool [options] Rock27Boot.bin\n");
    printf("-h|--help         This help message\n");
    printf("-e|--extract      Extract binary images from Rock27Boot.bin file\n");
    printf("-d|--descramble   Descramble extracted binary images\n");
    printf("-i|--info         Print info about Rock27Boot.bin file\n");
    printf("\n");
    printf("Usually you would like to use -d -e together to obtain raw binary\n");
    printf("(out files rkboot_s1.bin, rkboot_s2.bin, rkboot_s3.bin, rkboot_s4.bin)\n");
}

int main (int argc, char **argv)
{
    struct rkboot_info_t rkboot_info;
    FILE *fp_in, *fp_out;
    int32_t i = 0, action = NONE;
    int32_t ret;
    void *buff;
    char *in_filename = NULL;

    if ( argc < 2 )
    {
        usage();
        return -1;
    }

    /* print banner */
    fprintf(stderr,"rkboottool " VERSION "\n");
    fprintf(stderr,"(C) Marcin Bukat 2011\n");
    fprintf(stderr,"This is free software; see the source for copying conditions.  There is NO\n");
    fprintf(stderr,"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n");

    /* arguments handling */
    while (i < argc)
    {
        if ((strcmp(argv[i],"-i")==0) || (strcmp(argv[i],"--info")==0))
        {
            action |= INFO;
        }
        else if ((strcmp(argv[i],"-e")==0) || (strcmp(argv[i],"--extract")==0))
        {
            action |= EXTRACT;
        }
        else if ((strcmp(argv[i],"-d")==0) || (strcmp(argv[i],"--descramble")==0))
        {
            action |= SCRAMBLE;
        }
        else if ((strcmp(argv[i],"-h")==0) || (strcmp(argv[i],"--help")==0))
        {
            usage();
            return 0;
        }
        else if ( argv[i][0] != '-' )
        {
            /* file argument */
            in_filename = argv[i];
        }
        i++;
    }

    if ( (fp_in = fopen(in_filename, "rb")) == NULL )
    {
        fprintf(stderr, "error: can't open %s file for reading\n", in_filename);
        return -1;
    }

    ret = fread(&rkboot_info, 1, sizeof(rkboot_info), fp_in);

    if (ret != sizeof(rkboot_info))
    {
        fclose(fp_in);
        fprintf(stderr, "error: can't read %s file header\n", in_filename);
        fprintf(stderr, "read %d, expected %d\n", ret, sizeof(rkboot_info));
        return -2;
    }

    if (action & INFO)
    {
        printf("file: %s\n", in_filename);
        printf("signature: %s\n", rkboot_info.sign);
        printf("check bytes: ");
        for (i = 0; i < 16; i++)
            printf("0x%0x ", rkboot_info.check_values[i]);

        printf("\n");
        printf("timestamp %d.%d.%d %d:%d:%d\n", rkboot_info.time.day,
                                                rkboot_info.time.month,
                                                rkboot_info.time.year,
                                                rkboot_info.time.hour,
                                                rkboot_info.time.minute,
                                                rkboot_info.time.second);
        printf("UI master version: 0x%0x\n", rkboot_info.ui_master_version);
        printf("UI slave version: 0x%0x\n", rkboot_info.ui_slave_version);
        printf("s1 data offset: 0x%0x\n", rkboot_info.s1_offset);
        printf("s1 data len: 0x%0x\n", rkboot_info.s1_len);
        printf("s2 offset: 0x%0x\n", rkboot_info.s2_offset);
        printf("s2 len: 0x%0x\n", rkboot_info.s2_len);
        printf("s3 offset: 0x%0x\n", rkboot_info.s3_offset);
        printf("s3 len: 0x%0x\n", rkboot_info.s3_len);
        printf("s4 offset: 0x%0x\n", rkboot_info.s4_offset);
        printf("s4 len: 0x%0x\n", rkboot_info.s4_len);
        printf("UI version flag: 0x%0x\n", rkboot_info.version_flag);
    }

    if (action & EXTRACT)
    {
        /* first stage */
        buff = binary_extract(fp_in, rkboot_info.s1_offset,
                                     rkboot_info.s1_len,
                                     action & SCRAMBLE,
                                     CONTINOUS_ENC);

        if ( buff == NULL )
        {
            fclose(fp_in);
            fprintf(stderr, "error: can't extract image\n");
            return -2;
        }

        /* output */
        if ((fp_out = fopen("rkboot_s1.bin", "wb")) == NULL)
        {
            free(buff);
            fclose(fp_in);
            fprintf(stderr, "[error]: can't open rkboot_s1.bin for writing\n");
            return -3;
        }

        fwrite(buff, 1, rkboot_info.s1_len, fp_out);
 
        fprintf(stderr, "[info]: extracted rkboot_s1.bin file\n");
        free(buff);
        fclose(fp_out);

        /* second stage */
        buff = binary_extract(fp_in, rkboot_info.s2_offset,
                                     rkboot_info.s2_len,
                                     action & SCRAMBLE,
                                     CONTINOUS_ENC);

        if ( buff == NULL )
        {
            fclose(fp_in);
            fprintf(stderr, "error: can't extract image\n");
            return -2;
        }

        if ((fp_out = fopen("rkboot_s2.bin", "wb")) == NULL)
        {
            free(buff);
            fclose(fp_in);
            fprintf(stderr, "[error]: can't open rkboot_s2.bin for writing\n");
            return -4;
        }

        fwrite(buff, 1, rkboot_info.s2_len, fp_out);

        fprintf(stderr, "[info]: extracted rkboot_s2.bin file\n");
        free(buff);
        fclose(fp_out);

        /* third stage */
        buff = binary_extract(fp_in, rkboot_info.s3_offset,
                                     rkboot_info.s3_len,
                                     action & SCRAMBLE,
                                     PAGE_ENC);
        if ( buff == NULL )
        {
            fclose(fp_in);
            fprintf(stderr, "[error]: can't extract image.\n");
            return -2;
        }

        if ((fp_out = fopen("rkboot_s3.bin", "wb")) == NULL)
        {
            free(buff);
            fclose(fp_in);
            fprintf(stderr, "[error]: can't open rkboot_s3.bin for writing\n");
            return -4;
        }

        fwrite(buff, 1, rkboot_info.s3_len, fp_out);

        fprintf(stderr, "[info]: extracted rkboot_s3.bin file\n");
        free(buff);
        fclose(fp_out);

        /* forth stage */
        buff = binary_extract(fp_in, rkboot_info.s4_offset,
                                     rkboot_info.s4_len,
                                     action & SCRAMBLE,
                                     CONTINOUS_ENC);
        if ( buff == NULL )
        {
            fclose(fp_in);
            fprintf(stderr, "[error]: can't extract image\n");
            return -2;
        }

        if ((fp_out = fopen("rkboot_s4.bin", "wb")) == NULL)
        {
            free(buff);
            fclose(fp_in);
            fprintf(stderr, "[error]: can't open rkboot_s4.bin for writing\n");
            return -4;
        }

        fwrite(buff, 1, rkboot_info.s4_len, fp_out);

        fprintf(stderr, "[info]: extracted rkboot_s4.bin file\n");
        free(buff);
        fclose(fp_out);
    }

    fclose(fp_in);
    return 0;
}

