// Copyright 2007,2008  Segher Boessenkool  <segher@kernel.crashing.org>
// Copyright 2008 Sven Peter <svpe@gmx.net>
// Licensed under the terms of the GNU GPL, version 2
// http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt

// Converts edoc to elf files, based on dol2elf by Segher Boessenkool.
// Entry point is always assumed as zero.

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <elf.h>

struct section {
    uint32_t addr;
    uint32_t size;
    uint32_t offset;
    uint32_t elf_offset;
    uint32_t str_offset;
};

uint16_t le16(const uint8_t *p)
{
    return (p[1] << 8) | p[0];
}

uint32_t le32(const uint8_t *p)
{
    return (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0];
}

uint64_t le64(const uint8_t *p)
{
    return ((uint64_t)le32(p + 4) << 32) | le32(p);
}

uint64_t le34(const uint8_t *p)
{
    return 4 * (uint64_t)le32(p);
}

void wle16(uint8_t *p, uint16_t x)
{
    p[1] = x >> 8;
    p[0] = x;
}

void wle32(uint8_t *p, uint32_t x)
{
    wle16(p + 2, x >> 16);
    wle16(p, x);
}

void wle64(uint8_t *p, uint64_t x)
{
    wle32(p + 4, x >> 32);
    wle32(p, x);
}

static uint32_t edoc_chksum(uint16_t *data, uint32_t len)
{
    uint32_t sum = 0;

    while(len > 0)
    {
        sum += le16((uint8_t *)data++);
        len -= 2;
    }

    return sum & 0xffff;
}

void fatal(const char *s, ...)
{
    char message[256];
    va_list ap;

    va_start(ap, s);
    vsnprintf(message, sizeof message, s, ap);

    perror(message);

    exit(1);
}

static void edoc2elf(char *inname, char *outname)
{
    uint8_t tmpbuffer[16];
    uint8_t elfheader[0x400] = {0};
    uint8_t segheader[0x400] = {0};
    uint8_t secheader[0x400] = {0};
    uint8_t strings[0x400] = "\0.strtab";
    uint32_t str_offset = 9;
    struct section *section;
    FILE *in, *out;
    uint32_t n_sections;
    uint32_t size_total;

    uint32_t entry;
    uint32_t elf_offset;
    uint32_t i;
    uint8_t *p;

    in = fopen(inname, "rb");
    fread(tmpbuffer, 1, sizeof tmpbuffer, in);

    if(memcmp(tmpbuffer, "EDOC", 4) != 0)
    {
        fprintf(stderr, "invalid edoc file.\n");
        return;
    }

    n_sections = 0;
    size_total = le32(tmpbuffer + 4);
    fseek(in, 0xc, SEEK_SET);
    do
    {
        n_sections++;
        fread(tmpbuffer, 1, sizeof tmpbuffer, in);
    }
    while(feof(in) == 0 && fseek(in, le32(tmpbuffer + 4) + 12 - sizeof(tmpbuffer), SEEK_CUR) == 0 && ftell(in) < size_total);

    printf("%d sections.\n", n_sections);
    section = calloc(n_sections, sizeof(*section));
    if(section == NULL)
        fatal("section alloc failure");

    elf_offset = 0x1000;
    i = 0;
    fseek(in, 0xc, SEEK_SET);
    do
    {
        fread(tmpbuffer, 1, sizeof tmpbuffer, in);
        section[i].offset = ftell(in);
        section[i].addr = le32(tmpbuffer);
        section[i].size = le32(tmpbuffer + 4);
        section[i].elf_offset = elf_offset;
        elf_offset += section[i].size;

        sprintf(strings + str_offset, ".text.%d", i);
        section[i].str_offset = str_offset;
        str_offset += i > 10 ? 9 : 8;

        fseek(in, section[i].size + 12 - sizeof(tmpbuffer), SEEK_CUR);
        i++;
    }
    while(i < n_sections);

    entry = 0;
    printf("entry point = %08x\n", entry);

    memset(elfheader, 0, sizeof elfheader);
    elfheader[0] = 0x7f;
    elfheader[1] = 0x45;
    elfheader[2] = 0x4c;
    elfheader[3] = 0x46;
    elfheader[4] = 0x01; // file class, 32bit executable
    elfheader[5] = 0x01; // little-endian 
    elfheader[6] = 0x01; 

    wle16(elfheader + 0x10, 2);  // executable file
    wle16(elfheader + 0x12, 40); // ARM
    wle32(elfheader + 0x14, 1);  // ELF version
    wle32(elfheader + 0x18, entry);

    wle32(elfheader + 0x1c, 0x400);
    wle32(elfheader + 0x20, 0x800);
    wle32(elfheader + 0x24, 0); // no flags
    wle16(elfheader + 0x28, 0x34); // header size
    wle16(elfheader + 0x2a, 0x20); // header table entry size
    wle16(elfheader + 0x2c, n_sections); // count
    wle16(elfheader + 0x2e, 0x28); // section header table entry size
    wle16(elfheader + 0x30, n_sections + 2); // count
    wle16(elfheader + 0x32, 1); // string table index

    p = segheader;
    for (i = 0; i < n_sections; i++)
        if (section[i].size) {
            wle32(p + 0x00, 1);
            wle32(p + 0x04, section[i].elf_offset);
            wle32(p + 0x08, section[i].addr);
            wle32(p + 0x0c, section[i].addr);
            wle32(p + 0x10, section[i].size);
            wle32(p + 0x14, section[i].size);
            wle32(p + 0x18, 5);
            wle32(p + 0x1c, 0x20);
            p += 0x20;
        }

    p = secheader + 0x28;
    wle32(p + 0x00, 1);
    wle32(p + 0x04, 3);
    wle32(p + 0x08, 0);
    wle32(p + 0x0c, 0);
    wle32(p + 0x10, 0xc00);
    wle32(p + 0x14, 0x400);
    wle32(p + 0x18, 0);
    wle32(p + 0x1c, 0);
    wle32(p + 0x20, 1);
    wle32(p + 0x24, 0);
    p += 0x28;

    for (i = 0; i < n_sections; i++)
        if (section[i].size) {
            wle32(p + 0x00, section[i].str_offset);
            wle32(p + 0x04, i == 18 ? 8 : 1);
            wle32(p + 0x08, i < 7 ? 6 : 3);
            wle32(p + 0x0c, section[i].addr);
            wle32(p + 0x10, section[i].elf_offset);
            wle32(p + 0x14, section[i].size);
            wle32(p + 0x18, 0);
            wle32(p + 0x1c, 0);
            wle32(p + 0x20, 0x20);
            wle32(p + 0x24, 0);
            p += 0x28;
        }

    out = fopen(outname, "wb");
    fwrite(elfheader, 1, sizeof elfheader, out);
    fwrite(segheader, 1, sizeof segheader, out);
    fwrite(secheader, 1, sizeof secheader, out);
    fwrite(strings, 1, sizeof strings, out);

    for (i = 0; i < n_sections; i++)
        if (section[i].size) {
            p = malloc(section[i].size);
            fseek(in, section[i].offset, SEEK_SET);
            fread(p, 1, section[i].size, in);
            fseek(out, section[i].elf_offset, SEEK_SET);
            fwrite(p, 1, section[i].size, out);
            free(p);
        }

    fclose(out);
    fclose(in);
}

int main(int argc, char **argv)
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <edoc> <elf>\n", argv[0]);
        exit(1);
    }

    edoc2elf(argv[1], argv[2]);

    return 0;
}

