/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 *
 * Copyright (c) 2009, Dave Chapman
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 * 
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

#include <stdio.h>
#if !defined(_MSC_VER)
#include <unistd.h>
#include <fcntl.h>
#endif
#include <string.h>
#include <stdlib.h>
#if !defined(_MSC_VER)
#include <inttypes.h>
#else
#include "pstdint.h"
#endif
#include <sys/types.h>
#include <sys/stat.h>

#include "mtp_common.h"
#include "bootimg.h"
#include "mknkboot.h"

/* Code to create a single-boot bootloader.
   Based on tools/gigabeats.c by Will Robertson.
*/

/* Entry point (and load address) for the main Rockbox bootloader */
#define BL_ENTRY_POINT 0x8a000000

static void put_uint32le(uint32_t x, unsigned char* p)
{
    p[0] = (unsigned char)(x & 0xff);
    p[1] = (unsigned char)((x >> 8) & 0xff);
    p[2] = (unsigned char)((x >> 16) & 0xff);
    p[3] = (unsigned char)((x >> 24) & 0xff);
}

static uint32_t calc_csum(const unsigned char* pb, int cb)
{
    uint32_t l = 0;
    while (cb--)
        l += *pb++;
    return l;
}

static void create_single_boot(unsigned char* boot, int bootlen,
                               unsigned char** fwbuf, off_t* fwsize)
{
    unsigned char* buf;

    /* 15 bytes for header, 16 for signature bypass,
     * 12 for record header, size of bootloader, 12 for footer */
    *fwsize = 15 + 16 + 12 + bootlen + 12;
    *fwbuf = malloc(*fwsize);

    if(fwbuf == NULL) {
        fprintf(stderr, "[ERR]  Cannot allocate memory.\n" );
        *fwbuf = NULL;
        *fwsize = 0;
        return;
    }

    buf = *fwbuf;

    /* Copy bootloader image. */
    memcpy(buf + 43, boot, bootlen);

    /* Step 2: Create the file header */
    sprintf((char *)buf, "B000FF\n");
    put_uint32le(0x88200000, buf+7);

    /* If the value below is too small, the update will attempt to flash.
     * Be careful when changing this (leaving it as is won't cause issues) */
    put_uint32le(0xCC0CD8, buf+11);

    /* Step 3: Add the signature bypass record */
    put_uint32le(0x88065A10, buf+15);
    put_uint32le(4, buf+19);
    put_uint32le(0xE3A00001, buf+27);
    put_uint32le(calc_csum(buf+27,4), buf+23);

    /* Step 4: Create a record for the actual code */
    put_uint32le(BL_ENTRY_POINT, buf+31);
    put_uint32le(bootlen, buf+35);
    put_uint32le(calc_csum(buf + 43, bootlen), buf+39);

    /* Step 5: Write the footer */
    put_uint32le(0, buf+*fwsize-12);
    put_uint32le(BL_ENTRY_POINT, buf+*fwsize-8);
    put_uint32le(0, buf+*fwsize-4);

    return;
}

static int readfile(const char* filename, struct filebuf *buf)
{
    int res;
    FILE* fp;
    size_t bread;
#ifdef _LARGEFILE64_SOURCE
    struct stat64 sb;
    res = stat64(filename, &sb);
#else
    struct stat sb;
    res = stat(filename, &sb);
#endif
    if(res == -1) {
        fprintf(stderr, "[ERR]  Getting firmware file size failed!\n");
        return 1;
    }
    buf->len = sb.st_size;
    buf->buf = (unsigned char*)malloc(buf->len);
    /* load firmware binary to memory. */
    fp = fopen(filename, "rb");
    bread = fread(buf->buf, sizeof(unsigned char), buf->len, fp);
    if((off_t)(bread * sizeof(unsigned char)) != buf->len) {
        fprintf(stderr, "[ERR]  Error reading  file %s!\n", filename);
        return 1;
    }
    fclose(fp);
    return 0;
}


int beastpatcher(const char* bootfile, const char* firmfile, int interactive)
{
    char yesno[4];
    struct mtp_info_t mtp_info;
    struct filebuf bootloader;
    struct filebuf firmware;
    struct filebuf fw;
    bootloader.buf = bootimg;
    bootloader.len = LEN_bootimg;

    if (bootfile) {
        if(readfile(bootfile, &bootloader) != 0) {
            fprintf(stderr,"[ERR]  Reading bootloader file failed.\n");
            return 1;
        }
    }
    if (firmfile) {
        if(readfile(firmfile, &firmware) != 0) {
            fprintf(stderr,"[ERR]  Reading firmware file failed.\n");
            return 1;
        }
    }

    if (mtp_init(&mtp_info) < 0) {
        fprintf(stderr,"[ERR]  Can not init MTP\n");
        return 1;
    }

    /* Scan for attached MTP devices. */
    if (mtp_scan(&mtp_info) < 0)
    {
        fprintf(stderr,"[ERR]  No devices found\n");
        return 1;
    }

    printf("[INFO] Found device \"%s - %s\"\n", mtp_info.manufacturer,
                                                mtp_info.modelname);
    printf("[INFO] Device version: \"%s\"\n",mtp_info.version);

    if (interactive) {
        if(firmfile) {
            printf("\nEnter i to install the Rockbox dualboot bootloader or c "
                    "to cancel and do nothing (i/c): ");
        }
        else {
            printf("\nEnter i to install the Rockbox bootloader or c to cancel "
                    "and do nothing (i/c): ");
        }
        fgets(yesno,4,stdin);
    }

    if (!interactive || yesno[0]=='i')
    {
        if(firmfile) {
            /* if a firmware file is given create a dualboot image. */
            if(verifyfirm(&firmware) < 0 || mknkboot(&firmware, &bootloader, &fw))
            {
                fprintf(stderr,"[ERR]  Creating dualboot firmware failed.\n");
                return 1;
            }
        }
        else {
            /* Create a single-boot bootloader from the embedded bootloader */
            create_single_boot(bootloader.buf, bootloader.len, &fw.buf, &fw.len);
        }

        if (fw.buf == NULL)
        {
            fprintf(stderr,"[ERR]  Creating the bootloader failed.\n");
            return 1;
        }

        if (mtp_send_firmware(&mtp_info, fw.buf, fw.len) == 0)
        {
            fprintf(stderr,"[INFO] Bootloader installed successfully.\n");
        }
        else
        {
            fprintf(stderr,"[ERR]  Bootloader install failed.\n");
        }

        /* We are now done with the firmware image */
        free(fw.buf);
    }
    else
    {
        fprintf(stderr,"[INFO] Installation cancelled.\n");
    }
    if(bootfile) {
        free(bootloader.buf);
    }
    if(firmfile) {
        free(firmware.buf);
    }

    mtp_finished(&mtp_info);

    return 0;
}


int sendfirm(const char* filename)
{
    struct mtp_info_t mtp_info;

    if (mtp_init(&mtp_info) < 0) {
        fprintf(stderr,"[ERR]  Can not init MTP\n");
        return 1;
    }

    /* Scan for attached MTP devices. */
    if (mtp_scan(&mtp_info) < 0)
    {
        fprintf(stderr,"[ERR]  No devices found\n");
        return 1;
    }

    printf("[INFO] Found device \"%s - %s\"\n", mtp_info.manufacturer,
                                                mtp_info.modelname);
    printf("[INFO] Device version: \"%s\"\n",mtp_info.version);

    mtp_send_file(&mtp_info, filename);

    mtp_finished(&mtp_info);
    return 0;
}

