/*
  Everyone else is writing i80x86 virtual machines, why not me too?
    -- rkeene [010719991500] rkeene@suspend.net
           rkeene (Roy Keene),   rkeene@suspend.net
help from, saurik (Jay Freeman), saurik@saurik.com
and        #asm on the IRC network of EFnet.

Lets see..

doopcodereal()		Used to execute an opcode (or group of opcodes) in real mode.
opcode*()		Used to execute a specific opcode.

writemembyte()		Used to write to an area of memmory.
readmembyte()		Used to read an area of memmory.
changeval()		Change two parts of a register.
getval()		Put two parts of a register together.
changevalext()		Change three parts of a register.

Partially implemented opcodes  [opcode*()]:
0x00, 0x0f, 0xe4

Fully implemented opcodes  [opcode*()]:
0x04, 0x05, 0x06, 0x14, 0x15, 0x24, 0x25, 0x27, 0x2f, 0x37, 0x3f, 0x50, 0x8b, 
0x98, 0xb8, 0xbe, 0xbf, 0xd4, 0xd5, 0xf3, 0xf5, 0xf7, 0xf8, 0xfc

*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define VERSION "0.01"
/* 
   Default to 4.0megs of RAM.
*/
#ifndef RAMSIZE
#define	RAMSIZE 4194304
#endif

/* General Purpose regs */
unsigned char	ah=0;
unsigned char 	bh=0;
unsigned char 	ch=0;
unsigned char 	dh=0;
unsigned char	al=0;
unsigned char	bl=0;
unsigned char	cl=0;
unsigned char	dl=0;
int	ip=0;
int	si=0;
int	di=0;
int	bp=0;
int	sp=0;
int 	ax=0;
int	bx=0;
int	cx=0;
int	dx=0;
long	eax=0;
long 	ebx=0;
long	ecx=0;
long	edx=0;
long	esi=0;
long	edi=0;
long	ebp=0;
long	esp=0;
long	eip=0;

/* Flags */
int	af=0;
int	cf=0;
int	df=0;
int	IF=0;
int	eflags=         0x00000000;
int	eflags_reset=	0x00000002;
int	eflags_id=	0x00200000;
int	eflags_vip=	0x00100000;
int	eflags_vif=	0x00080000;
int	eflags_ac=	0x00040000;
int	eflags_vm=	0x00020000;
int	eflags_rf=	0x00010000;
int	eflags_nt=	0x00004000;
int	eflags_iopl3=	0x00003000;
int	eflags_iopl2=	0x00002000;
int	eflags_iopl1=	0x00001000;
int	eflags_iopl0=	0x00000000;
int	eflags_of=	0x00000800;
int	eflags_di=	0x00000400; 
int     eflags_if=	0x00000200;
int	eflags_tf=	0x00000100;
int	eflags_sf=	0x00000080;
int	eflags_zf=	0x00000040;
int	eflags_af=	0x00000010;
int	eflags_pf=	0x00000004;
int	eflags_cf=	0x00000001;

/* Segment Regs */
long	cs=0;
long	ss=0;
long	ds=0;
long	es=0;
long	fs=0;
long	gs=0;

/* Table Regs */
int	gdtr=0;
int	idtr=0;
int	ldtr=0;
int	tr=0;

/* Control regs */
int 	cr0=0;
int 	cr1=0;
int 	cr2=0;
int 	cr3=0;
int 	cr4=0;
int 	cr5=0;
int 	cr6=0;
int 	cr7=0;

/* Debug Regs */
int	dr0=0;
int	dr1=0;
int	dr2=0;
int	dr3=0;
int	dr4=0;
int	dr5=0;
int	dr6=0;
int	dr7=0;

/* Program Variables */
long	*retptr[8][3];
#if (defined GO32) || (defined WIN32)
char	biosfile[255]="bios.ram";
#else
char	biosfile[255]="io/VGABIOS-elpin-2.20";
#endif
#if (USEFILE==1)
FILE	*memfd;
char	memfile[255]="io/vmi586.mem";
#else
unsigned char	memarray[RAMSIZE];
#endif
int	stack[8192];
void	(*(regs[255]))();
unsigned long absaddr;
#ifdef CHECK_INSTRUCTIONS
char	INSTRUCTION[256]={1,0,0,0,1,1,1,0,0,0,0,
	0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,
	0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,
	1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,1,0,0,
	0,1,0,0,0};
#endif


/*
   Remember to pass the locations to ext, high, and low.
   mesh==1 if setting ax,ah,al
   mesh==0 if setting eax
*/
void changevalext(unsigned long *ext, int *high, int *low, unsigned int nval, int mesh)
{
  unsigned int tmpv=0;
#if (DEBUG>=1)
  printf("changeval(%i, %i, %i, %i)\n called",(unsigned int) *ext,*high,*low,nval);
#endif
  tmpv=nval;
  tmpv-=((tmpv>>16)<<16);
  *high=tmpv>>8;
  tmpv-=(*high<<8);
  *low=tmpv;
  if (mesh==0) { *ext=nval; } else { *ext=((*ext>>16)<<16)+(*high<<8)+(*low); }
  return;
}

/*
   Pass memlocs.
*/
void changeval(int *high, int *low, int nv)
{
  unsigned int tmpv=0;
#if (DEBUG>=1)
  printf("changeval(%i, %i, %i)\n called",*high,*low,nv);
#endif
  tmpv=nv;
  *high=tmpv>>8;
  tmpv-=(*high<<8);
  *low=tmpv;
  return;
}

/*
   Assemble high and low bytes into a word.
*/
int getval(int high, int low)
{
  return((high<<8)+(low));
}

/*
  A standard interface to memmory, very slow for now (1byte at a time).
  Probably will be changed later, joy.
*/
void writemembyte(unsigned long location, int value)
{
#if (DEBUG>=3)
  printf("Writing %c(%i) to %i\n",value,value,(unsigned int) location); 
#endif
#if (USEFILE==1)
  fseek(memfd,location,SEEK_SET);
  fputc(value,memfd);
#else
  memarray[location]=value;
#endif
}

/*
  Read memmory the standard way.
*/
unsigned int readmembyte(unsigned long location)
{
#if (DEBUG>=3)
  printf("Reading byte from %i, ",(unsigned int) location);
#endif
#if (USEFILE==1)
  fseek(memfd,location,SEEK_SET);
#if (DEBUG>=3)
  printf("(%i)\n",fgetc(memfd));
  fseek(memfd,location,SEEK_SET);
#endif
  return(fgetc(memfd));
#else
#if (DEBUG>=3)
  printf("(%i)\n",memarray[location]);
#endif
  return (memarray[location]);
#endif
}

void doopcodereal(unsigned int bcs, unsigned long offset, long length)
{
  unsigned int opcodeval=0;
  cs=bcs;
  for (ip=offset;ip<length;ip++)
  {
    absaddr=((cs<<16)+ip);
    opcodeval=readmembyte(absaddr);
#ifdef CHECK_INSTRUCTIONS
    if (INSTRUCTION[opcodeval]!=1) { 
#if (DEBUG>=1)
      printf("Opcode not implemented (%i)\n",opcodeval); 
#endif
    } else { 
      (regs[opcodeval])(); 
    }
#else
    (regs[opcodeval])();
#endif
  }
}

/*
	#de		0	divide error
	#db		1	debug
	#bp		3	breakpoint
	#of		4	overflow
	#br		5	BOUND overflow
	#ud		6	undefined opcode
	#nm		7	no math coprossor
	#df		8	double fault
	#ts		10	invlaid ts
	#np		11	segment not present
	#ss		12	stack segfault
	#gp		13	general protection
	#pf		14	page fault
	#mf		16	math fault
	#ac		17	alignment check
	#mc		19	machine check
*/
void callint(int intpt) {
  int intloc;
  intloc=readmembyte(intpt*4);
#if (DEBUG>=2)
  printf("Interrupt %i called.  Running segment %i\n", intpt, intloc);
#endif
  doopcodereal(intloc,0,65535);
  return;
}



void opcode00(void) { 
  writemembyte((bx+si),al+readmembyte(bx+si)); ip+=1;
#if (DEBUG>=2) 
  printf("00   add [%i],%i\n",(bx+si),al);
#endif
}
void opcode04(void) {
  ip++;
  al+=readmembyte(absaddr+1);
  ax=(((ax>>8)<<8)+al);
  eax=(((eax>>8)<<8)+al);
#if (DEBUG>=2)
  printf("04   add\n");
#endif
}
void opcode05(void) {
  if ((cr0 & 1)==0) { 
    ip+=2;
    ax+=((readmembyte(absaddr+2)<<8)+readmembyte(absaddr+1));
    eax=((eax>>16)<<16)+ax;
    ah=(ax>>8);
    al=(ax-(ah<<8));
#if (DEBUG>=2)
    printf("05   add\n");
#endif
    return;
  } else {
    ip+=4;
    eax+=(readmembyte(absaddr+4)<<24)+(readmembyte(absaddr+3)<<16)+(readmembyte(absaddr+2)<<8)+readmembyte(absaddr+1);
    ax=((eax<<16)>>16);
    ah=(ax>>8);
    al=(ax-(ah<<8));
#if (DEBUG>=2)
    printf("05   add\n");
#endif
    return;
  }  
}
void opcode06(void) {
  stack[sp]=es>>8; stack[sp+1]=es-((es>>8)<<8); sp=sp+2;
#if (DEBUG>=2) 
  printf("06   push es\n");
#endif
}
void opcode0f(void) {
  int tmpA;
  tmpA=(readmembyte(absaddr+1));
  if (tmpA>=0xc8) {
    ip+=4;
    *retptr[tmpA][3]=((*retptr[tmpA][3])>>24)+((*retptr[tmpA][3] & 0xFF)<<24)+((*retptr[tmpA][3] & 0x0000FF00)<<8)+((*retptr[tmpA][3] & 0x00FF0000)>>8);
    *retptr[tmpA][2]=(*retptr[tmpA][3] & 0xFFFF);
    if (tmpA>4) { 
      *retptr[tmpA][0]=(*retptr[tmpA][2] & 0xFF);
      *retptr[tmpA][1]=(*retptr[tmpA][2] & 0xFF00);
    }
#if (DEBUG>=2)
    printf("0FC8 bswap %i\n", tmpA+65);
#endif
    return;
  }
  if (tmpA==0xa3) {
/*
Need to write BT sometime... 
*/
    return;
  }
  if (tmpA==0x06) {
#if (DEBUG>=2)
    printf("0F06 CLTS\n");
#endif
  }
  if (tmpA==0xa2) {
    if (eax==0) {
      eax=1; ax=1; ah=0; al=1;
      bl=0x47; bh=0x65; bx=0x6547; ebx=0x756e6547;
      cl=0x6e; ch=0x74; cx=0x746e; ecx=0x6c65746e;
      dl=0x69; dh=0x6e; dx=0x6e69; edx=0x49656e69;
    }
#if (DEBUG>=2)
    printf("0FA2 CPUId\n");
#endif
  }
#if (DEBUG>=2)
  printf("Unksupported form of 0x0F, %i\n",tmpA);
#endif
}
void opcode14(void) {
  ip++;
  al+=readmembyte(absaddr+1)+cf;
  ax=(ah<<8)+(al);
  eax=((eax>>16)<<16)+ax;
#if (DEBUG>=2)
  printf("14   adc al,...\n");
#endif
}
void opcode15(void) {
  if ((cr0 & 1)==0) { 
    ip+=2;
    ax+=((readmembyte(absaddr+2)<<8)+readmembyte(absaddr+1))+cf;
    eax=((eax>>16)<<16)+ax;
    ah=(ax>>8);
    al=(ax-(ah<<8));
#if (DEBUG>=2)
    printf("15   adc ax,...\n");
#endif
    return;
  } else {
    ip+=4;
    eax+=(readmembyte(absaddr+4)<<24)+(readmembyte(absaddr+3)<<16)+(readmembyte(absaddr+2)<<8)+readmembyte(absaddr+1)+cf;
    ax=((eax<<16)>>16);
    ah=(ax>>8);
    al=(ax-(ah<<8));
#if (DEBUG>=2)
    printf("15   adc eax,...\n");
#endif
    return;
  }
}
void opcode24(void) {
  ip++;
  al=(al & readmembyte(absaddr+1));
#if (DEBUG>=2)
  printf("24   and al,...\n");
#endif
}
void opcode25(void) {
  if ((cr0 & 1)==0) { 
    ip+=2;
    ax=(ax & ((readmembyte(absaddr+2)<<8)+readmembyte(absaddr+1)));
    eax=((eax>>16)<<16)+ax;
    ah=(ax>>8);
    al=(ax-(ah<<8));
#if (DEBUG>=2)
    printf("25   and ax,...\n");
#endif
    return;
  } else {
    ip+=4;
    eax=(eax & ((readmembyte(absaddr+4)<<24)+(readmembyte(absaddr+3)<<16)+(readmembyte(absaddr+2)<<8)+readmembyte(absaddr+1)));
    ax=((eax<<16)>>16);
    ah=(ax>>8);
    al=(ax-(ah<<8));
#if (DEBUG>=2)
    printf("25   and eax,...\n");
#endif
    return;
  }  
}
void opcode27(void) {
  if (((al & 0x0f)>9) || af==1) {
    al+=6;
    af=1;
  } else {
    af=0;
  }
  if (((al & 0xf0)>0x90) || cf==1) {
    al+=0x60;
    cf=1;
  } else {
    cf=0;
  }
#if (DEBUG>=2)
  printf("27   daa\n");
#endif
}
void opcode2f(void) {
  if (((al & 0xf)>9) || af==1) {
    al-=6;
    af=1;
  } else { af=0; }
  if ((al>0x9f) || cf==1) {
    al-=0x60;
    cf=1;
  } else { cf=0; }

}
void opcode37(void)
{
  if (((al & 0x0f) > 9) || (af==1)) {
    al+=6;
    ah+=1;
    af=1; cf=1;
  } else {
    af=0; cf=0;
  }
  al=(al & 0x0f);
#if (DEBUG>=2)
  printf("37   aaa\n");
#endif
}
void opcode3f(void) {
  if (((al & 0x0f)>9) || (af==1)) {
    al-=6;
    ah-=1;
    af=1;cf=1;
  } else {
    af=0;cf=0;
  }
  al=(al & 0x0f);
#if (DEBUG>=2)
  printf("3F   aas\n");
#endif
}
void opcode50(void)
{ 
  stack[sp]=ah; stack[sp+1]=al; sp=sp+2; 
#if (DEBUG>=2) 
  printf("50   push ax\n");
#endif
}   
void opcode8b(void)
{
  cx=si;
  ip++;
#if (DEBUG>=2) 
  printf("8BCE mov cx,si\n");
#endif
}
void opcode98(void) {
  if ((cr0 & 1)==0) {
    if ((al>7)==1) { ah=255; } else { ah=0; }
    ax=(ah<<8)+(al);
    eax=((eax>>16)<<16)+ax;
#if (DEBUG>=2)
    printf("98   CBW\n");
#endif
    return;
  } else {
    if ((ax>>15)==1) { eax=((65535<<16)+ax); } else { eax=ax; }
#if (DEBUG>=2)
    printf("98   CDWE\n");
#endif
  }
}
void opcodeb8(void) 
{
  ax=(readmembyte(absaddr+2)<<8)+(readmembyte(absaddr+1));
  ah=ax>>8;
  al=(ax-((ax>>8)<<8));
  eax=((eax>>16)<<16)+ax;
  ip+=2;
#if (DEBUG>=2)
  printf("B8   mov ax,%i\n",ax);
#endif
}
void opcodebe(void)
{
  si=(readmembyte(absaddr+2)<<8)+(readmembyte(absaddr+1));
  esi=((esi>>16)<<16)+si;
  ip+=2;
#if (DEBUG>=2) 
  printf("BE   mov si,%i\n",si);
#endif
}
void opcodebf(void)
{
  di=(readmembyte(absaddr+2)<<8)+(readmembyte(absaddr+1));
  edi=((edi>>16)<<16)+di;
  ip+=2;
#if (DEBUG>=2) 
  printf("BF   mov di,%i\n",di);
#endif
}
void opcoded4(void) {
  int tmpA,tmpB;
  ip++;
  tmpA=al; tmpB=readmembyte(absaddr+1);
  if (tmpB==0) { callint(0); return; }  /* Divide overflow. */
  ah=tmpA/tmpB;
  al=tmpA % tmpB;
#if (DEBUG>=2)
  printf("D4   aam\n");
#endif
}
void opcoded5(void) {
  ip++;
  ah=0;
  al=((al+ah+readmembyte(absaddr+1)) & 0xff);
  ax=getval(ah,al);
  eax=((eax>>16)<<16)+ax;
#if (DEBUG>=2)
  printf("D5   aad\n");
#endif
}

void opcodee4(void) {
  int tmpA;
  tmpA=readmembyte(absaddr+1);
  ip+=1;
  
#if (DEBUG>=2)
  printf("E4   in");
#endif
}

void opcodef3(void)
{     
#if (DEBUG>=1)
  printf("F3   repz --- NOT SUPPORTED\n");
#endif
}
void opcodef5(void) {
  cf=(cf*-1)+1;
#if (DEBUG>=2)
  printf("F5   cfc\n");
#endif
}
void opcodef7(void)
{
  cx=(cx^0xffff)+1;
  ip++;
#if (DEBUG>=2)
  printf("F7D9 neg cx\n");
#endif
}
void opcodef8(void) {
  cf=0;
#if (DEBUG>=2)
  printf("F8   clc\n");
#endif
}
void opcodefc(void)
{
  df=0;
#if (DEBUG>=1)
  printf("FC   cld\n");
#endif
}



void make_regs_and_ops(void) {
  regs[0x00]=opcode00;
  regs[0x04]=opcode04;
  regs[0x05]=opcode05;
  regs[0x06]=opcode06;
  regs[0x0f]=opcode0f;
  regs[0x14]=opcode14;
  regs[0x15]=opcode15;
  regs[0x24]=opcode24;
  regs[0x25]=opcode25;
  regs[0x27]=opcode27;
  regs[0x2f]=opcode2f;
  regs[0x37]=opcode37;
  regs[0x3f]=opcode3f;
  regs[0x50]=opcode50;
  regs[0x8b]=opcode8b;
  regs[0x98]=opcode98;
  regs[0xb8]=opcodeb8;
  regs[0xbe]=opcodebe;
  regs[0xbf]=opcodebf;
  regs[0xd4]=opcoded4;
  regs[0xd5]=opcoded5;
  regs[0xe4]=opcodee4;
  regs[0xf3]=opcodef3;
  regs[0xf5]=opcodef5;
  regs[0xf7]=opcodef7;
  regs[0xf8]=opcodef8;
  regs[0xfc]=opcodefc;

/* Setup pointers to registers in the standard way. */
  retptr[0][0]=(long *) &al;
  retptr[1][0]=(long *) &cl;
  retptr[2][0]=(long *) &dl;
  retptr[3][0]=(long *) &bl;
  retptr[0][1]=(long *) &al;
  retptr[1][1]=(long *) &cl;
  retptr[2][1]=(long *) &dl;
  retptr[3][1]=(long *) &bl;
  retptr[0][2]=(long *) &ax;
  retptr[1][2]=(long *) &cx;
  retptr[2][2]=(long *) &dx;
  retptr[3][2]=(long *) &bx;
  retptr[4][2]=(long *) &sp;
  retptr[5][2]=(long *) &bp;
  retptr[6][2]=(long *) &si;
  retptr[7][2]=(long *) &di;
  retptr[0][3]=(long *) &eax;
  retptr[1][3]=(long *) &ecx;
  retptr[2][3]=(long *) &edx;
  retptr[3][3]=(long *) &ebx;
  retptr[4][3]=(long *) &esp;
  retptr[5][3]=(long *) &ebp;
  retptr[6][3]=(long *) &esi;
  retptr[7][3]=(long *) &edi;

}

int main(void)
{
  FILE *biosId;
  int i;
  printf("Creating virtual memmory using a");
#if (USEFILE==1)
  memfd=fopen(memfile,"w+");
  printf(" file");
#else
  printf("n array");
#endif
  printf(", %ikb\nLoading startup program %s\n",RAMSIZE/1024,biosfile);
  make_regs_and_ops();
  biosId=fopen(biosfile,"r");
  for (i=0;i<32768;i++)
  {
    writemembyte(((1<<16)+i),fgetc(biosId));
  }
  doopcodereal(1,0,32768);
  return(0);
}
