/*
# _____     ___ ____     ___ ____
#  ____|   |    ____|   |        | |____|    https://github.com/pspdev
# |     ___|   |     ___|    ____| |    \    PSPDEV Open Source Project.
#-----------------------------------------------------------------------
# Review pspsdk README & LICENSE files for further details.
#
# Converts any file into a PSP's .o file.
#
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;

int alignment = 16;
int have_size = 1;
int have_irx = 0;

u32 LE32(u32 b) {
    u32 t = 0x12345678;
    if (*((unsigned char*)(&t)) == 0x78) {
	return b;
    } else {
	return ((b & 0xff000000) >> 24) |
	       ((b & 0x00ff0000) >> 8 ) |
	       ((b & 0x0000ff00) << 8 ) |
	       ((b & 0x000000ff) << 24);
    }
}

u16 LE16(u16 b) {
    u32 t = 0x12345678;
    if (*((unsigned char*)(&t)) == 0x78) {
	return b;
    } else {
	return ((b & 0xff00) >> 8) |
	       ((b & 0x00ff) << 8);
    }
}

unsigned char elf_header[] = {
    0x7f,  'E',  'L',  'F', 0x01, 0x01, 0x01, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // e_ident
    0x01, 0x00,                                     // e_type (relocatable)
    0x08, 0x00,                                     // e_machine (mips)
    0x01, 0x00, 0x00, 0x00,                         // e_version
    0x00, 0x00, 0x00, 0x00,                         // e_entry
    0x00, 0x00, 0x00, 0x00,                         // e_phoff
    0x34, 0x00, 0x00, 0x00,                         // e_shoff
    0x01, 0x40, 0x92, 0x20,                         // e_flags
    0x34, 0x00,                                     // e_ehsize
    0x00, 0x00,                                     // e_phentsize
    0x00, 0x00,                                     // e_phnum
    0x28, 0x00,                                     // e_shentsize
    0x05, 0x00,                                     // e_shnum
    0x01, 0x00,                                     // e_shstrndx
};

// 0 = NULL
// 1 = .shstrtab
// 2 = .symtab
// 3 = .strtab
// 4 = .data

struct elf_section_t {
    u32 sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size;
    u32 sh_link, sh_info, sh_addralign, sh_entsize;
};

struct elf_symbol_t {
    u32 st_name, st_value, st_size;
    u8 st_info, st_other;
    u16 st_shndx;
};

//                 0 0000000001 11111111 12222222 22233
//                 0 1234567890 12345678 90123456 78901
char shstrtab[] = "\0.shstrtab\0.symtab\0.strtab\0.data";

void create_elf(FILE * dest, const unsigned char * source, u32 size, const char * label) {
    int i, l_size;
    struct elf_section_t section;
    struct elf_symbol_t symbol;
    u32 strtab_size;
    char strtab[512];
    u32 data_size[4];
    
    if (have_irx) {
	elf_header[36] = 1;
	elf_header[37] = 0;
	elf_header[38] = 0;
	elf_header[39] = 0;
    }
    
    for (i = 0; i < sizeof(elf_header); i++) {
	fputc(elf_header[i], dest);
    }
    
    l_size = strlen(label);
    
    strtab[0] = 0;
    strcpy(strtab + 1, label);
    strcat(strtab + 1, "_start");
    strcpy(strtab + 1 + l_size + 7, label);
    strcat(strtab + 1 + l_size + 7, "_end");
    if (have_size) {
        strtab_size = (l_size * 3 + 1 + 7 + 5 + 6);
        strcpy(strtab + 1 + l_size + 7 + l_size + 5, label);
        strcat(strtab + 1 + l_size + 7 + l_size + 5, "_size");
    } else {
        strtab_size = (l_size * 2 + 1 + 7 + 5);
    }
    
    // section 0 (NULL)
    section.sh_name = section.sh_type = section.sh_flags = section.sh_addr = section.sh_offset = section.sh_size =
    section.sh_link = section.sh_info = section.sh_addralign = section.sh_entsize = 0;
    
    fwrite(&section, 1, sizeof(section), dest);
    
    
    // section 1 (.shstrtab)
    section.sh_name = LE32(1);
    section.sh_type = LE32(3); // STRTAB
    section.sh_flags = 0;
    section.sh_addr = 0;
    section.sh_offset = LE32(40 * 5 + 0x34);
    section.sh_size = LE32(33);
    section.sh_link = 0;
    section.sh_info = 0;
    section.sh_addralign = LE32(1);
    section.sh_entsize = 0;
    
    fwrite(&section, 1, sizeof(section), dest);
    
    
    // section 2 (.symtab)
    section.sh_name = LE32(11);
    section.sh_type = LE32(2); // SYMTAB
    section.sh_flags = 0;
    section.sh_addr = 0;
    section.sh_offset = LE32(40 * 5 + 0x34 + 33);
    section.sh_size = LE32(have_size ? 0x30 : 0x20);
    section.sh_link = LE32(3);
    section.sh_info = 0;
    section.sh_addralign = LE32(4);
    section.sh_entsize = LE32(0x10);
    
    fwrite(&section, 1, sizeof(section), dest);
    
    
    // section 3 (.strtab)
    section.sh_name = LE32(19);
    section.sh_type = LE32(3); // STRTAB
    section.sh_flags = 0;
    section.sh_addr = 0;
    section.sh_offset = LE32(40 * 5 + 0x34 + 33 + (have_size ? 0x30 : 0x20));
    section.sh_size = LE32(strtab_size);
    section.sh_link = 0;
    section.sh_info = 0;
    section.sh_addralign = LE32(1);
    section.sh_entsize = 0;
    
    fwrite(&section, 1, sizeof(section), dest);
    
    
    // section 4 (.data)
    section.sh_name = LE32(27);
    section.sh_type = LE32(1); // PROGBITS
    section.sh_flags = LE32(3); // Write + Alloc
    section.sh_addr = 0;
    section.sh_offset = LE32(40 * 5 + 0x34 + 33 + (have_size ? 0x30 : 0x20) + strtab_size);
    section.sh_size = LE32(size + have_size * 16);
    section.sh_link = 0;
    section.sh_addralign = LE32(alignment);
    section.sh_entsize = 0;
    
    fwrite(&section, 1, sizeof(section), dest);
    
    
    
    fwrite(shstrtab, 1, 33, dest);

    symbol.st_name = LE32(1);
    symbol.st_value = LE32(have_size * 16);
    symbol.st_size = LE32(size);
    symbol.st_info = 0x11;
    symbol.st_other = 0;
    symbol.st_shndx = LE16(4);
    fwrite(&symbol, 1, sizeof(symbol), dest);

    symbol.st_name = LE32(l_size + 1 + 7);
    symbol.st_value = LE32(size + have_size * 16);
    symbol.st_size = 0;
    symbol.st_info = 0x11;
    symbol.st_other = 0;
    symbol.st_shndx = LE16(4);
    fwrite(&symbol, 1, sizeof(symbol), dest);

    if (have_size) {
        symbol.st_name = LE32(l_size * 2 + 1 + 7 + 5);
        symbol.st_value = 0;
        symbol.st_size = LE32(4);
        symbol.st_info = 0x11;
        symbol.st_other = 0;
        symbol.st_shndx = LE16(4);
        fwrite(&symbol, 1, sizeof(symbol), dest);
    }

    fwrite(strtab, 1, strtab_size, dest);
    
    if (have_size) {
        data_size[0] = LE32(size);
        data_size[1] = 0;
        data_size[2] = 0;
        data_size[3] = 0;
        fwrite(data_size, 4, 4, dest);
    }
    fwrite(source, 1, size, dest);
}

void usage() {
    printf("bin2o - Converts a binary file into a .o file.\n"
           "Usage: bin2o [-a XX] [-n] [-i] [-b XX] [-e XX] [-s XX] infile outfile label\n"
	   "  -i    - create an iop-compatible .o file.\n"
	   "  -n    - don't add label_size symbol.\n"
	   "  -a XX - set .data section alignment to XX.\n"
	   "          (has to be a power of two).\n"
	   "  -b XX - start reading file at this offset.\n"
	   "  -e XX - stop reading file at this offset.\n"
	   "  -s XX - force output data to be of this size.\n"
	   "\n"
	  );
}

int main(int argc, char *argv[])
{
    u32 fd_size, start = 0, end = 0xffffffff, size = 0xffffffff;
    unsigned char * buffer;
    FILE * source = NULL, * dest = NULL;
    char * f_source = 0, * f_dest = 0, * f_label = 0;
    int i;

    for (i = 1; i < argc; i++) {
	if (argv[i][0] == '-') {
	    switch (argv[i][1]) {
	    case 'a':
		i++;
		if (!argv[i]) {
		    usage();
		    printf("-a requires an argument.\n");
		    return 1;
		}
		if (argv[i][0] == '-') {
		    usage();
		    printf("-a requires an argument.\n");
		    return 1;
		}
		alignment = atoi(argv[i]);
		if ((alignment - 1) & alignment) {
		    printf("Error: alignment must be a power of 2.\n");
		    return 1;
		}
		break;
	    case 'n':
		have_size = 0;
		break;
	    case 'i':
		have_irx = 1;
		break;
	    case 'b':
		i++;
		if (!argv[i]) {
		    usage();
		    printf("-b requires an argument.\n");
		    return 1;
		}
		if (argv[i][0] == '-') {
		    usage();
		    printf("-b requires an argument.\n");
		    return 1;
		}
		start = atoi(argv[i]);
		break;
	    case 'e':
		i++;
		if (!argv[i]) {
		    usage();
		    printf("-e requires an argument.\n");
		    return 1;
		}
		if (argv[i][0] == '-') {
		    usage();
		    printf("-e requires an argument.\n");
		    return 1;
		}
		end = atoi(argv[i]);
		break;
	    case 's':
		i++;
		if (!argv[i]) {
		    usage();
		    printf("-s requires an argument.\n");
		    return 1;
		}
		if (argv[i][0] == '-') {
		    usage();
		    printf("-s requires an argument.\n");
		    return 1;
		}
		size = atoi(argv[i]);
		break;
	    default:
		usage();
		printf("Unknow option: %s.\n", argv[i]);
		return 1;
	    }
	} else {
	    if (!f_source) {
		f_source = argv[i];
	    } else if (!f_dest) {
		f_dest = argv[i];
	    } else if (!f_label) {
		f_label = argv[i];
	    } else {
		usage();
		printf("Too many arguments.\n");
		return 1;
	    }
	}
    }
    
    if (!f_source || !f_dest || !f_label) {
	usage();
	printf("Not enough arguments.\n");
	return 1;
    }

    if (!(source = fopen(f_source, "rb"))) {
	printf("Error opening %s for reading.\n", f_source);
	return 1;
    }

    fseek(source, 0, SEEK_END);
    fd_size = ftell(source);
    fseek(source, start, SEEK_SET);
    
    if (fd_size < end)
	end = fd_size;
    
    if (end < (size - start))
	size = end - start;

    buffer = malloc(size);
    if (buffer == NULL) {
	printf("Failed to allocate memory.\n");
	fclose(source);
	free(buffer);
	return 1;
    }

    if (fread(buffer, 1, size, source) != size) {
	printf("Failed to read file.\n");
	fclose(source);
	free(buffer);
	return 1;
    }
    fclose(source);

    if (!(dest = fopen(f_dest, "wb+"))) {
	printf("Failed to open/create %s.\n", f_dest);
	fclose(dest);
	return 1;
    }
    
    create_elf(dest, buffer, size, f_label);

    fclose(dest);
    free(buffer);

    return 0;
}
