/*****************************************************************************
 * FILE: loader.c							     *
 *									     *
 * DESC: loader for 32bit RSX compiled with EMX/GCC			     *
 *									     *
 * Copyright (C) 1994							     *
 *	Rainer Schnitker, Heeper Str. 283, 33607 Bielefeld		     *
 *	email: rainer@mathematik.uni-bielefeld.de			     *
 *									     *
 * See the files "README" and "COPYING" for further copyright and warranty   *
 * information. 							     *
 *									     *
 *****************************************************************************/

#include <dos.h>
#include "LOADER.H"

struct info {
    unsigned long  membytes;
    unsigned long  memaddress;
    unsigned long  text_start;
    unsigned long  data_start;
    unsigned long  bss_start;
    unsigned long  stack_start;
    unsigned long  init_brk;
    unsigned long  entry;
    unsigned short code16sel;
    unsigned short data16sel;
} info;

extern unsigned _psp;

WORD code32sel; 		/* RSX CS selector */
WORD data32sel; 		/* RSX DS selector */
DWORD stackp32; 		/* RSX satck pointer */

static void far *extdata;	/* DOS memory for RSX */
static unsigned segm;		/* segment address */
static char far * env_str;	/* env far pointer */
static char cline[260]; 	/* arguments */

static int n_strlen(char *str)
{
    char *s;
    for (s = str; *s; ++s);
    return (s - str);
}

static int f_strlen(char far *str)
{
    char far *s;
    for (s = str; *s; ++s);
    return ((int)(char near *)s - (int)(char near *)str);
}

static char *my_strcpy(char *string1, char *string2)
{
    char *dst = string1;
    while ((*dst = *string2) != 0)
	++dst, ++string2;
    return (string1);
}

static void error(char *s)
{
    dos_puts("loader v1.0 : (c) Rainer Schnitker\n\r$");
    dos_puts("rsx_32 loader error: $");
    dos_puts(s);
}

static int skip_exe_hdr(int filehandle, DWORD * headoff)
{
    struct exe_hdr exehdr;

    exehdr.signatur = 0;
    dos_read(filehandle, &exehdr, sizeof(WORD) * 3);

    if (exehdr.signatur == 0x5a4d) {	/* falls exe-kopf */
	*headoff += (DWORD) exehdr.high * 512L;
	if (exehdr.low)
	    *headoff += (DWORD) exehdr.low - 512L;
    }
    if (dos_lseek(filehandle, *headoff, SEEK_SET) != -1L)
	return 0;
    else {
	*headoff = 0;
	dos_lseek(filehandle, 0, SEEK_SET);
	return -1;
    }
}

static int argvenv(int argc, char **argv)
{
    DWORD far * vectors;	    /* building argv, env    */
    WORD len;			    /* current len of string */
    WORD stkp;			    /* current stack pos     */
    WORD count = 3;		    /* 0=argc 1=argv 2=env   */
    int i, envc = 0;
    static char npx_str[] = "RSX_x87=?";

    /* instead allocate memory, take some EMX space at DS:0x8000 */
    vectors = (DWORD far *) (((DWORD)segm << 16) | 0x8000);

    /* EMX program stack pointer */
    /* stack grows down from DS:0xFFFC */
    stkp = (WORD) stackp32;
    FP_SEG(extdata) = segm;
    FP_OFF(extdata) = stkp;

    /* store env strings in user stack, built vectors */
    for (; *env_str; ) {
	len = f_strlen(env_str) + 1;
	stkp -= len;
	stkp &= ~3;
	FP_OFF(extdata) = stkp;
	far_memcpy(extdata, env_str, len);
	vectors[count++] = (DWORD) stkp;
	env_str += len;
	envc ++;
    }
    /* put 387 status on env */
    i = npx_installed();
    npx_str[8] = (char) i + '0';
    len = sizeof(npx_str) + 1;
    stkp -= len;
    stkp &= ~3;
    FP_OFF(extdata) = stkp;
    far_memcpy(extdata, (char far *)npx_str, len);
    vectors[count++] = (DWORD) stkp;
    env_str += len;
    envc ++;

    vectors[count++] = 0L;	 /* last is a NULL pointer */

    /* store arg strings in user stack, built vectors */
    for (i = 0; i < argc; i++) {
	len = f_strlen(argv[i]) + 1;
	stkp -= len;
	stkp &= ~3;
	FP_OFF(extdata) = stkp;
	far_memcpy(extdata, (void far *) argv[i], len);
	vectors[count] = (DWORD) stkp;
	count++;
    }
    vectors[count++] = 0L;	 /* last is a NULL pointer */

    len = count * sizeof(long);
    stkp -= len;
    vectors[0] = argc;
    vectors[1] = stkp + (4 + envc) * sizeof(long);  /* & vectors[3+nenvp+1] */
    vectors[2] = stkp + 3 * sizeof(long);	    /* & vectors[3] */
    FP_OFF(extdata) = stkp;
    far_memcpy(extdata, vectors, len);

    stackp32 = stkp;		    /* save current pos! (for entry) */
    stackp32 += 3 * sizeof(long);
    return 0;
}


static int read_in(int fhandle, DWORD address, long size)
{
#define READ_MAX 0xFF00

    void far *dosmemp;
    WORD word_size;

    FP_OFF(dosmemp) = 0;

    while (size != 0L) {
	FP_SEG(dosmemp) = (WORD) (address >> 4);
	word_size = (size >> 16) ? READ_MAX : (WORD) size;
	dos_read_far(fhandle, dosmemp, word_size);
	address += (DWORD) READ_MAX;
	size -= (DWORD) word_size;
    }

    return 0;
}

static int load_protected_program(char *filename)
{
    GNUOUT aout_hdr;
    DWORD sizetext, sizedata, sizestack;
    DWORD headoff;
    int fhandle;

    if ((fhandle = dos_open(filename, DO_RDONLY | DO_DENYWR)) == -1) {
	error("open rsx32 error\r\n$");
	return 1;
    }

    headoff = 0;
    skip_exe_hdr(fhandle, &headoff);

    /* read gnu aout header */
    dos_read(fhandle, &aout_hdr, sizeof(aout_hdr));

    /* test header */
    if ((WORD) aout_hdr.a_info != 0x10b) {
	error("illegal a.out header\r\n$");
	dos_close(fhandle);
	return 2;
    }

    sizetext = (aout_hdr.a_text + SEGMENT_SIZE - 1L) & ~(SEGMENT_SIZE - 1L);
    sizedata = aout_hdr.a_data + ((aout_hdr.a_bss + 4095L) & ~4095L);
    sizestack = 64 * 1024L;

    info.text_start = N_TXTADDR(x);
    info.data_start = info.text_start + sizetext;
    info.bss_start = info.data_start + aout_hdr.a_data;

    info.membytes = sizestack + sizetext + sizedata;
    info.entry = aout_hdr.a_entry;
    info.init_brk = info.data_start + sizedata;
    stackp32 = info.text_start - 4L;

    /* MEMORY per DOS besorgen */
    if (!DosReallocParagraph(_psp, (WORD)(info.membytes>>4))) {
	error("realloc memory error\r\n$");
	return 3;
    }
    segm = _psp;

    info.memaddress = (DWORD)segm << 4;

    /* read in code */
    dos_lseek(fhandle, headoff + N_TXTOFF(aout_hdr), SEEK_SET);
    if (read_in(fhandle, info.memaddress + info.text_start, aout_hdr.a_text))
	return 1;

    /* read in data */
    dos_lseek(fhandle, headoff + N_DATOFF(aout_hdr), SEEK_SET);
    if (read_in(fhandle, info.memaddress + info.data_start, aout_hdr.a_data))
	return 1;

    dos_close(fhandle);

    /* zero bss segment */
    if (aout_hdr.a_bss) {
	FP_SEG(extdata) = (WORD) ((info.memaddress + info.bss_start) >> 4);
	FP_OFF(extdata) = (WORD) ((info.memaddress + info.bss_start) & 0xFL);
	far_bzero(extdata, (WORD) aout_hdr.a_bss);
    }
    return 0;
}

static int real_to_protected(WORD mode)
{
    static WORD DPMIdata_para_needed;
    static WORD DPMIdata_segm_address;
    WORD DPMIflags, DPMIversion;
    BYTE processor;
    DWORD PM_jump;		/* switch to protmode jump */

    if (GetDpmiEntryPoint(&PM_jump, &DPMIdata_para_needed,
			  &DPMIflags, &DPMIversion, &processor)) {
	error("No DPMI-host found!\r\n$");
	return -1;
    }
    if (mode == 1 && !(DPMIflags & 1)) {
	error("32bit programs not supported by Host\r\n$");
	return -1;
    }
    if (DPMIdata_para_needed) { /* get DPMI ring 0 stack */
	DPMIdata_segm_address = GetDpmiHostParagraph(DPMIdata_para_needed);
	if (!DPMIdata_segm_address) {
	    error("Can't alloc memory for the DPMI-host-stack\r\n$");
	    return -1;
	}
    }
    if (DpmiEnterProtectedMode(PM_jump, mode, DPMIdata_segm_address)) {
	error("can't switch to Protected Mode\r\n$");
	return -1;
    }

    if (DPMIdata_para_needed)
	LockLinRegion((DWORD) DPMIdata_segm_address << 4,
		      (DWORD) DPMIdata_para_needed << 4);

    /* Now we are in Protected Mode */
    info.code16sel = GetCS();
    info.data16sel = GetDS();

    return 0;
}

static void init_descriptors()
{
    AllocLDT(2, &(code32sel));
    data32sel = code32sel + SelInc();

    SetBaseAddress(code32sel, info.memaddress);
    SetBaseAddress(data32sel, info.memaddress);
    SetAccess(code32sel, APP_CODE_SEL, DEFAULT_BIT | GRANULAR_BIT);
    SetAccess(data32sel, APP_DATA_SEL, DEFAULT_BIT | GRANULAR_BIT);
    SetLimit(code32sel, info.membytes - 1);
    SetLimit(data32sel, info.membytes - 1);
}

static void get_cmdline_from_psp(unsigned psp_segm)
{
    int z, env_seg;
    char far *cmdl;

    /* get envoronment segment from PSP */
    env_seg = *(int far *) (((DWORD) psp_segm << 16) | 0x2c);

    /* build far pointer to environment */
    cmdl = (char far *) ((DWORD) env_seg << 16);

    /* save environment pointer */
    env_str = (char far *) ((DWORD) env_seg << 16);

    /* skip env-strings ; last has two 0-bytes */
    for (;;) {
	if (*cmdl == '\0' && *(cmdl+1) == '\0')
	    break;
	cmdl++;
    }

    cmdl += 4;
    /* copy arg0 */
    for (z = 0; z < sizeof(cline); z++) {
	cline[z] = *cmdl;
	if (cline[z] == '\0')
	    break;
	cmdl++;
    }
    cline[z++] = ' ';

    /* build cmdline far pointer from PSP */
    cmdl = (char far *) ((DWORD) psp_segm << 16 | 0x81);

    /* copy arg1,arg2,... */
    for ( ; z < sizeof(cline) ; z++) {
	cline[z] = *cmdl;
	if (cline[z] == 13)
	    break;
	cmdl++;
    }
    cline[z] = '\0';
}

static void build_args(int *argn, char ***argvp, int stub_opt)
{
    static char extra[] = "!RSX";
    static char *argvec[20];

    int argc, src, dst, bs, quote;
    char *q;

    argc = 0;
    dst = src = 0;

    if (stub_opt)
	argvec[argc++] = extra;

    while (cline[src] == ' ' || cline[src] == '\t' || cline[src] == '\n')
	++src;
    do {
	if (cline[src] == 0)
	    q = NULL;
	else {
	    q = cline + dst;
	    bs = 0;
	    quote = 0;
	    for (;;) {
		if (cline[src] == '"') {
		    while (bs >= 2) {
			cline[dst++] = '\\';
			bs -= 2;
		    }
		    if (bs & 1)
			cline[dst++] = '"';
		    else
			quote = !quote;
		    bs = 0;
		} else if (cline[src] == '\\')
		    ++bs;
		else {
		    while (bs != 0) {
			cline[dst++] = '\\';
			--bs;
		    }
		    if (cline[src] == 0 ||
			((cline[src] == ' ' || cline[src] == '\t') && !quote))
			break;
		    cline[dst++] = cline[src];
		}
		++src;
	    }
	    while (cline[src] == ' ' || cline[src] == '\t'
		   || cline[src] == '\n')
		++src;
	    cline[dst++] = 0;
	}
	argvec[argc++] = q;
    } while (q != NULL);

    *argn = argc - 1;
    *argvp = (char **) &(argvec[0]);
}

int main(void)
{
    int argc;
    char **argv;
    char exe_name[130];

    get_cmdline_from_psp(_psp);
    build_args(&argc, &argv, 0);
    my_strcpy(exe_name, argv[0]);

    if (argc == 2 && argv[1][0]=='-' && argv[1][1] == '/' && argv[1][6] == '/') {
	unsigned newpsp;
	int i;
	char s;
	newpsp = 0;
	for ( i=2 ; i<=5 ; i++) {
	    s = argv[1][i];
	    if (s >= 'A')
		s -= ('A' - 10);
	    else
		s -= '0';
	    newpsp <<= 4;
	    newpsp |= s;
	}
	get_cmdline_from_psp(newpsp);
	build_args(&argc, &argv, 1);
    }

    if (argc > 7 && argv[1][0]=='!' && argv[1][1] == 'p' && argv[1][5] == 'y') {
	unsigned newpsp;
	int i;
	char s;
	newpsp = 0;
	for ( i=0 ; i<=3 ; i++) {
	    s = argv[7][i];
	    if (s >= 'a')
		s -= ('a' - 10);
	    else if (s >= 'A')
		s -= ('A' - 10);
	    else
		s -= '0';
	    newpsp <<= 4;
	    newpsp |= s;
	}
	get_cmdline_from_psp(newpsp);
	build_args(&argc, &argv, 1);
    }

    if (load_protected_program(exe_name))
	return(1);

    if (argvenv(argc, argv))
	return (1);

    if (real_to_protected(1))
	return (1);

    init_descriptors();
    jmp_to_user();
    return 0;
}
