/* 
 * This file defines a 8086 virtual machine
 * 
 * Copywrite 1993, Hamish Coleman
 *
 */
 
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#include <termio.h>
#include <termcap.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/times.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <limits.h>
#include <linux/fd.h>
#include "global.h"
#include "vm86.h"
#include "vm.h"
#include "int21_names.h"

struct vm86_struct vm86s;
int vm_status;
int vm_iflag;
int error;

#undef debmsg
#define debmsg(f,a...)		printf(f,##a)

void show_regs(void)
{
	int i;
	char s[100];
	char *diss = s;
	int ilen;
	unsigned char *cp = (unsigned char *)0 + (_regs.cs<<4) + _regs.eip;
	
	_regs.cs &= 0xffff;

	debmsg("bp=%08x  sp=%04x:%04x  flags=%08x  ds=%04x  es=%04x  fs=%04x  gs=%04x\n", _regs.ebp, 
		_regs.ss, _regs.esp, _regs.eflags, _regs.ds & 0xffff, _regs.es & 0xffff, _regs.fs & 0xffff, _regs.gs & 0xffff);
	debmsg("ax=%08x  bx=%08x  cx=%08x  dx=%08x  di=%08x  si=%08x\n", _regs.eax, _regs.ebx, 
		_regs.ecx, _regs.edx, _regs.edi, _regs.esi);
	debmsg("%04x:%04x ",_regs.cs,_regs.eip);
	
	ilen = i386dis(2,_regs.cs,_regs.eip,cp,diss);
	for (i=0; i<ilen; i++)
		debmsg("%02x", *cp++);
	debmsg("\r\t\t\t%s\n\n",diss);
}

void dump_hex(u_char * buf, long len)
{
	int i;
	int j;
	char prt[17];
	
	prt[16]='\0';
	i=(int)buf+len;
	while ((int)buf<i) {
		debmsg("%08x:\t",(int)buf);
		for (j=0;j<16 && (int)buf<i;j++) {
			prt[j]= isprint(*buf)? *buf:'_';
			debmsg("%02x ", *buf++);
		}
		prt[j]='\0';
		if (j<16) {
			char tabs[]="\0\0\0\0\0\0\0\0\0";
			memset(tabs,'\t',9-(16+3*(j+1))%8);
			debmsg(tabs);
		}
		debmsg("\t%s\n",prt);
	}
}

static void inline port_out(char value, unsigned short port)
{
__asm__ volatile ("outb %0,%1"
		: :"a" ((char) value),"d" ((unsigned short) port));
}

static void inline port_outw(unsigned short value, unsigned short port)
{
__asm__ volatile ("outw %0,%1"
		: :"a" ((unsigned short) value),"d" ((unsigned short) port));
}

static char inline port_in(unsigned short port)
{
	char _v;
__asm__ volatile ("inb %1,%0"
		:"=a" (_v):"d" ((unsigned short) port));
	return _v;
}

int inb(int port)
{
	char byte;
	static char count = 0;
	
	port &= 0xffff;
	debmsg("inb  [%04x]    ", port);
	if (port == 0x3da || port == 0x3ba) {
		count++;
		if (count == 10) {
			byte = 9;
			count = 0;
			debmsg("%02x\n",byte &0xff);
			return byte &0xff;
		}
	}
	ioperm(port,1,1);
	byte = port_in(port);
	ioperm(port,1,0);
	debmsg("%02x\n",byte &0xff);
	
	return byte &0xff;
}

void outb(int port, int byte)
{
	port &= 0xffff;
	byte &= 0xff;
	debmsg("outb [%04x] %02x\n", port, byte);
	
	ioperm(port,1,1);
	port_out(byte,port);
	ioperm(port,1,0);
}

void outw(int port, int word)
{
	port &= 0xffff;
	word &= 0xffff;
	debmsg("outw [%04x] %04x\n", port, word);
	
	ioperm(port,2,1);
	port_outw(word,port);
	ioperm(port,2,0);
}

void emulate_push(int i)
{
	*SEG_ADR((us *), ss, sp) = i;
	_regs.esp -= 2;
}

void emulate_int(int i)
{
	us *ssp;
	
	/* remove some annoying and _constant_ interrupts
	 * this was more usefull before I changed this
	 * from an IBM emulator to a DOS emulator
	 */
	if (i != 0x16 && i != 0x28 && i != 0x2a) {
		debmsg("i%02x: ",i);
		switch (i) {
			case 0x21: /* Dos int */
				debmsg("fn %02x %s\n",HI(ax),names21[HI(ax)]);
				break;
			case 0x13: /* BIOS disk int */
				debmsg("fn %02x %s\n",HI(ax),bios_13_names[HI(ax)]);
				break;
			case 0x10: /* Video */
				debmsg("fn %02x @%04x:%04x %s\n",HI(ax),_regs.cs,_regs.eip,bios_10_names[HI(ax)]);
				show_regs();
				break;
			default:
				debmsg("\n");
		}
	}
	ssp = SEG_ADR((us *), ss, sp);
	*--ssp = _regs.eflags;
	*--ssp = _regs.cs;
	*--ssp = _regs.eip;
	_regs.esp -= 6;
	_regs.cs =  ((us *)0)[ (i<<1) +1];
	_regs.eip = ((us *)0)[  i<<1    ];
	_regs.eflags &= 0xfffffcff;
}

void emulate_iret(void)
{
	us *ssp;
	
	ssp = SEG_ADR((us *), ss, sp);
	_regs.eip = *ssp++;
	_regs.cs = *ssp++;
	_regs.eflags = (_regs.eflags & 0xffff0000) | *ssp++;
	_regs.esp += 6;
}

void sigalrm(int sig)
{
	vm_status = F_sigalrm;
}

void sigsegv(int sig)
{
	us *ssp;
	unsigned char *csp;

	csp = SEG_ADR((unsigned char *), cs, ip);
	switch (*csp) {
		case I_bound: /* bound instruction */
			debmsg("BOUND INSN\n");
			show_regs();
			_regs.eip += 4;  /* check this? */
			/* emulate_int(5) */
			break;
		case I_int: /* int xx */
			_regs.eip += 2;
			emulate_int((int)*++csp);
			break;
		case I_int3: /* int 3 */
			_regs.eip += 1;
			debmsg("sigsegv: INT3\n");
			debmsg("so this CAN happen!\n");
			_exit(80);
			emulate_int(3);
			break;
		case I_iret: /* iret */
			emulate_iret();
			break;           /* do we need to set RF ? */

		case I_inw: /* inw xx */
			_regs.eax &= ~0xff00;
			_regs.eax |= inb((int)csp[1] +1) << 8;
		case I_inb: /* inb xx */
			_regs.eax &= ~0xff;
			_regs.eax |= inb((int)csp[1]);
			_regs.eip += 2;
			break;
		case I_inw_dx: /* inw dx */
			_regs.eax &= ~0xff00;
			_regs.eax |= inb(_regs.edx +1) << 8;
		case I_inb_dx: /* inb dx */
			_regs.eax &= ~0xff;
			_regs.eax |= inb(_regs.edx);
			_regs.eip += 1;
			break;

		case I_outw: /* outw xx */
			outw((int)csp[1], _regs.eax);
			_regs.eip += 2;
			break;
		case I_outb: /* outb xx */
			outb((int)csp[1], _regs.eax);
			_regs.eip += 2;
			break;
		case I_outw_dx: /* outw dx */
			outw(_regs.edx, _regs.eax);
			_regs.eip += 1;
			break;
		case I_outb_dx: /* outb dx */
			outb(_regs.edx, _regs.eax);
			_regs.eip += 1;
			break;
			
		case I_insb:
		case I_insw:
		case I_outsb:
		case I_outsw:
			debmsg("Error: in/out string w/b\n");
			show_regs();
			_regs.eip++;
			break;
	
		case I_cli: /* cli */
			vm_iflag = 0;
			_regs.eip += 1;
			break;
		case I_sti: /* sti */
			vm_iflag = 1;
			_regs.eip += 1;
			break;
	
		case I_pushf: /* pushf */
			_regs.eflags &= 0xfff ^ IF;
			if (vm_iflag) _regs.eflags |= IF;
			ssp = SEG_ADR((us *), ss, sp);
			*--ssp = (us)_regs.eflags;
			_regs.esp -= 2;
			_regs.eip += 1;
			break;
		case I_popf: /* popf */
			ssp = SEG_ADR((us *), ss, sp);
			_regs.eflags &= ~0xffff;
			_regs.eflags |= (int)*ssp;
			_regs.esp += 2;
			_regs.eip += 1;
			break;
	
		case I_hlt: /* HLT  */
			_regs.eip += 1;
			vm_status = I_hlt;
			return;
		case I_lock: /* lock */
			vm_status = I_lock;
			return;
		default:
			vm_status = F_sigsegv;
			return;
	}
	if ((_regs.eflags & TF) && !(_regs.eflags & RF) &&
	    (*csp != I_iret)) {                 /* if we are TF and not (IRET or RF) then */
		debmsg("Hmmm.... obviously this CAN happen !....");
		debmsg("found Single Step in the SigSegV Fn");
		_exit(80);
		emulate_int(1);                 /* we are in single step mode ... */
	}
}

void sigill(int sig)
{
unsigned char *csp;
int i, d;

	csp = SEG_ADR((unsigned char *), cs, ip);
	i = (csp[0] << 8) + csp[1]; /* swapped */
	if ((i & 0xf800) != 0xd800) { /* no fpu instruction */
		vm_status = F_sigill;
		return;
	}
	switch(i & 0xc0) {
		case 0x00:
			if ((i & 0x7) == 0x6) {
				d = *(short *)(csp +2);
				_regs.eip += 4;
			} else {
				_regs.eip += 2;
				d = 0;
			}
			break;
		case 0x40:
			d = (signed)csp[2];
			_regs.eip += 3;
			break;
		case 0x80:
			d = *(short *)(csp +2);
			_regs.eip += 4;
			break;
		default:
			_regs.eip += 2;
			d = 0;
	}
	vm_status = F_mathem;
}

void sigfpe(int sig)
{
	vm_status = F_sigfpe;
}

void sigtrap(int sig)
{
unsigned char *csp;

	csp = SEG_ADR((unsigned char *), cs, ip);
	if (*csp == 0xcc)
	{
		_regs.eip++;
		emulate_int(3);
	} else
	{
		emulate_int(1);
	}
}

#define SETSIG(sig, fun)	sa.sa_handler = fun; \
				sa.sa_flags = 0; \
				sa.sa_mask = 0; \
				sigaction(sig, &sa, NULL);


void vm_prep(void)
{
struct sigaction sa;

	SETSIG(SIGSEGV, sigsegv);
	SETSIG(SIGILL, sigill);
	SETSIG(SIGALRM, sigalrm);
	SETSIG(SIGFPE, sigfpe);
	SETSIG(SIGTRAP, sigtrap);
}

int vm(void)
{
	vm_status = 0;
	for (;vm_status==0;) 
		(void)vm86(&vm86s);
	return vm_status;
}

void doit(void)
{
	vm_prep();
	emulate_int(0x10);
	for (;!error;) {
		switch (vm()) {
			case F_sigsegv:
				debmsg("SIGSEGV:\n");
				show_regs();
				error = 1;
				break;
			case F_sigill:
				debmsg("SIGILL:\n");
				show_regs();
				error = 1;
				break;
			case F_mathem:
				debmsg("mathem:\n");
				show_regs();
				error = 1;
				break;
			case F_sigfpe:
				debmsg("SIGFPE:\n");
				show_regs();
				error = 1;
				break;
			case I_hlt:
				if (SEG_ADR((int),cs,ip) >= 0xF0000) {
					debmsg("Invalid call to BIOS at %04x:%04x\n",_regs.cs,_regs.eip);
					error = 1;
					break;
				}
				debmsg("HLT called at %04x:%04x, assuming IRET to caller\n",_regs.cs,_regs.eip);
				error = 80;
				break;
			case I_lock:
				break;
			default:
		}
	}
}

void emulate(int argc, char **argv)
{
	int fd;
	char * ptr;
	int newmode;
	char oldmode;
	
	if (argc < 2) {
		printf("\nTo use this program:\n");
		printf("\t%s MODENO\n",argv[0]);
		_exit(0);
	}
	
	debmsg("\n\n\n"
	       "********************************************************************************\n");
	debmsg("Bios setmode program, derived from:\n");
	debmsg(emu_banner);
	
	if ((fd=open("/dev/mem", O_RDONLY)) < 0) {
		perror("opening mem");
		_exit(1);
	}
	ptr = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0);
	if (-1 == (int) ptr) {
		perror("mmap lo-mem");
		_exit(1);
	}
	memcpy((char *)(0),ptr,4096);
	munmap(ptr,4096);
	
	ptr = mmap(NULL, 32768, PROT_READ, MAP_PRIVATE, fd, 0xC0000);
	if (-1 == (int) ptr) {
		perror("mmap BIOS");
		_exit(1);
	}
	memcpy((char *)(0xC0000),ptr,32768);
	munmap(ptr,32768);
	
	close(fd);
	
	memset( (char *)0xf0000, 0xF4, 0xffff);		/* set BIOS area to HLT   */
	oldmode = *(char *)0x449;			/* get current video mode */
	memset( (char *)0x2000, 0xF4, 0x2);		/* set 'current' program to HLT */
	memset( (char *)0x2002, 0x90, 0x2);		/* set 'current' program to HLT */

	_regs.eax = _regs.ebx = _regs.edx = _regs.ecx = 0;
	_regs.ebp = _regs.esi = _regs.edi = 0;
	_regs.cs = _regs.ss = _regs.es = _regs.fs = _regs.gs = 0x200;
	_regs.esp = 0xE000;
	_regs.eip = 0;
	_regs.eflags = 0;
	newmode = atoi(argv[1]);
	LO(ax) = newmode;

	debmsg("--------------------------------------------------------------------------------\n");
	debmsg("setting mode %i\n",newmode);
	emulate_int(0x10);
	doit();	
	if (error != 80) {
		printf("Error during emulate\n");
		_exit(1);
	}
	
	debmsg("--------------------------------------------------------------------------------\n");
	debmsg("ax = %08x\n",_regs.eax);
	fclose(stdout);
	_exit(80);
}