/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2008 by Maurus Cuelenaere
 *
 * based on tcctool.c by Dave Chapman
 *
 * USB code based on ifp-line - http://ifp-driver.sourceforge.net
 *
 * ifp-line is (C) Pavel Kriz, Jun Yamishiro and Joe Roback and
 * licensed under the GPL (v2)
 *
 *
 * 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 <inttypes.h>
#include <usb.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdbool.h>
#include <unistd.h>
#include "jz4740.h"
#include "jz_xloader.h"

#define VERSION "0.4"

#define MAX_FIRMWARESIZE   (64*1024*1024)   /* Arbitrary limit (for safety) */

/* For win32 compatibility: */
#ifndef O_BINARY
#define O_BINARY 0
#endif

/* USB IDs for USB Boot Mode */
#define VID              0x601A
#define PID              0x4740

#define EP_BULK_TO       0x01
#define TOUT             5000

enum USB_JZ4740_REQUEST
{
    VR_GET_CPU_INFO = 0,
    VR_SET_DATA_ADDRESS,
    VR_SET_DATA_LENGTH,
    VR_FLUSH_CACHES,
    VR_PROGRAM_START1,
    VR_PROGRAM_START2,
    VR_NOR_OPS,
    VR_NAND_OPS,
    VR_SDRAM_OPS,
    VR_CONFIGURATION
};

enum NOR_OPS_TYPE
{
    NOR_INIT = 0,
    NOR_QUERY,
    NOR_WRITE,
    NOR_ERASE_CHIP,
    NOR_ERASE_SECTOR
};

enum NOR_FLASH_TYPE
{
    NOR_AM29 = 0,
    NOR_SST28,
    NOR_SST39x16,
    NOR_SST39x8
};

enum NAND_OPS_TYPE
{
    NAND_QUERY = 0,
    NAND_INIT,
    NAND_MARK_BAD,
    NAND_READ_OOB,
    NAND_READ_RAW,
    NAND_ERASE,
    NAND_READ,
    NAND_PROGRAM,
    NAND_READ_TO_RAM
};

enum SDRAM_OPS_TYPE
{
    SDRAM_LOAD,
};

enum DATA_STRUCTURE_OB
{
    DS_flash_info,
    DS_hand
};

enum OPTION
{
    OOB_ECC,
    OOB_NO_ECC,
    NO_OOB,
};

int filesize(FILE* fd)
{
    int ret;
    
    fseek(fd, 0, SEEK_END);
    ret = ftell(fd);
    fseek(fd, 0, SEEK_SET);
    
    return ret;
}

bool file_exists(const char* filename)
{
    FILE* fp = fopen(filename, "r");
    
    if(fp)
    {
        fclose(fp);
        return true;
    }
    else
        return false;
}


#define SEND_COMMAND(cmd, arg) err = usb_control_msg(dh, USB_ENDPOINT_OUT | USB_TYPE_VENDOR, (cmd), (arg)>>16, (arg)&0xFFFF, NULL, 0, TOUT);\
                               if (err < 0) \
                               { \
                                   fprintf(stderr,"\n[ERR]  Error sending control message (%d, %s)\n", err, usb_strerror()); \
                                   return -1; \
                               }

#define GET_CPU_INFO(s)        err = usb_control_msg(dh, USB_ENDPOINT_IN | USB_TYPE_VENDOR, VR_GET_CPU_INFO, 0, 0, (s), 8, TOUT); \
                               if (err < 0) \
                               { \
                                   fprintf(stderr,"\n[ERR]  Error sending control message (%d, %s)\n", err, usb_strerror()); \
                                   return -1; \
                               }

#define SEND_DATA(ptr, size)   err = usb_bulk_write(dh, USB_ENDPOINT_OUT | EP_BULK_TO, ((char*)(ptr)), (size), TOUT); \
                               if (err != (size))  \
                               { \
                                   fprintf(stderr,"\n[ERR]  Error writing data\n"); \
                                   fprintf(stderr,"[ERR]  Bulk write error (%d, %s)\n", err, strerror(-err)); \
                                   return -1; \
                               }

#define GET_DATA(ptr, size)    err = usb_bulk_read(dh, USB_ENDPOINT_IN | EP_BULK_TO, ((char*)(ptr)), (size), TOUT); \
                               if (err != (size))  \
                               { \
                                   fprintf(stderr,"\n[ERR]  Error writing data\n"); \
                                   fprintf(stderr,"[ERR]  Bulk write error (%d, %s)\n", err, strerror(-err)); \
                                   return -1; \
                               }
int upload_data(usb_dev_handle* dh, int address, unsigned char* p, int len)
{
    int err;
    char buf[9];
    unsigned char* tmp_buf;

    fprintf(stderr, "[INFO] GET_CPU_INFO: ");
    GET_CPU_INFO(buf);
    buf[8] = 0;
    fprintf(stderr, "%s\n", buf);

    fprintf(stderr, "[INFO] SET_DATA_ADDRESS to 0x%x...", address);
    SEND_COMMAND(VR_SET_DATA_ADDRESS, address);
    fprintf(stderr, " Done!\n");

    fprintf(stderr, "[INFO] Sending data...");
    /* Must not split the file in several packages! */
    SEND_DATA(p, len);
    fprintf(stderr, " Done!\n");
    
    fprintf(stderr, "[INFO] Verifying data...");
    SEND_COMMAND(VR_SET_DATA_ADDRESS, address);
    SEND_COMMAND(VR_SET_DATA_LENGTH, len);
    tmp_buf = malloc(len);
    if (tmp_buf == NULL)
    {
        fprintf(stderr, "\n[ERR]  Could not allocate memory.\n");
        return -1;
    }
    GET_DATA(tmp_buf, len);
    if (memcmp(tmp_buf, p, len) != 0)
        fprintf(stderr, "\n[WARN] Sent data isn't the same as received data...\n");
    else
        fprintf(stderr, " Done!\n");
    free(tmp_buf);
    
    return 0;
}

int boot(usb_dev_handle* dh, int address, bool stage2)
{
    int err;
    
    fprintf(stderr, "[INFO] Booting device STAGE%d...", (stage2 ? 2 : 1));
    SEND_COMMAND((stage2 ? VR_PROGRAM_START2 : VR_PROGRAM_START1), address );
    fprintf(stderr, " Done!\n");
    
    return err;
}

int upload_app(usb_dev_handle* dh, int address, unsigned char* p, int len, bool stage2)
{
    int err = upload_data(dh, address, p, len);
    if(err == 0)
    {
        err = boot(dh, address, stage2);
        if(err == 0)
            fprintf(stderr, "[INFO] Done!\n");
    }
    
    return err;
}

int read_data(usb_dev_handle* dh, int address, unsigned char *p, int len)
{
    int err;
    char buf[9];

    fprintf(stderr, "[INFO] GET_CPU_INFO: ");
    GET_CPU_INFO(buf);
    buf[8] = 0;
    fprintf(stderr, "%s\n", buf);

    fprintf(stderr, "[INFO] Reading data...");
    SEND_COMMAND(VR_SET_DATA_ADDRESS, address);
    SEND_COMMAND(VR_SET_DATA_LENGTH, len);
    GET_DATA(p, len);
    fprintf(stderr, " Done!\n");
    return 0;
}

unsigned int read_reg(usb_dev_handle* dh, int address, int size)
{
    int err;
    unsigned char buf[4];
    
    SEND_COMMAND(VR_SET_DATA_ADDRESS, address);
    SEND_COMMAND(VR_SET_DATA_LENGTH, size);
    GET_DATA(buf, size);

    if(size == 1)
        return buf[0];
    else if(size == 2)
        return (buf[1] << 8) | buf[0];
    else if(size == 4)
        return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
    else
        return 0;
}

int set_reg(usb_dev_handle* dh, int address, unsigned int val, int size)
{
    int err, i;
    unsigned char buf[4];
    
    buf[0] = val & 0xff;
    if(i > 1)
    {
        buf[1] = (val >> 8) & 0xff;
        if(i > 2)
        {
            buf[2] = (val >> 16) & 0xff;
            buf[3] = (val >> 24) & 0xff;
        }
    }
    
    SEND_COMMAND(VR_SET_DATA_ADDRESS, address);
    SEND_DATA(buf, size);

    return 0;
}
#define or_reg(dh, adr, val, size) set_reg(dh, adr, (read_reg(dh, adr, size) | (val)), size);
#define and_reg(dh, adr, val, size) set_reg(dh, adr, (read_reg(dh, adr, size) & (val)), size);
#define bc_reg(dh, adr, val, size) set_reg(dh, adr, (read_reg(dh, adr, size) & ~(val)), size);
#define xor_reg(dh, adr, val, size) set_reg(dh, adr, (read_reg(dh, adr, size) ^ (val)), size);

#define TEST(m, size) fprintf(stderr, "%s -> %x\n", #m, read_reg(dh, m, size));
int test_device(usb_dev_handle* dh)
{
    TEST(INTC_ISR, 4);
    TEST(INTC_IMR, 4);
    TEST(INTC_IMSR, 4);
    TEST(INTC_IMCR, 4);
    TEST(INTC_IPR, 4);
    
    fprintf(stderr, "\n");
    TEST(RTC_RCR, 4);
    TEST(RTC_RSR, 4);
    TEST(RTC_RSAR, 4);
    TEST(RTC_RGR, 4);
    TEST(RTC_HCR, 4);
    TEST(RTC_RCR, 4);
    TEST(RTC_HWFCR, 4);
    TEST(RTC_HRCR, 4);
    TEST(RTC_HWCR, 4);
    TEST(RTC_HWSR, 4);
    
    fprintf(stderr, "\n");
    TEST(GPIO_PXPIN(0), 4);
    TEST(GPIO_PXPIN(1), 4);
    TEST(GPIO_PXPIN(2), 4);
    TEST(GPIO_PXPIN(3), 4);
    
    fprintf(stderr, "\n");
    TEST(CPM_CLKGR, 4);
    
    fprintf(stderr, "\n");
    TEST(SADC_ENA, 1);
    TEST(SADC_CTRL, 1);
    TEST(SADC_TSDAT, 4);
    TEST(SADC_BATDAT, 2);
    TEST(SADC_STATE, 1);
    
    fprintf(stderr, "\n");
    
    TEST(SLCD_CFG, 4);
    TEST(SLCD_CTRL, 1);
    TEST(SLCD_STATE, 1);
    
    return 0;
}

unsigned int read_file(const char *name, unsigned char **buffer)
{
    FILE *fd;
    int len, n;
    
    fd = fopen(name, "rb");
    if (fd == NULL)
    {
        fprintf(stderr, "[ERR]  Could not open %s\n", name);
        return 0;
    }
    
    len = filesize(fd);
    
    *buffer = (unsigned char*)malloc(len);
    if (*buffer == NULL)
    {
        fprintf(stderr, "[ERR]  Could not allocate memory.\n");
        fclose(fd);
        return 0;
    }
    
    n = fread(*buffer, 1, len, fd);
    if (n != len)
    {
        fprintf(stderr, "[ERR]  Short read.\n");
        fclose(fd);
        return 0;
    }
    fclose(fd);
    
    return len;
}
#define _GET_CPU fprintf(stderr, "[INFO] GET_CPU_INFO:"); \
                 GET_CPU_INFO(cpu); \
                 cpu[8] = 0; \
                 fprintf(stderr, " %s\n", cpu);
#define _SET_ADDR(a) fprintf(stderr, "[INFO] Set address to 0x%x...", a); \
                     SEND_COMMAND(VR_SET_DATA_ADDRESS, a); \
                     fprintf(stderr, " Done!\n");
#define _SEND_FILE(a) fsize = read_file(a, &buffer); \
                      if(fsize == 0) \
                        return -1; \
                      fprintf(stderr, "[INFO] Sending file %s: %d bytes...", a, fsize); \
                      SEND_DATA(buffer, fsize); \
                      free(buffer); \
                      fprintf(stderr, " Done!\n");
#define _VERIFY_DATA(a,c) fprintf(stderr, "[INFO] Verifying data (%s)...", a); \
                          fsize = read_file(a, &buffer); \
                          if(fsize == 0) \
                            return -1; \
                          buffer2 = (unsigned char*)malloc(fsize); \
                          SEND_COMMAND(VR_SET_DATA_ADDRESS, c); \
                          SEND_COMMAND(VR_SET_DATA_LENGTH,  fsize); \
                          GET_DATA(buffer2, fsize); \
                          if(memcmp(buffer, buffer2, fsize) != 0) \
                              fprintf(stderr, "\n[WARN] Sent data isn't the same as received data...\n"); \
                          else \
                              fprintf(stderr, " Done!\n"); \
                          free(buffer); \
                          free(buffer2);
#define _STAGE1(a)     fprintf(stderr, "[INFO] Stage 1 at 0x%x\n", a); \
                       SEND_COMMAND(VR_PROGRAM_START1, a);
#define _STAGE2(a)     fprintf(stderr, "[INFO] Stage 2 at 0x%x\n", a); \
                       SEND_COMMAND(VR_PROGRAM_START2, a);
#define _FLUSH         fprintf(stderr, "[INFO] Flushing caches...\n"); \
                       SEND_COMMAND(VR_FLUSH_CACHES, 0);
#ifdef _WIN32
 #define _SLEEP(x) Sleep(x*1000);
#else
 #define _SLEEP(x) sleep(x);
#endif
int mimic_of(usb_dev_handle *dh, bool vx767)
{
    int err, fsize;
    unsigned char *buffer, *buffer2;
    char cpu[8];
    
    fprintf(stderr, "[INFO] Start!\n");
    _GET_CPU;
    _SET_ADDR(0x8000 << 16);
    _SEND_FILE("1.bin");
    _GET_CPU;
    _VERIFY_DATA("1.bin", 0x8000 << 16);
    _STAGE1(0x8000 << 16);
    _SLEEP(3);
    _VERIFY_DATA("2.bin", 0xB3020060);
    _GET_CPU;
    _GET_CPU;
    _FLUSH;
    _GET_CPU;
    _GET_CPU;
    _SET_ADDR(0x8000 << 16);
    _SEND_FILE("3.bin");
    _GET_CPU;
    _VERIFY_DATA("3.bin", 0x8000 << 16);
    _GET_CPU;
    _FLUSH;
    _GET_CPU;
    _GET_CPU;
    _SET_ADDR(0x80D0 << 16);
    _SEND_FILE("4.bin");
    _GET_CPU;
    _VERIFY_DATA("4.bin", 0x80D0 << 16);
    _GET_CPU;
    _FLUSH;
    _GET_CPU;
    _GET_CPU;
    _SET_ADDR(0x80E0 << 16);
    _SEND_FILE("5.bin");
    _GET_CPU;
    _VERIFY_DATA("5.bin", 0x80E0 << 16);
    _GET_CPU;
    _FLUSH;
    _GET_CPU;
    _GET_CPU;
    _SET_ADDR(0x80004000);
    _SEND_FILE("6.bin");
    _GET_CPU;
    _VERIFY_DATA("6.bin", 0x80004000);
    _GET_CPU;
    _FLUSH;
    _GET_CPU;
    _GET_CPU;
    _SET_ADDR(0x80FD << 16);
    _SEND_FILE("7.bin");
    _GET_CPU;
    _VERIFY_DATA("7.bin", 0x80FD << 16);
    _GET_CPU;
    _FLUSH;
    _GET_CPU;
    _STAGE2(0x80FD0004);
    _VERIFY_DATA("8.bin", 0x80004004);
    _VERIFY_DATA("9.bin", 0x80004008);
    _SLEEP(2);
    _GET_CPU;
    _SET_ADDR(0x80E0 << 16);
    _SEND_FILE("10.bin");
    _GET_CPU;
    _VERIFY_DATA("10.bin", 0x80E0 << 16);
    _GET_CPU;
    _FLUSH;
    _GET_CPU;
    if(vx767)
    {
        _STAGE2(0x80E10008);
    }
    else
    {
        _STAGE2(0x80E00008);
    }
    fprintf(stderr, "[INFO] Done!\n");
    return 0;
}

int send_rockbox(usb_dev_handle *dh, const char* filename)
{
    int fsize;
    unsigned char *buffer;
    
    fprintf(stderr, "[INFO] Start!\n");
    if(file_exists("jz_xloader.bin"))
    {
        fprintf(stderr, "[INFO] Using jz_xloader.bin\n");
        fsize = read_file("jz_xloader.bin", &buffer);
        upload_data(dh, 0x080000000, buffer, fsize);
        free(buffer);
    }
    else
    {
        fprintf(stderr, "[INFO] Using built-in jz_xloader.bin\n");
        upload_data(dh, 0x080000000, jz_xloader, LEN_jz_xloader);
    }
    boot(dh, 0x080000000, false);
    _SLEEP(1);
    
    fsize = read_file(filename, &buffer);
    upload_data(dh, 0x080004000, buffer, fsize);
    free(buffer);
    boot(dh, 0x080004000, true);
    
    fprintf(stderr, "[INFO] Done!\n");
    
    return 0;
}

#define SEND_NAND_COMMAND(cs, cmd, option) SEND_COMMAND(VR_NAND_OPS, ((cmd&0xF)|((cs&0xFF)<<4)|((option&0xFF)<<12)) );
#define LENGTH 1024*1024*5
int nand_dump(usb_dev_handle *dh)
{
    int err;
    unsigned int n;
    FILE *fd;
    unsigned char* buffer;
    
    fd = fopen("nand_dump.bin", "wb");
    if (fd == NULL)
    {
        fprintf(stderr, "[ERR]  Could not open nand_dump.bin\n");
        return 0;
    }
    
    buffer = (unsigned char*)malloc(LENGTH);
    if (buffer == NULL)
    {
        fprintf(stderr, "[ERR]  Could not allocate memory.\n");
        fclose(fd);
        return 0;
    }
    
    SEND_NAND_COMMAND(0, NAND_INIT, 0);
    /*
    fprintf(stderr, "[INFO] Querying NAND...\n");
    SEND_NAND_COMMAND(0, NAND_QUERY, 0);
    GET_DATA(buffer, 4);
    printf("[INFO] %x %x %x %x\n", buffer[0], buffer[1], buffer[2], buffer[3]);
    */
    SEND_COMMAND(VR_SET_DATA_ADDRESS, 0);
    SEND_COMMAND(VR_SET_DATA_LENGTH, LENGTH);
    SEND_NAND_COMMAND(0, NAND_READ, NO_OOB);
    
    fprintf(stderr, "[INFO] Reading data...\n");
    err = usb_bulk_read(dh, USB_ENDPOINT_IN | EP_BULK_TO, (char*)buffer, LENGTH, TOUT);
    if (err != LENGTH) 
    {
        fprintf(stderr,"\n[ERR]  Error writing data\n");
        fprintf(stderr,"[ERR]  Bulk write error (%d, %s)\n", err, strerror(-err));
        fclose(fd);
        free(buffer);
        return -1;
    }
    
    n = fwrite(buffer, 1, LENGTH, fd);
    if (n != LENGTH)
    {
        fprintf(stderr, "[ERR]  Short write.\n");
        fclose(fd);
        free(buffer);
        return 0;
    }
    fclose(fd);
    free(buffer);
    
    return n;
}

#define ROM_LENGTH 0x1000*16
int rom_dump(usb_dev_handle *dh)
{
    int err;
    unsigned int n;
    FILE *fd;
    unsigned char* buffer;
    
    fd = fopen("rom_dump.bin", "wb");
    if (fd == NULL)
    {
        fprintf(stderr, "[ERR]  Could not open rom_dump.bin\n");
        return 0;
    }
    
    buffer = (unsigned char*)malloc(ROM_LENGTH);
    if (buffer == NULL)
    {
        fprintf(stderr, "[ERR]  Could not allocate memory.\n");
        fclose(fd);
        return 0;
    }
    
    SEND_COMMAND(VR_SET_DATA_ADDRESS, 0x1FC00000);
    SEND_COMMAND(VR_SET_DATA_LENGTH, ROM_LENGTH);
    
    fprintf(stderr, "[INFO] Reading data...\n");
    err = usb_bulk_read(dh, USB_ENDPOINT_IN | EP_BULK_TO, (char*)buffer, ROM_LENGTH, TOUT);
    if (err != ROM_LENGTH) 
    {
        fprintf(stderr,"\n[ERR]  Error writing data\n");
        fprintf(stderr,"[ERR]  Bulk write error (%d, %s)\n", err, strerror(-err));
        fclose(fd);
        free(buffer);
        return -1;
    }
    
    n = fwrite(buffer, 1, ROM_LENGTH, fd);
    if (n != ROM_LENGTH)
    {
        fprintf(stderr, "[ERR]  Short write.\n");
        fclose(fd);
        free(buffer);
        return 0;
    }
    fclose(fd);
    free(buffer);
    
    return n;
}

int jzconnect(int address, unsigned char* buf, int len, int func)
{
    struct usb_bus *bus;
    struct usb_device *tmp_dev;
    struct usb_device *dev = NULL;
    usb_dev_handle *dh;
    int err;

    fprintf(stderr,"[INFO] Searching for device...\n");
 
    usb_init();
    if(usb_find_busses() < 0)
    {
        fprintf(stderr, "[ERR]  Could not find any USB busses.\n");
        return -2;
    }

    if (usb_find_devices() < 0)
    {
        fprintf(stderr, "[ERR]  USB devices not found(nor hubs!).\n");
        return -3;
    }

    for (bus = usb_get_busses(); bus; bus = bus->next)
    {
        for (tmp_dev = bus->devices; tmp_dev; tmp_dev = tmp_dev->next)
        {
            if (tmp_dev->descriptor.idVendor == VID &&
                tmp_dev->descriptor.idProduct == PID)
            {
                dev = tmp_dev;
                goto found;
            }
        }
    }

    fprintf(stderr, "[ERR]  Device not found.\n");
    fprintf(stderr, "[ERR]  Ensure your device is in USB boot mode and run usbtool again.\n");
    return -4;

found:
    if ( (dh = usb_open(dev)) == NULL)
    {
        fprintf(stderr,"[ERR]  Unable to open device.\n");
        return -5;
    }
    
/* usb_set_configuration() calls are already done in Linux */
#ifdef _WIN32
    err = usb_set_configuration(dh, 1);

    if (err < 0)
    {
        fprintf(stderr, "[ERR]  usb_set_configuration failed (%d, %s)\n", err, usb_strerror());
        usb_close(dh);
        return -6;
    }
#endif
    
    /* "must be called" written in the libusb documentation */
    err = usb_claim_interface(dh, 0);
    if (err < 0)
    {
        fprintf(stderr, "[ERR]  Unable to claim interface (%d, %s)\n", err, usb_strerror());
        usb_close(dh);
        return -7;
    }

    fprintf(stderr,"[INFO] Found device, uploading application.\n");

    /* Now we can transfer the application to the device. */ 

    switch(func)
    {
        case 1:
        case 5:
            err = upload_app(dh, address, buf, len, (func == 5));
        break;
        case 2:
            err = read_data(dh, address, buf, len);
        break;
        case 3:
            err = test_device(dh);
        break;
        case 6:
        case 7:
            err = mimic_of(dh, (func == 7));
        break;
        case 8:
            err = nand_dump(dh);
        break;
        case 9:
            err = rom_dump(dh);
        break;
        case 10:
            err = send_rockbox(dh, (char*)buf);
        break;
    }
    
    /* release claimed interface */
    usb_release_interface(dh, 0);

    usb_close(dh);
    
    return err;
}

void print_usage(void)
{
#ifdef _WIN32
    fprintf(stderr, "Usage: usbtool.exe <CMD> [FILE] [ADDRESS] [LEN]\n");
#else
    fprintf(stderr, "Usage: usbtool <CMD> [FILE] [ADDRESS] [LEN]\n");
#endif

    fprintf(stderr, "\t[ADDRESS] has to be in 0xHEXADECIMAL format\n");
    fprintf(stderr, "\tCMD:\n");
    fprintf(stderr, "\t\t 1 -> upload file to specified address and boot from it\n");
    fprintf(stderr, "\t\t 2 -> read data from [ADDRESS] with length [LEN] to [FILE]\n");
    fprintf(stderr, "\t\t 3 -> read device status\n");
    fprintf(stderr, "\t\t 5 -> same as 1 but do a stage 2 boot\n");
    fprintf(stderr, "\t\t 6 -> mimic VX747 OF fw recovery\n");
    fprintf(stderr, "\t\t 7 -> mimic VX767 OF fw recovery\n");
    fprintf(stderr, "\t\t 8 -> do a NAND dump\n");
    fprintf(stderr, "\t\t 9 -> do a ROM dump\n");
    fprintf(stderr, "\t\t10 -> send Rockbox bootloader at [FILE] to SDRAM\n");

#ifdef _WIN32
    fprintf(stderr, "\nExample:\n\t usbtool.exe 1 fw.bin 0x80000000\n");
    fprintf(stderr, "\t usbtool.exe 2 save.bin 0x81000000 1024\n");
#else
    fprintf(stderr, "\nExample:\n\t usbtool 1 fw.bin 0x80000000\n");
    fprintf(stderr, "\t usbtool 2 save.bin 0x81000000 1024\n");
#endif
}

int main(int argc, char* argv[])
{
    unsigned char* buf;
    int n, len, address, cmd=0;
    FILE* fd;

    fprintf(stderr, "USBtool v" VERSION " - (C) 2008 Maurus Cuelenaere\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");
    
    if(argc > 1)
        sscanf(argv[1], "%d", &cmd);
    switch(cmd)
    {
        case 5:
        case 1:
            if (strcmp(argv[3], "-1") == 0)
                address = 0x80000000;
            else
            {
                if (sscanf(argv[3], "0x%x", &address) <= 0)
                {
                    print_usage();
                    return -1;
                }
            }
            
            fd = fopen(argv[2], "rb");
            if (fd < 0)
            {
                fprintf(stderr, "[ERR]  Could not open %s\n", argv[2]);
                return 4;
            }
            
            len = filesize(fd);

            if (len > MAX_FIRMWARESIZE)
            {
                fprintf(stderr, "[ERR]  Firmware file too big\n");
                fclose(fd);
                return 5;
            }
            
            buf = malloc(len);
            if (buf == NULL)
            {
                fprintf(stderr, "[ERR]  Could not allocate memory.\n");
                fclose(fd);
                return 6;
            }
            
            n = fread(buf, 1, len, fd);
            if (n != len)
            {
                fprintf(stderr, "[ERR]  Short read.\n");
                fclose(fd);
                return 7;
            }
            fclose(fd);
            
            fprintf(stderr, "[INFO] File size: %d bytes\n", n);
            
            return jzconnect(address, buf, len, cmd);
        case 2:
            if (sscanf(argv[3], "0x%x", &address) <= 0)
            {
                print_usage();
                return -1;
            }
            
            fd = fopen(argv[2], "wb");
            if (fd < 0)
            {
                fprintf(stderr, "[ERR]  Could not open %s\n", argv[2]);
                return 4;
            }
            
            sscanf(argv[4], "%d", &len);
            
            buf = malloc(len);
            if (buf == NULL)
            {
                fprintf(stderr, "[ERR]  Could not allocate memory.\n");
                fclose(fd);
                return 6;
            }
            
            int err = jzconnect(address, buf, len, 2);
            
            n = fwrite(buf, 1, len, fd);
            if (n != len)
            {
                fprintf(stderr, "[ERR]  Short write.\n");
                fclose(fd);
                return 7;
            }
            fclose(fd);
            
            return err;
        case 10:
            if(argc < 3)
            {
                print_usage();
                return 1;
            }
            
            if(!file_exists(argv[2]))
            {
                print_usage();
                return 1;
            }
            return jzconnect(address, (unsigned char*)argv[2], 0, 10);
        case 3:
        case 6:
        case 7:
        case 8:
        case 9:
            return jzconnect(address, NULL, 0, cmd);
        default:
            print_usage();
            return 1;
    }

    return 0;
}
