/*

    RSXOPT.C - patches options in the Win32 executable

    Copyright (C) 1995-1996
	Rainer Schnitker, Heeper Str. 283, 33607 Bielefeld
	email: rainer@mathematik.uni-bielefeld.de

    All rights reserved

*/

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include "ntbind.h"
#include "port.h"

struct layout
{
    DWORD text_base;		  /* Base address of the text segment */
    DWORD text_end;		  /* End address of the text segment */
    DWORD data_base;		  /* Base address of initialized data */
    DWORD data_end;		  /* End address of initialized data */
    DWORD bss_base;		  /* Base address of uninitialized data */
    DWORD bss_end;		  /* End address of uninitialized data */
    DWORD heap_base;		  /* Base address of the heap */
    DWORD heap_end;		  /* End address of the heap */
    DWORD heap_brk;		  /* End address of the used heap space */
    DWORD heap_off;		  /* Location of heap data in the executable */
    DWORD os2_dll;		  /* Address of the import table for (I1) */
    DWORD stack_base;		  /* Base address of the stack */
    DWORD stack_end;		  /* End address of the stack */
    DWORD flags;		  /* Flags (DLL) and interface number */
    DWORD reserved[2];		  /* Not yet used */
    unsigned char options[64];	  /* emx options to be used under OS/2 */
};

/* return first non-digit */
static char * asc2long(char *s, unsigned long *retv)
{
    char *str = s;
    *retv = 0;

    while (*str != 0) {
	if ((*str < '0') || (*str > '9'))
	    break;
	*retv *= 10;
	*retv += *str - '0';
	str++;
    }
    return (str);
}

static char *scan_for_option(char *s)
{
    unsigned long temp;

    switch (*s) {

    case 'c':                   /* core */
	puts("- disable core dumps");
	break;

    case 'e':                   /* redirect standard error */
	puts("- redirect stderr to stdout");
	break;

    case 'h':                   /* max handles */
	++s;
	if (!isdigit(*s))
	    return NULL;
	s = asc2long(s, &temp);
	printf("- limit of handles (win32s) = %ld\n", temp);
	return s;

    case 'q':                   /* quote all arguments to child */
	puts("- quote all arguments to childs");
	break;

    case 's':                   /* heap */
	++s;
	if (!isdigit(*s))
	    return NULL;
	s = asc2long(s, &temp);
	printf("- max. heap size = %ld KB\n", temp);
	return s;

    case 'r':                   /* default disk-drive */
	if (!isalpha(*++s))
	    return NULL;
	printf("- prepend drive '%c'\n", *s);
	break;

    case 't':                   /* truncate filenames to 8.3 */
	puts("- truncate files to 8.3 format");
	break;

    case 'V':                   /* enables VT100/ANSI */
        puts("- enables VT100/ANSI emulation");
	break;

    default:
	return NULL;

    } /* switch (*s) */

    return s;
}

static void display_options(char *s)
{
    char *t;

    if (! *s) {
	puts("File has no options");
	return;
    }

    printf("File contains options: '%s'\n", s);

    for (; *s != '\0'; ++s) {
	while (*s == ' ')
	    ++s;
	if (*s == '-') {
	    t = scan_for_option(++s);
	    if (t == NULL) {
		printf("** error in rsxnt options : %s **\n", --s);
		return;
	    }
	} else
	    break;
	s = t;
    }
}

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

    read(filehandle, &exehdr, sizeof(struct exe_hdr));

    if (exehdr.signatur == 0x5a4d) {	/* falls exe-kopf */
	DWORD new_off;

	lseek(filehandle, 0x3C, SEEK_SET);
	read(filehandle, &new_off, sizeof(long));

	if (new_off) {
	    DWORD pe_magic;
	    lseek(filehandle, new_off, SEEK_SET);
	    read(filehandle, &pe_magic, sizeof(pe_magic));
	    if (pe_magic == 0x4550) {
		*headoff = new_off + 4;
		return 0;
	    }
	}
    }	/* exe files */
    *headoff = 0;
    return -1;
}

static int do_options(char * name, char option, char **argv)
{
    FILEHDR	    file_hdr;
    NTOPTHDR	    ntopt_hdr;
    SCNHDR	    scn_hdr;
    struct layout   layout;
    DWORD	    headoff;
    int             mode, i;
    int 	    pehandle;

    if (option == 'i')
        mode = O_BINARY | O_RDONLY;
    else
	mode = O_BINARY | O_RDWR;

    if ((pehandle = open(name, mode)) < 0) {
	perror("open");
	return 1;
    }
    if (skip_exe_hdr(pehandle, &headoff) < 0) {
	puts("no a valid Win32 file");
	return (1);
    }

    lseek(pehandle, headoff, SEEK_SET);
    read(pehandle, &file_hdr, sizeof(FILEHDR));

    if (file_hdr.f_magic != 0x14C) {
	puts("no COFF header found");
	return (1);
    }

    read(pehandle, &ntopt_hdr, sizeof(NTOPTHDR));

    if (ntopt_hdr.Magic != 0x10b) {
	puts("no optional header found");
	return (1);
    }

    for (i = 0; i < file_hdr.f_nscns; i++) {
	read(pehandle, &scn_hdr, sizeof(SCNHDR));

	if (strcmp(scn_hdr.s_name, ".data") == 0)
	    break;
    }

    lseek(pehandle, scn_hdr.s_scnptr, SEEK_SET);
    read(pehandle, &layout, sizeof (struct layout));

    if (layout.text_end != layout.data_base &&
	layout.data_end != layout.bss_base) {
	puts("This file is not created with GCC+RSXNT");
	close(pehandle);
	return 1;
    }

    if (layout.options[0] != '-')
	layout.options[0] = 0;

    if (option == 'i')
	display_options(layout.options);
    else {
	int i;
	char *s = layout.options;

	if (option == 'a') {
	    s += strlen(s);
	    *s++ = ' ';
	}
        if (option != 'd') {
            for (i = 3; argv[i]; i++) {
                strcpy(s, argv[i]);
                s += strlen(s);
                *s++ = ' ';
            }
            if (i > 3)
                *--s = '\0';
        } else
            memset (layout.options, 0, sizeof(layout.options));

	lseek(pehandle, scn_hdr.s_scnptr, SEEK_SET);
        write(pehandle, &layout, sizeof (struct layout));
    }
    close(pehandle);
    return 0;
}

static void usage(void )
{
    puts("usage: rsxntopt [-a -b -i] file.exe [options]");
    puts("\t-a: append options");
    puts("\t-b: build new options");
    puts("\t-d: delete options");
    puts("\t-i: info about options");

    puts("\t\noptions:");
    puts("\t-c  : disable core dumps");
    puts("\t-e  : redirect stderr to stdout");
    puts("\t-h# : limit of handles (use for win32s, default 40)");
    puts("\t-q  : quote all arguments to childs");
    puts("\t-s# : max. heap size of process");
    puts("\t-r* : prepend drive * to path names");
    puts("\t-t  : truncate files to 8.3 format");
    puts("\t-V  : enables VT100/ANSI emulation");
}

int main(int argc, char **argv)
{
    puts("RSXNTOPT - bind options in exefile ; (c) Rainer Schnitker");

    if (argc < 3) {
	usage();
	return 1;
    }

    if ((strcmp(argv[1],"-i") != 0)
        && (strcmp(argv[1],"-a") != 0)
        && (strcmp(argv[1],"-b") != 0)) {

	usage();
	return 1;
    }

    return do_options(argv[2], argv[1][1], argv);
}
