/* From http://www.hanshq.net/making-executables.html */

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>


static const uint8_t rot13_program[] = {
                               /* read:                          */
0xb8,0x03,0x00,0x00,0x00,      /*   movl   $3,%eax               */
0x31,0xdb,                     /*   xorl   %ebx,%ebx             */
0xb9,0x00,0xa0,0x04,0x08,      /*   movl   $buffer,%ecx          */
0xba,0x00,0x10,0x00,0x00,      /*   movl   $4096,%edx            */
0xcd,0x80,                     /*   int    $0x80                 */
0x85,0xc0,                     /*   testl  %eax,%eax             */
0x7e,0x21,                     /*   jle    end                   */
0x89,0xc2,                     /*   movl   %eax,%edx             */
                               /* rot13:                         */
0x0f,0xb6,0x19,                /*   movzbl (%ecx),%ebx           */
0x8a,0x9b,0x00,0x90,0x04,0x08, /*   movb   rot13_table(%ebx),%bl */
0x88,0x19,                     /*   movb   %bl,(%ecx)            */
0x41,                          /*   incl   %ecx                  */
0x48,                          /*   decl   %eax                  */
0x75,0xf1,                     /*   jnz    rot13                 */
0xb8,0x04,0x00,0x00,0x00,      /*   movl   $4,%eax               */
0xbb,0x01,0x00,0x00,0x00,      /*   movl   $1,%ebx               */
0x29,0xd1,                     /*   subl   %edx,%ecx             */
0xcd,0x80,                     /*   int    $0x80                 */
0xeb,0xc8,                     /*   jmp    read                  */
                               /* end:                           */
0xb8,0x01,0x00,0x00,0x00,      /*   movl   $1,%eax               */
0x31,0xdb,                     /*   xorl   %ebx,%ebx             */
0xcd,0x80                      /*   int    $0x80                 */
};

static const uint8_t rot13_table[] = {   0,   1,   2,   3,   4,   5,   6,
  7,   8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,
 23,  24,  25,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,
 39,  40,  41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,  52,  53,  54,
 55,  56,  57,  58,  59,  60,  61,  62,  63,  64, 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M',  91,  92,  93,  94,  95,  96, 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150,
151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166,
167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182,
183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198,
199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214,
215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230,
231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246,
247, 248, 249, 250, 251, 252, 253, 254, 255 };


/* Write an 8-bit value to file. */
static void write8(FILE *file, uint8_t value)
{
        if (fputc(value, file) == EOF) {
                perror("fputc");
                exit(1);
        }
}

/* Write a 16-bit value in little-endian to file. */
static void write16(FILE *file, uint16_t value)
{
        write8(file, (value >> 0) & 0xff);
        write8(file, (value >> 8) & 0xff);
}

/* Write a 32-bit value in little-endian to file. */
static void write32(FILE *file, uint32_t value)
{
        write8(file, (value >> 0)  & 0xff);
        write8(file, (value >> 8)  & 0xff);
        write8(file, (value >> 16) & 0xff);
        write8(file, (value >> 24) & 0xff);
}

/* Seek to offset in file. */
static void seek(FILE *file, long offset)
{
        if (fseek(file, offset, SEEK_SET) == -1) {
                perror("fseek");
                exit(1);
        }
}

#define TEXT_ADDR  0x08048000
#define RDATA_ADDR 0x08049000
#define BSS_ADDR   0x0804a000

#define TEXT_SIZE  sizeof(rot13_program)
#define RDATA_SIZE sizeof(rot13_table)
#define BSS_SIZE   4096

#define EHSIZE 52       /* ELF header size. */
#define PHSIZE 32       /* Program header size. */
#define PAGE_SIZE 4096

/* Round value up to alignment. */
static uint32_t align_to(uint32_t value, uint32_t alignment)
{
        uint32_t remainder = value % alignment;

        if (remainder == 0) {
                return value;
        }

        return value + (alignment - remainder);
}

int main(int argc, char **argv)
{
        FILE *f;
        size_t i;

        if (argc != 2) {
                fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
                exit(1);
        }

        f = fopen(argv[1], "wb");
        if (f == NULL) {
                perror("fopen");
                exit(1);
        }

        /* ELF header. */
        write8(f, 0x7f);       /* EI_MAG0                     */
        write8(f, 'E');        /* EI_MAG1                     */
        write8(f, 'L');        /* EI_MAG2                     */
        write8(f, 'F');        /* EI_MAG3                     */
        write8(f, 1);          /* EI_CLASS:     32-bit        */
        write8(f, 1);          /* EI_DATA:      Little-endian */
        write8(f, 1);          /* EI_VERSION:   Current       */
        write8(f, 0);          /* EI_OSABI:     No extensions */
        write8(f, 0);          /* EI_ABIVERSION               */
        write8(f, 0);          /* EI_PAD ... (7 bytes)        */
        write8(f, 0);
        write8(f, 0);
        write8(f, 0);
        write8(f, 0);
        write8(f, 0);
        write8(f, 0);

        /* ELF header (continued). */
        write16(f, 2);         /* e_type:     ET_EXEC         */
        write16(f, 3);         /* e_machine:  EM_386          */
        write32(f, 1);         /* e_version:  EV_CURRENT      */
        write32(f, TEXT_ADDR); /* e_entry                     */
        write32(f, EHSIZE);    /* e_phoff                     */
        write32(f, 0);         /* e_shoff                     */
        write32(f, 0);         /* e_flags                     */
        write16(f, EHSIZE);    /* e_ehsize                    */
        write16(f, PHSIZE);    /* e_phentsize                 */
        write16(f, 3);         /* e_phnum                     */
        write16(f, 40);        /* e_shentsize                 */
        write16(f, 0);         /* e_shnum                     */
        write16(f, 0);         /* e_shstrndx                  */

        assert(RDATA_ADDR - TEXT_ADDR >= TEXT_SIZE);
        assert(BSS_ADDR - RDATA_ADDR >= RDATA_SIZE);

        uint32_t text_offset = align_to(EHSIZE + 3 * PHSIZE, PAGE_SIZE);
        uint32_t rdata_offset = align_to(text_offset + TEXT_SIZE, PAGE_SIZE);

        /* Program header 1 (TEXT). */
        write32(f, 1);           /* p_type: PT_LOAD      */
        write32(f, text_offset); /* p_offset             */
        write32(f, TEXT_ADDR);   /* p_vaddr              */
        write32(f, 0);           /* p_paddr              */
        write32(f, TEXT_SIZE);   /* p_filesz             */
        write32(f, TEXT_SIZE);   /* p_memsz              */
        write32(f, 0x4 | 0x1);   /* p_flags: PF_R | PF_X */
        write32(f, PAGE_SIZE);   /* p_align              */

        /* Program header 2 (RDATA). */
        write32(f, 1);            /* p_type: PT_LOAD */
        write32(f, rdata_offset); /* p_offset        */
        write32(f, RDATA_ADDR);   /* p_vaddr         */
        write32(f, 0);            /* p_paddr         */
        write32(f, RDATA_SIZE);   /* p_filesz        */
        write32(f, RDATA_SIZE);   /* p_memsz         */
        write32(f, 0x4);          /* p_flags: PF_R   */
        write32(f, PAGE_SIZE);    /* p_align         */

        /* Program header 3 (BSS). */
        write32(f, 1);         /* p_type: PT_LOAD      */
        write32(f, 0);         /* p_offset             */
        write32(f, BSS_ADDR);  /* p_vaddr              */
        write32(f, 0);         /* p_paddr              */
        write32(f, 0);         /* p_filesz             */
        write32(f, BSS_SIZE);  /* p_memsz              */
        write32(f, 0x4 | 0x2); /* p_flags: PF_R | PF_W */
        write32(f, PAGE_SIZE); /* p_align              */

        /* Write the TEXT segment. */
        seek(f, text_offset);
        for (i = 0; i < sizeof(rot13_program); i++) {
                write8(f, rot13_program[i]);
        }

        /* Write the RDATA segment. */
        seek(f, rdata_offset);
        for (i = 0; i < sizeof(rot13_table); i++) {
                write8(f, rot13_table[i]);
        }

        if (fclose(f) == EOF) {
                perror("fclose");
                exit(1);
        }

        return 0;
}
