/*  DOSRUN, main.c                                         *
 *  (C)1999 LGB (Gabor Lenart), lgb@vlug.vein.hu           *
 *  Free software under the terms of GNU/GPL v2 or later   */


#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "config.h"
#include "dosrun.h"

#include "memory.h"
#include "derrno.h"
#include "loader.h"
#include "xms.h"

#include "main.h"


struct vm86_struct vm;
static int in_vm86=0;
int exitcode=0;
extern char **environ;
static int intno=-1;
static word oldcs,oldip;
word *oldflagsp;

int derrno;
FILE *stddebug;


/* yes, ugly */
static inline int my_vm86 ( struct vm86_struct *vm )
{
  int r;
  in_vm86=1;
#ifdef __PIC__
  asm volatile (
  "pushl %%ebx\n\t"
  "movl %2, %%ebx\n\t"
  "int $0x80\n\t"
  "popl %%ebx"
  : "=a" (r)
  : "0" (113), "r" (vm));
#else
  asm volatile (
  "int $0x80"
  : "=a" (r)
  : "0" (113), "b" (vm));
#endif
  in_vm86=0;
  return r;
}


void dump_address ( int seg , int ofs )
{
  byte *p;
  int a;
  fprintf(stderr,"CS:EIP=%04X:%08X DS=%04X ES=%04X EBP=%08X              FS=%04X\n",seg,ofs,DS,ES,EBP,FS);
  fprintf(stderr,"SS:ESP=%04X:%08X EFLAGS=%08X ESI=%08X EDI=%08X GS=%04X\n",SS,ESP,EFLAGS,ESI,EDI,GS);
  fprintf(stderr,"EAX=%08X         EBX=%08X    ECX=%08X EDX=%08X\n",EAX,EBX,ECX,EDX);
  p=(byte*)(seg<<4)+ofs;
  fprintf(stderr,"Code:");
  for (a=0;a<16;a++) fprintf(stderr," %02X",*(p++));
  fprintf(stderr,"\n\n");
}


inline void dump ( void )
{
  dump_address(CS,IP);
}


static inline void pushw ( word w )
{
  SP-=2;
  *DOS_PW(SS,SP)=w;
}



static inline int emu ( void )
{
  int ret;
  intno=-1;
  ret=my_vm86(&vm);
//  fflush(stderr);
  switch (VM86_TYPE(ret)) {
    case VM86_SIGNAL  : fprintf(stderr,"VM86: VM86_SIGNAL return value=%d\n",VM86_ARG(ret));
                        dump();
			return 0;
    case VM86_UNKNOWN : return inst_emu();
    case VM86_INTx    : intno=VM86_ARG(ret);
/*			if (CS!=EMU_INT_SEG) {
  			  if (stddebug) fprintf(stddebug,"Int SIMULATE %02Xh at %04X:%04X.\n",intno,CS,IP);
     			  pushw(FLAGS);
			  pushw(CS);
			  pushw(IP);
			  oldflagsp=DOS_PW(SS,SP);
			  oldcs=CS;
			  oldip=IP;
			  EFLAGS&=~(VIF_MASK|TF_MASK);
			  IP=*(word*)(intno<<2);
			  CS=*(word*)((intno<<2)+2);
			  returrn 1;
			}  */
#ifdef CONFIG_XMS			  
   		        if (DOS_PA(CS,IP)==DOS_PA(XMS_PROC_SEG,XMS_PROC_OFS)+2) return xms_emu();
#endif
/*                        if (stddebug) fprintf(stddebug,"Int REDIRECT %02Xh (AH=%02Xh) at %04X:%04X\n",intno,AH,CS,IP);
			intno=(DOS_PA(CS,IP)-2-DOS_PA(EMU_INT_SEG,EMU_INT_OFS))>>2;
                        if (stddebug) fprintf(stddebug,"Int PROCESS %02Xh (AH=%02Xh) at %04X:%04X\n",intno,AH,CS,IP); */
			ret=0;
                        switch (intno) {
			  case 0x10 : ret=int10_emu();
			              break;
			  case 0x12 : AX=640;    /* 640K ram below 1M */
			              ret=1;
				      break;
#ifdef CONFIG_INT15
			  case 0x15 : ret=int15_emu();
			              break;
#endif
#ifdef CONFIG_INT16
			  case 0x16 : ret=int16_emu();
			              break;
#endif
                          case 0x20 : fprintf(stderr,"Exit on int 20h\n");
			              ret=0;
				      break;
			  case 0x21 : ret=int21_emu();
			              break;
			  case 0x29 : ret=1;
			              putchar(AL);
				      fflush(stdout);
			              break;
#ifdef CONFIG_INT2F
			  case 0x2f : ret=int2f_emu();
			              break;
#endif
                          default   :
#ifdef CONFIG_DEBUG_UNIMPLEMENTED_INT
     			              fprintf(stderr,"INT: unimplemented interrupt int %02Xh\n",intno);
			              dump_address(CS,IP-2);
#endif
                                      ret=1;
                                      break;

			} /* switch */
			if (ret) { 
//			  *oldflagsp=FLAGS;
			  return ret;
			} else return 0;
    case VM86_STI     : fprintf(stderr,"VM86: VM86_STI return value\n");
                        dump();
                        return 1;
  } /* switch */
  fprintf(stderr,"VM86: !! unknown VM86 return value\n");
  dump();
  return 0;
}


static void sighandler ( int signum )
{
  fprintf(stderr,"Signal %d received in %s mode.\n",signum,in_vm86?"vm86":"normal");
  if (in_vm86) dump();
  exit(1);
}


inline static void init_sighandler ( void )
{
  int a;
  for (a=0;a<32;a++) signal(a,sighandler);
}


inline static int execute ( int argc , char ** argv , char ** environ )
{
  int addr=load_prg(argv[1],NULL,NULL);
  if (addr==0) {
    fprintf(stderr,"Error loading executable %s.\n",argv[1]);
    exit(1);
  }
/*  if (config.chdir_path&&chdir(config.chdir_path)) {
    fprintf(stderr,"directory=\"%s\", ",config.chdir_path);
    perror("chdir()");
    exit(1);
  } */
  while (emu());
  dosmem_dump();    /* see MCB list at the end for debugging */
  return exitcode;
}


/* At first I wanted to disable the function to hook an interrupt vector by a DOS
   application because it used by viruses and TSR programs mainly. The first
   reason is clear, the second as well : dosrun does not support TSR programs.
   dosrun is configured to return from vm86 mode on interrupt calling so there is
   no way for a DOS application to hook an interrupt vector. But you may need
   this function anyway so a new configuration time option - RAW_INT - was
   introduced. In that mode, dosrun will return only at a specified interrupt
   which is used to produce a gateway between vm86 and Linux native mode.
   The table only contains int <num> iret <dummy> sequences, where <num> is
   the interrupt gateway number and <dummy> is a padding byte to get 4 byte
   per interrupt. On system initialization the interrupt table contains pointers
   to the elements of this table. Since only the gateway interrupt returns
   from vm86 mode, DOS programs may hook interrupt vectors. When returning
   from vm86 mode, dosrun can get the interrupt number from CS:IP with the
   information of the starting address of our table and the size of an
   element (4 bytes). This method looks ugly but I think this is the fastest
   one (of course, not using RAW_INT option is faster). The disadvantage is
   the problem when a DOS application wants to use our gateway interrupt
   for its own purposes. dosrun can detect that CS:IP is in the boundaries
   of our table but if not, it pass back the controll into vm86 mode,
   which is must slower than the simple method. But using "the right" gateway
   interrupt is a right think TM.
*/


inline static void init_int ( void )
{
  int a;
  word *w=NULL;
  byte *b=DOS_PB(EMU_INT_SEG,EMU_INT_OFS);
  for (a=0;a<256;a++) {
    *(w++)=EMU_INT_OFS+(a<<2);
    *(w++)=EMU_INT_SEG;
    *(b++)=0xCD;         /* INT */
    *(b++)=EMU_INT;      /* #EMU_INT */
    *(b++)=0xCF;         /* IRET */
    *(b++)=0xFF;         /* padding byte */
  }
  memset(&vm.int_revectored,255,32);
}




int main ( int argc , char **argv )
{
  int r=0;
  fprintf(stderr,"DOSRUN pre0.50 (C)1999 Arpi & LGB\n\n");
  if (!getuid()||getuid()!=geteuid()) {
    fprintf(stderr,"DO NOT run dosrun as root or with (neither with not root) setuid bit !!\n");
    exit(1);
  }
  r=options_parser(argc,argv);
  if (r<0) exit(-r);
  stddebug=fdopen(255,"w");
//  stddebug=NULL;
  init_sighandler();
  if (argc<2) {
    fprintf(stderr,"Usage: %s <prg>\n",argv[0]);
    exit(1);
  }
  dosmem_init(0x400,640*1024);  /* hmmm ... we use memory after int table */
#ifdef CONFIG_XMS
  if (xms_init(XMS_SIZE<<10,XMS_HANDLES)) {
    fprintf(stderr,"Cannot initialize XMS emulation.\n");
    exit(1);
  }
#endif
  bzero(&vm,sizeof(vm));
  init_int();
  vm.cpu_type=CPU_386;
  EFLAGS=IF_MASK|IOPL_MASK;
  return execute(argc,argv,environ);
}
