/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2008 by Maurus Cuelenaere
 *
 * 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 <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "creative.h"
#include "hmac-sha1.h"
/*
  ---------------------------------------------------------------------------
  Shamelessly taken from elf.h (for compatibility reasons included)
  ---------------------------------------------------------------------------

   This file defines standard ELF types, structures, and macros.
   Copyright (C) 1995-2003,2004,2005,2006,2007 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.
*/

#include <stdint.h>

/* Type for a 16-bit quantity.  */
typedef uint16_t Elf32_Half;

/* Types for signed and unsigned 32-bit quantities.  */
typedef uint32_t Elf32_Word;
typedef int32_t  Elf32_Sword;

/* Types for signed and unsigned 64-bit quantities.  */
typedef uint64_t Elf32_Xword;
typedef int64_t  Elf32_Sxword;

/* Type of addresses.  */
typedef uint32_t Elf32_Addr;

/* Type of file offsets.  */
typedef uint32_t Elf32_Off;

/* Type for section indices, which are 16-bit quantities.  */
typedef uint16_t Elf32_Section;

#define EI_NIDENT (16)

typedef struct
{
  unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
  Elf32_Half    e_type;             /* Object file type */
  Elf32_Half    e_machine;          /* Architecture */
  Elf32_Word    e_version;          /* Object file version */
  Elf32_Addr    e_entry;            /* Entry point virtual address */
  Elf32_Off     e_phoff;            /* Program header table file offset */
  Elf32_Off     e_shoff;            /* Section header table file offset */
  Elf32_Word    e_flags;            /* Processor-specific flags */
  Elf32_Half    e_ehsize;           /* ELF header size in bytes */
  Elf32_Half    e_phentsize;        /* Program header table entry size */
  Elf32_Half    e_phnum;            /* Program header table entry count */
  Elf32_Half    e_shentsize;        /* Section header table entry size */
  Elf32_Half    e_shnum;            /* Section header table entry count */
  Elf32_Half    e_shstrndx;         /* Section header string table index */
} Elf32_Ehdr;

typedef struct
{
  Elf32_Word    sh_name;        /* Section name (string tbl index) */
  Elf32_Word    sh_type;        /* Section type */
  Elf32_Word    sh_flags;       /* Section flags */
  Elf32_Addr    sh_addr;        /* Section virtual addr at execution */
  Elf32_Off     sh_offset;      /* Section file offset */
  Elf32_Word    sh_size;        /* Section size in bytes */
  Elf32_Word    sh_link;        /* Link to another section */
  Elf32_Word    sh_info;        /* Additional section information */
  Elf32_Word    sh_addralign;   /* Section alignment */
  Elf32_Word    sh_entsize;     /* Entry size if section holds table */
} Elf32_Shdr;

#define ELFMAG0        0x7f     /* Magic number byte 0 */
#define ELFMAG1        'E'      /* Magic number byte 1 */
#define ELFMAG2        'L'      /* Magic number byte 2 */
#define ELFMAG3        'F'      /* Magic number byte 3 */

#define SHF_WRITE      (1 << 0) /* Writable */
#define SHF_ALLOC      (1 << 1) /* Occupies memory during execution */
#define SHF_EXECINSTR  (1 << 2) /* Executable */

#define SHT_NOBITS     8        /* Program space with no data (bss) */

/*
  ---------------------------------------------------------------------------
  End of elf.h
  ---------------------------------------------------------------------------
*/

static const char null_key_v3[]  = "CTL:Z3N07|PDE0.DPMP.";
static const char null_key_v4[]  = "CTL:N0MAD|PDE0.DPFP.";

static const struct device_info devices[] =
{
    /* Creative ZEN V */
    {"C\0r\0e\0a\0t\0i\0v\0e\0 \0Z\0E\0N\0 \0V",                                  42, null_key_v4},
    /* Creative ZEN */
    {"C\0r\0e\0a\0t\0i\0v\0e\0 \0Z\0E\0N",                                        48, null_key_v3},
    /* Creative ZEN X-Fi */
    {"C\0r\0e\0a\0t\0i\0v\0e\0 \0Z\0E\0N\0 \0X\0-\0F\0i\0",                       34, null_key_v4},
    /* Creative ZEN Mozaic */
    {"C\0r\0e\0a\0t\0i\0v\0e\0 \0Z\0E\0N\0 \0M\0o\0z\0a\0i\0c\0",                 38, null_key_v4}
};

/*
Create a Zen Vision:M FRESCUE structure file
*/
extern void int2le(unsigned int val, unsigned char* addr);
extern unsigned int le2int(unsigned char* buf);


static int make_ciff_file(const unsigned char *inbuf, unsigned int length,
                          unsigned char *outbuf, int device)
{
    unsigned char key[20];
    memcpy(outbuf, "FFIC", 4);
    int2le(length+90, &outbuf[4]);
    memcpy(&outbuf[8], "FNIC", 4);
    int2le(96, &outbuf[0xC]);
    memcpy(&outbuf[0x10], devices[device].cinf, devices[device].cinf_size);
    memset(&outbuf[0x10+devices[device].cinf_size], 0,
           96 - devices[device].cinf_size);
    memcpy(&outbuf[0x70], "ATAD", 4);
    int2le(length+32, &outbuf[0x74]);
    memcpy(&outbuf[0x78], "H\0j\0u\0k\0e\0b\0o\0x\0\x32\0.\0j\0r\0m",
           25); /*Unicode encoded*/
    memset(&outbuf[0x78+25], 0, 32);
    memcpy(&outbuf[0x98], inbuf, length);
    memcpy(&outbuf[0x98+length], "LLUN", 4);
    int2le(20, &outbuf[0x98+length+4]);
    /* Do checksum */
    hmac_sha1((unsigned char *)devices[device].null, strlen(devices[device].null),
             outbuf, 0x98+length, key);
    memcpy(&outbuf[0x98+length+8], key, 20);
    return length+0x90+0x1C+8;
}

static int elf_convert(const unsigned char *inbuf, unsigned char *outbuf)
{
    Elf32_Ehdr *main_header;
    Elf32_Shdr *section_header;
    unsigned int i, j, sum;
    intptr_t startaddr;
    
    main_header = (Elf32_Ehdr*)inbuf;
    if( !( main_header->e_ident[0] == ELFMAG0 && main_header->e_ident[1] == ELFMAG1
        && main_header->e_ident[2] == ELFMAG2 && main_header->e_ident[3] == ELFMAG3 ) )
    {
        printf("Invalid ELF header!\n");
        return -1;
    }
    
    startaddr = (intptr_t)outbuf;
    
    for(i = 0; i < main_header->e_shnum; i++)
    {
        section_header = (Elf32_Shdr*)(inbuf+main_header->e_shoff+i*sizeof(Elf32_Shdr));
        
        if( (section_header->sh_flags & SHF_WRITE || section_header->sh_flags & SHF_ALLOC
             || section_header->sh_flags & SHF_EXECINSTR) && section_header->sh_size > 0 
             && section_header->sh_type != SHT_NOBITS                                     )
        {
            /* Address */
            int2le(section_header->sh_addr, outbuf);
            outbuf += 4;
            /* Size */
            int2le(section_header->sh_size, outbuf);
            outbuf += 4;
            /* Checksum */
            sum = 0;
            for(j=0; j<section_header->sh_size; j+= 4)
                sum += le2int((unsigned char*)(inbuf+section_header->sh_offset+j)) + (le2int((unsigned char*)(inbuf+section_header->sh_offset+j))>>16);
            int2le(sum, outbuf);
            outbuf += 2;
            memset(outbuf, 0, 2);
            outbuf += 2;
            /* Data */
            memcpy(outbuf, inbuf+section_header->sh_offset, section_header->sh_size);
            outbuf += section_header->sh_size;
        }
    }
    return (int)((intptr_t)outbuf - startaddr);
}

static int make_jrm_file(const unsigned char *inbuf, unsigned char *outbuf)
{
    int length;

    /* Clear the header area to zero */
    memset(outbuf, 0, 0x18);

    /* Header (EDOC) */
    memcpy(outbuf, "EDOC", 4);
    /* Total Size: temporarily set to 0 */
    memset(&outbuf[0x4], 0, 4);
    /* 4 bytes of zero */
    memset(&outbuf[0x8], 0, 4);
    
    length = elf_convert(inbuf, &outbuf[0xC]);
    if(length < 0)
        return -1;
    /* Now set the actual Total Size */
    int2le(4+length, &outbuf[0x4]);

    return 0xC+length;
}

int zvm_encode(const char *iname, const char *oname, int device, bool enable_ciff)
{
    size_t len;
    int length;
    FILE *file;
    unsigned char *outbuf;
    unsigned char *buf;

    file = fopen(iname, "rb");
    if (!file)
    {
        perror(iname);
        return -1;
    }
    fseek(file, 0, SEEK_END);
    length = ftell(file);

    fseek(file, 0, SEEK_SET);

    buf = (unsigned char*)malloc(length);
    if ( !buf )
    {
        printf("Out of memory!\n");
        return -1;
    }

    len = fread(buf, 1, length, file);
    if(len < (size_t)length)
    {
        perror(iname);
        return -2;
    }
    fclose(file);

    outbuf = (unsigned char*)malloc(length+0x300);
    if ( !outbuf )
    {
        free(buf);
        printf("Out of memory!\n");
        return -1;
    }
    length = make_jrm_file(buf, outbuf);
    free(buf);
    if(length < 0)
    {
        free(outbuf);
        printf("Error in making JRM file!\n");
        return -1;
    }
    if(enable_ciff)
    {
        buf = (unsigned char*)malloc(length+0x200);
        if ( !buf )
        {
            free(outbuf);
            printf("Out of memory!\n");
            return -1;
        }
        memset(buf, 0, length+0x200);
        length = make_ciff_file(outbuf, length, buf, device);
        free(outbuf);
    }
    else
        buf = outbuf;

    file = fopen(oname, "wb");
    if (!file)
    {
        free(buf);
        perror(oname);
        return -3;
    }

    len = fwrite(buf, 1, length, file);
    if(len < (size_t)length)
    {
        free(buf);
        perror(oname);
        return -4;
    }

    free(buf);

    fclose(file);

    return 0;
}
