/*
 * (C) Copyright 1992, ..., 2007 the "DOSEMU-Development-Team".
 *
 * for details see file COPYING.DOSEMU in the DOSEMU distribution
 */

/* 	MS-DOS API translator for DOSEMU\'s DPMI Server
 *
 * DANG_BEGIN_MODULE msdos.c
 *
 * REMARK
 * MS-DOS API translator allows DPMI programs to call DOS service directly
 * in protected mode.
 *
 * /REMARK
 * DANG_END_MODULE
 *
 * First Attempted by Dong Liu,  dliu@rice.njit.edu
 *
 */

#include <stdlib.h>
#include <string.h>

#include "emu.h"
#include "emu-ldt.h"
#include "int.h"
#include "bios.h"
#include "emm.h"
#include "dpmi.h"
#include "dpmisel.h"
#include "msdos.h"
#include "vgaemu.h"
#include "utilities.h"
#include "dos2linux.h"

#define SUPPORT_DOSEMU_HELPERS 1
#if SUPPORT_DOSEMU_HELPERS
#include "doshelpers.h"
#endif

#define TRANS_BUFFER_SEG EMM_SEGMENT
#define EXEC_SEG (MSDOS_CLIENT.lowmem_seg + EXEC_Para_ADD)

#define DTA_over_1MB (GetSegmentBase(MSDOS_CLIENT.user_dta_sel) + MSDOS_CLIENT.user_dta_off)
#define DTA_under_1MB SEGOFF2LINEAR(MSDOS_CLIENT.lowmem_seg + DTA_Para_ADD, 0)

#define MAX_DOS_PATH 260

#define D_16_32(reg)		(MSDOS_CLIENT.is_32 ? reg : reg & 0xffff)
#define MSDOS_CLIENT (msdos_client[msdos_client_num - 1])
#define CURRENT_ENV_SEL ((u_short)READ_WORD(SEGOFF2LINEAR(CURRENT_PSP, 0x2c)))
#define WRITE_ENV_SEL(sel) (WRITE_WORD(SEGOFF2LINEAR(CURRENT_PSP, 0x2c), sel))

static int msdos_client_num = 0;
static struct msdos_struct msdos_client[DPMI_MAX_CLIENTS];

static int ems_frame_mapped;
static void *ems_map_buffer = NULL;
static u_short ems_frame_unmap[EMM_UMA_STD_PHYS*2];
static u_short ems_frame_segs[EMM_UMA_STD_PHYS+1];

void msdos_setup(void)
{
    int i;

    ems_map_buffer = malloc(emm_get_size_for_partial_page_map(EMM_UMA_STD_PHYS));
    ems_frame_segs[0] = EMM_UMA_STD_PHYS;
    for(i = 0; i < EMM_UMA_STD_PHYS; i++) {
	ems_frame_segs[i + 1] = EMM_SEGMENT + i * (0x1000/EMM_UMA_STD_PHYS);
	ems_frame_unmap[i * 2] = 0xffff;
	ems_frame_unmap[i * 2 + 1] = i;
    }
}

void msdos_init(int is_32, unsigned short mseg)
{
    unsigned short envp;
    msdos_client_num++;
    memset(&MSDOS_CLIENT, 0, sizeof(struct msdos_struct));
    MSDOS_CLIENT.is_32 = is_32;
    MSDOS_CLIENT.lowmem_seg = mseg;
    /* convert environment pointer to a descriptor */
    envp = READ_WORD(SEGOFF2LINEAR(CURRENT_PSP, 0x2c));
    if (envp) {
	WRITE_ENV_SEL(ConvertSegmentToDescriptor(envp));
	D_printf("DPMI: env segment %#x converted to descriptor %#x\n",
		envp, CURRENT_ENV_SEL);
    }
    D_printf("MSDOS: init, %i\n", msdos_client_num);
}

void msdos_done(void)
{
  if (CURRENT_ENV_SEL)
      WRITE_ENV_SEL(GetSegmentBase(CURRENT_ENV_SEL) >> 4);
    msdos_client_num--;
    D_printf("MSDOS: done, %i\n", msdos_client_num);
}

int msdos_get_lowmem_size(void)
{
    return DTA_Para_SIZE + EXEC_Para_SIZE;
}

static unsigned int msdos_malloc(unsigned long size)
{
  int i;
  dpmi_pm_block block = DPMImalloc(size);
  if (!block.size)
    return 0;
  for (i = 0; i < MSDOS_MAX_MEM_ALLOCS; i++) {
    if (MSDOS_CLIENT.mem_map[i].size == 0) {
      MSDOS_CLIENT.mem_map[i] = block;
      break;
    }
  }
  return block.base;
}

static int msdos_free(unsigned int addr)
{
  int i;
  for (i = 0; i < MSDOS_MAX_MEM_ALLOCS; i++) {
    if (MSDOS_CLIENT.mem_map[i].base == addr) {
      DPMIfree(MSDOS_CLIENT.mem_map[i].handle);
      MSDOS_CLIENT.mem_map[i].size = 0;
      return 0;
    }
  }
  return -1;
}

static unsigned int msdos_realloc(unsigned int addr, unsigned int new_size)
{
  int i;
  dpmi_pm_block block;
  block.size = 0;
  for (i = 0; i < MSDOS_MAX_MEM_ALLOCS; i++) {
    if (MSDOS_CLIENT.mem_map[i].base == addr) {
      block = MSDOS_CLIENT.mem_map[i];
      break;
    }
  }
  if (!block.size)
    return 0;
  block = DPMIrealloc(block.handle, new_size);
  if (!block.size)
    return 0;
  MSDOS_CLIENT.mem_map[i] = block;
  return block.base;
}

static void prepare_ems_frame(void)
{
    if (ems_frame_mapped)
	return;
    ems_frame_mapped = 1;
    emm_get_partial_map_registers(ems_map_buffer, ems_frame_segs);
    /* 0 is the special OS_HANDLE */
    emm_map_unmap_multi(ems_frame_unmap, 0, EMM_UMA_STD_PHYS);
}

static void restore_ems_frame(void)
{
    if (!ems_frame_mapped)
	return;
    emm_set_partial_map_registers(ems_map_buffer);
    ems_frame_mapped = 0;
}

static int need_copy_dseg(struct sigcontext_struct *scp, int intr)
{
    switch (intr) {
	case 0x21:
	    switch (_HI(ax)) {
		case 0x0a:		/* buffered keyboard input */
		case 0x5a:		/* mktemp */
		case 0x69:
		    return 1;
		case 0x44:		/* IOCTL */
		    switch (_LO(ax)) {
			case 0x02 ... 0x05:
			case 0x0c: case 0x0d:
			    return 1;
		    }
		    break;
		case 0x5d:		/* Critical Error Information  */
		    return (_LO(ax) != 0x06 && _LO(ax) != 0x0b);
		case 0x5e:
		    return (_LO(ax) != 0x03);
	    }
	    break;
	case 0x25:			/* Absolute Disk Read */
	case 0x26:			/* Absolute Disk write */
	    return 1;
    }

    return 0;
}

static int need_copy_eseg(struct sigcontext_struct *scp, int intr)
{
    switch (intr) {
	case 0x10:			/* video */
	    switch (_HI(ax)) {
		case 0x10:		/* Set/Get Palette Registers (EGA/VGA) */
		    switch(_LO(ax)) {
			case 0x2:		/* set all palette registers and border */
			case 0x09:		/* ead palette registers and border (PS/2) */
			case 0x12:		/* set block of DAC color registers */
			case 0x17:		/* read block of DAC color registers */
			    return 1;
		    }
		    break;
		case 0x11:		/* Character Generator Routine (EGA/VGA) */
		    switch (_LO(ax)) {
			case 0x0:		/* user character load */
			case 0x10:		/* user specified character definition table */
			case 0x20: case 0x21:
			    return 1;
		    }
		    break;
		case 0x13:		/* Write String */
		case 0x15:		/*  Return Physical Display Parms */
		case 0x1b:
		    return 1;
		case 0x1c:
		    if (_LO(ax) == 1 || _LO(ax) == 2)
			return 1;
		    break;
	}
	break;
	case 0x21:
	    switch (_HI(ax)) {
		case 0x57:		/* Get/Set File Date and Time Using Handle */
		    if ((_LO(ax) == 0) || (_LO(ax) == 1)) {
			return 0;
		    }
		    return 1;
		case 0x5e:
		    return (_LO(ax) == 0x03);
	    }
	    break;
	case 0x33:
	    switch (_HI(ax)) {
		case 0x16:		/* save state */
		case 0x17:		/* restore */
		    return 1;
	    }
	    break;
#if SUPPORT_DOSEMU_HELPERS
	case DOS_HELPER_INT:			/* dosemu helpers */
	    switch (_LO(ax)) {
		case DOS_HELPER_PRINT_STRING:		/* print string to dosemu log */
		    return 1;
	    }
	    break;
#endif
    }

    return 0;
}

/* DOS selector is a selector whose base address is less than 0xffff0 */
/* and para. aligned.                                                 */
static int in_dos_space(unsigned short sel, unsigned long off)
{
    unsigned int base = GetSegmentBase(sel);

    if (base + off > 0x10ffef) {	/* ffff:ffff for DOS high */
      D_printf("MSDOS: base address %#x of sel %#x > DOS limit\n", base, sel);
      return 0;
    } else
    if (base & 0xf) {
      D_printf("MSDOS: base address %#x of sel %#x not para. aligned.\n", base, sel);
      return 0;
    } else
      return 1;
}

static void old_dos_terminate(struct sigcontext_struct *scp, int i)
{
    unsigned short psp_seg_sel, parent_psp = 0;
    unsigned short psp_sig;

    D_printf("MSDOS: old_dos_terminate, int=%#x\n", i);

    REG(cs)  = CURRENT_PSP;
    REG(eip) = 0x100;

#if 0
    _eip = READ_WORD(SEGOFF2LINEAR(CURRENT_PSP, 0xa));
    _cs = ConvertSegmentToCodeDescriptor(
      READ_WORD(SEGOFF2LINEAR(CURRENT_PSP, 0xa+2)));
#endif

    /* put our return address there */
    WRITE_WORD(SEGOFF2LINEAR(CURRENT_PSP, 0xa),
	     DPMI_OFF + HLT_OFF(DPMI_return_from_dosint) + i);
    WRITE_WORD(SEGOFF2LINEAR(CURRENT_PSP, 0xa+2), DPMI_SEG);

    psp_seg_sel = READ_WORD(SEGOFF2LINEAR(CURRENT_PSP, 0x16));
    /* try segment */
    psp_sig = READ_WORD(SEGOFF2LINEAR(psp_seg_sel, 0));
    if (psp_sig != 0x20CD) {
	/* now try selector */
	unsigned int addr;
	D_printf("MSDOS: Trying PSP sel=%#x, V=%i, d=%i, l=%#x\n",
	    psp_seg_sel, ValidAndUsedSelector(psp_seg_sel),
	    in_dos_space(psp_seg_sel, 0), GetSegmentLimit(psp_seg_sel));
	if (ValidAndUsedSelector(psp_seg_sel) && in_dos_space(psp_seg_sel, 0) &&
		GetSegmentLimit(psp_seg_sel) >= 0xff) {
	    addr = GetSegmentBase(psp_seg_sel);
	    psp_sig = READ_WORD(addr);
	    D_printf("MSDOS: Trying PSP sel=%#x, addr=%#x\n", psp_seg_sel, addr);
	    if (!(addr & 0x0f) && psp_sig == 0x20CD) {
		/* found selector */
		parent_psp = addr >> 4;
	        D_printf("MSDOS: parent PSP sel=%#x, seg=%#x\n",
		    psp_seg_sel, parent_psp);
	    }
	}
    } else {
	/* found segment */
	parent_psp = psp_seg_sel;
    }
    if (!parent_psp) {
	/* no PSP found, use current as the last resort */
	D_printf("MSDOS: using current PSP as parent!\n");
	parent_psp = CURRENT_PSP;
    }

    D_printf("MSDOS: parent PSP seg=%#x\n", parent_psp);
    if (parent_psp != psp_seg_sel)
	WRITE_WORD(SEGOFF2LINEAR(CURRENT_PSP, 0x16), parent_psp);
}

/*
 * DANG_BEGIN_FUNCTION msdos_pre_extender
 *
 * This function is called before a protected mode client goes to real
 * mode for DOS service. All protected mode selector is changed to
 * real mode segment register. And if client\'s data buffer is above 1MB,
 * necessary buffer copying is performed. This function returns 1 if
 * it does not need to go to real mode, otherwise returns 0.
 *
 * DANG_END_FUNCTION
 */

int msdos_pre_extender(struct sigcontext_struct *scp, int intr)
{
    D_printf("MSDOS: pre_extender: int 0x%x, ax=0x%x\n", intr, _LWORD(eax));
    if (MSDOS_CLIENT.user_dta_sel && intr == 0x21) {
	switch (_HI(ax)) {	/* functions use DTA */
	case 0x11: case 0x12:	/* find first/next using FCB */
	case 0x4e: case 0x4f:	/* find first/next */
	    MEMCPY_DOS2DOS(DTA_under_1MB, DTA_over_1MB, 0x80);
	    break;
	}
    }

    /* only consider DOS and some BIOS services */
    switch (intr) {
    case 0x41:			/* win debug */
	return MSDOS_DONE;

    case 0x15:			/* misc */
	switch (_HI(ax)) {
	  case 0xc2:
	    D_printf("MSDOS: PS2MOUSE function 0x%x\n", _LO(ax));
	    switch (_LO(ax)) {
	      case 0x07:		/* set handler addr */
		if ( _es && D_16_32(_ebx) ) {
		  D_printf("MSDOS: PS2MOUSE: set handler addr 0x%x:0x%x\n",
		    _es, D_16_32(_ebx));
		  MSDOS_CLIENT.PS2mouseCallBack.selector = _es;
		  MSDOS_CLIENT.PS2mouseCallBack.offset = D_16_32(_ebx);
		  REG(es) = DPMI_SEG;
		  REG(ebx) = DPMI_OFF + HLT_OFF(MSDOS_PS2_mouse_callback);
		} else {
		  D_printf("MSDOS: PS2MOUSE: reset handler addr\n");
		  REG(es) = 0;
		  REG(ebx) = 0;
		}
		return 0;
	      default:
		return 0;
	    }
	    break;
	  default:
	    return 0;
	}
    case 0x20:			/* DOS terminate */
	old_dos_terminate(scp, intr);
	return 0;
    case 0x21:
	switch (_HI(ax)) {
	    /* first see if we don\'t need to go to real mode */
	case 0x25: {		/* set vector */
	      INTDESC desc;
	      desc.selector = _ds;
	      desc.offset = D_16_32(_edx);
	      dpmi_set_interrupt_vector(_LO(ax), desc);
	      D_printf("MSDOS: int 21,ax=0x%04x, ds=0x%04x. dx=0x%04x\n",
		     _LWORD(eax), _ds, _LWORD(edx));
	    }
	    return MSDOS_DONE;
	case 0x35: {	/* Get Interrupt Vector */
	      INTDESC desc = dpmi_get_interrupt_vector(_LO(ax));
	      _es = desc.selector;
	      _ebx = desc.offset;
	      D_printf("MSDOS: int 21,ax=0x%04x, es=0x%04x. bx=0x%04x\n",
		     _LWORD(eax), _es, _LWORD(ebx));
	    }
	    return MSDOS_DONE;
	case 0x48:		/* allocate memory */
	    {
		unsigned long size = _LWORD(ebx) << 4;
		unsigned int addr = msdos_malloc(size);
		if (!addr) {
		    unsigned int meminfo[12];
		    GetFreeMemoryInformation(meminfo);
		    _eflags |= CF;
		    _LWORD(ebx) = meminfo[0] >> 4;
		    _LWORD(eax) = 0x08;
		} else {
		    unsigned short sel = AllocateDescriptors(1);
		    SetSegmentBaseAddress(sel, addr);
		    SetSegmentLimit(sel, size - 1);
		    _LWORD(eax) = sel;
		    _eflags &= ~CF;
		}
		return MSDOS_DONE;
	    }
	case 0x49:		/* free memory */
	    {
		if (msdos_free(GetSegmentBase(_es)) == -1) {
		    _eflags |= CF;
		} else {
		    _eflags &= ~CF;
		    FreeDescriptor(_es);
		    FreeSegRegs(scp, _es);
		}
		return MSDOS_DONE;
	    }
	case 0x4a:		/* reallocate memory */
	    {
		unsigned new_size = _LWORD(ebx) << 4;
		unsigned addr = msdos_realloc(GetSegmentBase(_es), new_size);
		if (!addr) {
		    unsigned int meminfo[12];
		    GetFreeMemoryInformation(meminfo);
		    _eflags |= CF;
		    _LWORD(ebx) = meminfo[0] >> 4;
		    _LWORD(eax) = 0x08;
		} else {
		    SetSegmentBaseAddress(_es, addr);
		    SetSegmentLimit(_es, new_size - 1);
		    _eflags &= ~CF;
		}
		return MSDOS_DONE;
	    }
	case 0x01 ... 0x08:	/* These are dos functions which */
	case 0x0b ... 0x0e:	/* are not required memory copy, */
	case 0x19:		/* and segment register translation. */
	case 0x2a ... 0x2e:
	case 0x30 ... 0x34:
	case 0x36: case 0x37:
	case 0x3e:
	case 0x42:
	case 0x45: case 0x46:
	case 0x4d:
	case 0x4f:		/* find next */
	case 0x54:
	case 0x58: case 0x59:
	case 0x5c:		/* lock */
	case 0x66 ... 0x68:
	case 0xF8:		/* OEM SET vector */
	    return 0;
	case 0x00:		/* DOS terminate */
	    old_dos_terminate(scp, intr);
	    LWORD(eax) = 0x4c00;
	    return 0;
	case 0x09:		/* Print String */
	    {
		int i;
		char *s, *d;
		prepare_ems_frame();
		REG(ds) = TRANS_BUFFER_SEG;
		REG(edx) = 0;
		d = SEG2LINEAR(REG(ds));
		s = (char *)GetSegmentBaseAddress(_ds) + D_16_32(_edx);
		for(i=0; i<0xffff; i++, d++, s++) {
		    *d = *s;
		    if( *s == '$')
			break;
		}
	    }
	    return 0;
	case 0x1a:		/* set DTA */
	  {
	    unsigned long off = D_16_32(_edx);
	    if ( !in_dos_space(_ds, off)) {
		MSDOS_CLIENT.user_dta_sel = _ds;
		MSDOS_CLIENT.user_dta_off = off;
		REG(ds) = MSDOS_CLIENT.lowmem_seg+DTA_Para_ADD;
		REG(edx)=0;
                MEMCPY_DOS2DOS(DTA_under_1MB, DTA_over_1MB, 0x80);
	    } else {
                REG(ds) = GetSegmentBase(_ds) >> 4;
                MSDOS_CLIENT.user_dta_sel = 0;
            }
	  }
	  return 0;

	/* FCB functions */
	case 0x0f: case 0x10:	/* These are not supported by */
	case 0x14: case 0x15:	/* dosx.exe, according to Ralf Brown */
	case 0x21 ... 0x24:
	case 0x27: case 0x28:
	    error("MS-DOS: Unsupported function 0x%x\n", _HI(ax));
	    _HI(ax) = 0xff;
	    return MSDOS_DONE;
	case 0x11: case 0x12:	/* find first/next using FCB */
	case 0x13:		/* Delete using FCB */
	case 0x16:		/* Create usring FCB */
	case 0x17:		/* rename using FCB */
	    prepare_ems_frame();
	    REG(ds) = TRANS_BUFFER_SEG;
	    REG(edx) = 0;
	    MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(ds), LWORD(edx)),
			GetSegmentBase(_ds) + D_16_32(_edx),
			0x50);
	    return 0;
	case 0x29:		/* Parse a file name for FCB */
	    {
		unsigned short seg = TRANS_BUFFER_SEG;
		prepare_ems_frame();
		REG(ds) = seg;
		REG(esi) = 0;
		MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(ds), LWORD(esi)),
			    GetSegmentBase(_ds) + D_16_32(_esi),
			    0x100);
		seg += 0x10;
		REG(es) = seg;
		REG(edi) = 0;
		MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(es), LWORD(edi)),
			    GetSegmentBase(_es) + D_16_32(_edi),
			    0x50);
	    }
	    return 0;
	case 0x47:		/* GET CWD */
	    prepare_ems_frame();
	    REG(ds) = TRANS_BUFFER_SEG;
	    REG(esi) = 0;
	    return 0;
	case 0x4b:		/* EXEC */
	  {
	    /* we must copy all data from higher 1MB to lower 1MB */
	    unsigned short segment = EXEC_SEG;
	    char *p;
	    unsigned short sel,off;

	    D_printf("BCC: call dos exec\n");
	    /* must copy command line */
	    REG(ds) = segment;
	    REG(edx) = 0;
	    p = (char *)GetSegmentBaseAddress(_ds) + D_16_32(_edx);
	    snprintf((char *)SEG2LINEAR(REG(ds)), MAX_DOS_PATH, "%s", p);
	    segment += (MAX_DOS_PATH + 0x0f) >> 4;

	    /* must copy parameter block */
	    REG(es) = segment;
	    REG(ebx) = 0;
	    MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(es), LWORD(ebx)),
	       GetSegmentBase(_es) + D_16_32(_ebx), 0x20);
	    segment += 2;
#if 0
	    /* now the envrionment segment */
	    sel = READ_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)));
	    WRITE_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)), segment);
	    MEMCPY_DOS2DOS(SEGOFF2LINEAR(segment, 0),           /* 4K envr. */
		GetSegmentBase(sel),
		0x1000);
	    segment += 0x100;
#else
	    WRITE_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)), 0);
#endif
	    /* now the tail of the command line */
    	    off = READ_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)+2));
	    sel = READ_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)+4));
	    WRITE_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)+4), segment);
	    WRITE_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)+2), 0);
	    MEMCPY_DOS2DOS(SEGOFF2LINEAR(segment, 0),
		GetSegmentBase(sel) + off,
		0x80);
	    segment += 8;

	    /* set the FCB pointers to something reasonable */
	    WRITE_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)+6), 0);
	    WRITE_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)+8), segment);
	    WRITE_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)+0xA), 0);
	    WRITE_WORD(SEGOFF2LINEAR(REG(es), LWORD(ebx)+0xC), segment);
	    MEMSET_DOS(SEGOFF2LINEAR(segment, 0), 0, 0x30);
	    segment += 3;

	    /* then the enviroment seg */
	    if (CURRENT_ENV_SEL)
		WRITE_ENV_SEL(GetSegmentBase(CURRENT_ENV_SEL) >> 4);

	    if (segment != EXEC_SEG + EXEC_Para_SIZE)
		error("DPMI: exec: seg=%#x (%#x), size=%#x\n",
			segment, segment - EXEC_SEG, EXEC_Para_SIZE);
	    if (ems_frame_mapped)
		error("DPMI: exec: EMS frame should not be mapped here\n");
	  }
	  return 0;

	case 0x50:		/* set PSP */
	    if ( !in_dos_space(_LWORD(ebx), 0)) {
		MSDOS_CLIENT.user_psp_sel = _LWORD(ebx);
		LWORD(ebx) = CURRENT_PSP;
		MEMCPY_DOS2DOS(SEGOFF2LINEAR(LWORD(ebx), 0),
		    GetSegmentBase(_LWORD(ebx)), 0x100);
		D_printf("MSDOS: PSP moved from %x to %x\n",
		    GetSegmentBase(_LWORD(ebx)),
		    SEGOFF2LINEAR(LWORD(ebx), 0));
	    } else {
		REG(ebx) = GetSegmentBase(_LWORD(ebx)) >> 4;
		MSDOS_CLIENT.user_psp_sel = 0;
	    }
	    return 0;

	case 0x26:		/* create PSP */
	    prepare_ems_frame();
	    REG(edx) = TRANS_BUFFER_SEG;
	    return 0;

	case 0x55:		/* create & set PSP */
	    if ( !in_dos_space(_LWORD(edx), 0)) {
		MSDOS_CLIENT.user_psp_sel = _LWORD(edx);
		LWORD(edx) = CURRENT_PSP;
	    } else {
		REG(edx) = GetSegmentBase(_LWORD(edx)) >> 4;
		MSDOS_CLIENT.user_psp_sel = 0;
	    }
	    return 0;

	case 0x39:		/* mkdir */
	case 0x3a:		/* rmdir */
	case 0x3b:		/* chdir */
	case 0x3c:		/* creat */
	case 0x3d:		/* Dos OPEN */
	case 0x41:		/* unlink */
	case 0x43:		/* change attr */
	case 0x4e:		/* find first */
	case 0x5b:		/* Create */
	    if ((_HI(ax) == 0x4e) && (_ecx & 0x8))
		D_printf("MSDOS: MS-DOS try to find volume label\n");
	    {
		char *src, *dst;
		prepare_ems_frame();
		REG(ds) = TRANS_BUFFER_SEG;
		REG(edx) = 0;
		src = (char *)GetSegmentBaseAddress(_ds) + D_16_32(_edx);
		dst = SEG_ADR((char *), ds, dx);
		D_printf("MSDOS: passing ASCIIZ > 1MB to dos %p\n", dst);
		D_printf("%p: '%s'\n", src, src);
                snprintf(dst, MAX_DOS_PATH, "%s", src);
	    }
	    return 0;
	case 0x38:
	    if (_LWORD(edx) != 0xffff) { /* get country info */
		prepare_ems_frame();
		REG(ds) = TRANS_BUFFER_SEG;
		REG(edx) = 0;
	    }
	    break;
	case 0x3f:		/* dos read */
	    set_io_buffer(GetSegmentBase(_ds) + D_16_32(_edx),
		D_16_32(_ecx));
	    prepare_ems_frame();
	    REG(ds) = TRANS_BUFFER_SEG;
	    REG(edx) = 0;
	    REG(ecx) = D_16_32(_ecx);
	    fake_call_to(DOS_LONG_READ_SEG, DOS_LONG_READ_OFF);
	    return MSDOS_ALT_ENT;
	case 0x40:		/* DOS Write */
	    set_io_buffer(GetSegmentBase(_ds) + D_16_32(_edx),
		D_16_32(_ecx));
	    prepare_ems_frame();
	    REG(ds) = TRANS_BUFFER_SEG;
	    REG(edx) = 0;
	    REG(ecx) = D_16_32(_ecx);
	    fake_call_to(DOS_LONG_WRITE_SEG, DOS_LONG_WRITE_OFF);
	    return MSDOS_ALT_ENT;
	case 0x53:		/* Generate Drive Parameter Table  */
	    {
		unsigned short seg = TRANS_BUFFER_SEG;
		prepare_ems_frame();
		REG(ds) = seg;
		REG(esi) = 0;
		MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(ds), LWORD(esi)),
			    GetSegmentBase(_ds) + D_16_32(_esi),
			    0x30);
		seg += 3;

		REG(es) = seg;
		REG(ebp) = 0;
		MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(es), LWORD(ebp)),
			    GetSegmentBase(_es) + D_16_32(_ebp),
			    0x60);
	    }
	    return 0;
	case 0x56:		/* rename file */
	    {
		unsigned short seg = TRANS_BUFFER_SEG;
		prepare_ems_frame();
		REG(ds) = seg;
		REG(edx) = 0;
		snprintf(SEG2LINEAR(REG(ds)), MAX_DOS_PATH, "%s",
			     (char *)GetSegmentBaseAddress(_ds) + D_16_32(_edx));
		seg += 0x20;

		REG(es) = seg;
		REG(edi) = 0;
		snprintf(SEG2LINEAR(REG(es)), MAX_DOS_PATH, "%s",
			     (char *)GetSegmentBaseAddress(_es) + D_16_32(_edi));
	    }
	    return 0;
	case 0x5f:		/* redirection */
	    switch (_LO(ax)) {
	    unsigned short seg;
	    case 0: case 1:
		return 0;
	    case 2 ... 6:
		prepare_ems_frame();
		seg = TRANS_BUFFER_SEG;
		REG(ds) = seg;
		REG(esi) = 0;
		MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(ds), LWORD(esi)),
			GetSegmentBase(_ds) + D_16_32(_esi),
			0x100);
		seg += 0x10;
		REG(es) = seg;
		REG(edi) = 0;
		MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(es), LWORD(edi)),
			GetSegmentBase(_es) + D_16_32(_edi),
			0x100);
		return 0;
	    }
	case 0x60:		/* Get Fully Qualified File Name */
	    {
		unsigned short seg = TRANS_BUFFER_SEG;
		prepare_ems_frame();
		REG(ds) = seg;
		REG(esi) = 0;
		MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(ds), LWORD(esi)),
		    GetSegmentBase(_ds) + D_16_32(_esi),
		    0x100);
		seg += 0x10;
		REG(es) = seg;
		REG(edi) = 0;
		return 0;
	    }
	case 0x6c:		/*  Extended Open/Create */
	    {
		char *src, *dst;
		prepare_ems_frame();
		REG(ds) = TRANS_BUFFER_SEG;
		REG(esi) = 0;
		src = (char *)GetSegmentBaseAddress(_ds) + D_16_32(_esi);
		dst = SEG_ADR((char *), ds, si);
		D_printf("MSDOS: passing ASCIIZ > 1MB to dos %p\n", dst);
		D_printf("%p: '%s'\n", src, src);
		snprintf(dst, MAX_DOS_PATH, "%s", src);
	    }
	    return 0;
	case 0x65:		/* internationalization */
    	    switch (_LO(ax)) {
		case 0:
		    prepare_ems_frame();
		    REG(es) = TRANS_BUFFER_SEG;
		    REG(edi) = 0;
		    MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(es), LWORD(edi)),
			GetSegmentBase(_es) + D_16_32(_edi),
			_LWORD(ecx));
		    break;
		case 1 ... 7:
		    prepare_ems_frame();
		    REG(es) = TRANS_BUFFER_SEG;
		    REG(edi) = 0;
		    break;
		case 0x21:
		case 0xa1:
		    prepare_ems_frame();
		    REG(ds) = TRANS_BUFFER_SEG;
		    REG(edx) = 0;
		    MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(ds), LWORD(edx)),
			GetSegmentBase(_ds) + D_16_32(_edx),
			_LWORD(ecx));
		    break;
		case 0x22:
		case 0xa2:
		    prepare_ems_frame();
		    REG(ds) = TRANS_BUFFER_SEG;
		    REG(edx) = 0;
		    strcpy(SEG_ADR((void *), ds, dx),
			(void *)GetSegmentBaseAddress(_ds) + D_16_32(_edx));
		    break;
	    }
            return 0;
    case 0x71:     /* LFN functions */
        {
        char *src, *dst;
        switch (_LO(ax)) {
        case 0x3B: /* change dir */
        case 0x41: /* delete file */
        case 0x43: /* get file attributes */
	    prepare_ems_frame();
            REG(ds) = TRANS_BUFFER_SEG;
            REG(edx) = 0;
            src = (char *)GetSegmentBaseAddress(_ds) + D_16_32(_edx);
            dst = SEG_ADR((char *), ds, dx);
            snprintf(dst, MAX_DOS_PATH, "%s", src);
            return 0;
        case 0x4E: /* find first file */
	    prepare_ems_frame();
            REG(ds) = TRANS_BUFFER_SEG;
            REG(edx) = 0;
            REG(es) = TRANS_BUFFER_SEG;
            REG(edi) = MAX_DOS_PATH;
            src = (char *)GetSegmentBaseAddress(_ds) + D_16_32(_edx);
            dst = SEG_ADR((char *), ds, dx);
            snprintf(dst, MAX_DOS_PATH, "%s", src);
            return 0;
        case 0x4F: /* find next file */
	    prepare_ems_frame();
            REG(es) = TRANS_BUFFER_SEG;
            REG(edi) = 0;
            MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(es), LWORD(edi)),
			   GetSegmentBase(_es) + D_16_32(_edi),
			   0x13e);
            return 0;
        case 0x47: /* get cur dir */
	    prepare_ems_frame();
            REG(ds) = TRANS_BUFFER_SEG;
            REG(esi) = 0;
            return 0;
	case 0x60: /* canonicalize filename */
	    prepare_ems_frame();
	    REG(ds) = TRANS_BUFFER_SEG;
	    REG(esi) = 0;
	    REG(es) = TRANS_BUFFER_SEG;
	    REG(edi) = MAX_DOS_PATH;
	    src = (char *)GetSegmentBaseAddress(_ds) + D_16_32(_esi);
	    dst = SEG_ADR((char *), ds, si);
	    snprintf(dst, MAX_DOS_PATH, "%s", src);
	    return 0;
        case 0x6c: /* extended open/create */
	    prepare_ems_frame();
            REG(ds) = TRANS_BUFFER_SEG;
            REG(esi) = 0;
            src = (char *)GetSegmentBaseAddress(_ds) + D_16_32(_esi);
            dst = SEG_ADR((char *), ds, si);
            snprintf(dst, MAX_DOS_PATH, "%s", src);
            return 0;
        case 0xA0: /* get volume info */
	    prepare_ems_frame();
            REG(ds) = TRANS_BUFFER_SEG;
            REG(edx) = 0;
            REG(es) = TRANS_BUFFER_SEG;
            REG(edi) = MAX_DOS_PATH;
            src = (char *)GetSegmentBaseAddress(_ds) + D_16_32(_edx);
            dst = SEG_ADR((char *), ds, dx);
            snprintf(dst, MAX_DOS_PATH, "%s", src);
            return 0;
        case 0xA1: /* close find */
            return 0;
        case 0xA6: /* get file info by handle */
            prepare_ems_frame();
            REG(ds) = TRANS_BUFFER_SEG;
            REG(edx) = 0;
            return 0;
        default: /* all other subfuntions currently not supported */
            _eflags |= CF;
            _eax = _eax & 0xFFFFFF00;
            return 1;
        }
        }
	default:
	    break;
	}
	break;
    case 0x33:			/* mouse */
	switch (_LWORD(eax)) {
	case 0x09:		/* Set Mouse Graphics Cursor */
	    prepare_ems_frame();
	    REG(es) = TRANS_BUFFER_SEG;
	    REG(edx) = 0;
	    MEMCPY_DOS2DOS(SEGOFF2LINEAR(REG(es), LWORD(edx)),
		    GetSegmentBase(_es) + D_16_32(_edx),
		    16);
	    return 0;
	case 0x0c:		/* set call back */
	case 0x14: {		/* swap call back */
	    struct pmaddr_s old_callback = MSDOS_CLIENT.mouseCallBack;
	    MSDOS_CLIENT.mouseCallBack.selector = _es;
	    MSDOS_CLIENT.mouseCallBack.offset = D_16_32(_edx);
	    if (_es) {
		D_printf("MSDOS: set mouse callback\n");
		REG(es) = DPMI_SEG;
		REG(edx) = DPMI_OFF + HLT_OFF(MSDOS_mouse_callback);
	    } else {
		D_printf("MSDOS: reset mouse callback\n");
		REG(es) = 0;
		REG(edx) = 0;
	    }
	    if (_LWORD(eax) == 0x14) {
		_es = old_callback.selector;
		if (MSDOS_CLIENT.is_32)
		    _edx = old_callback.offset;
		else
		    _LWORD(edx) = old_callback.offset;
	    }
	  }
	  return 0;
	}
	break;
    }

    if (need_copy_dseg(scp, intr)) {
	unsigned int src, dst;
	int len;
	prepare_ems_frame();
	REG(ds) = TRANS_BUFFER_SEG;
	src = GetSegmentBase(_ds);
	dst = SEGOFF2LINEAR(REG(ds), 0);
	len = min((int)(GetSegmentLimit(_ds) + 1), 0x10000);
	D_printf("MSDOS: whole segment of DS at %x copy to DOS at %x for %#x\n",
		src, dst, len);
	MEMCPY_DOS2DOS(dst, src, len);
    }

    if (need_copy_eseg(scp, intr)) {
	unsigned int  src, dst;
	int len;
	prepare_ems_frame();
	REG(es) = TRANS_BUFFER_SEG;
	src = GetSegmentBase(_es);
	dst = SEGOFF2LINEAR(REG(es), 0);
	len = min((int)(GetSegmentLimit(_es) + 1), 0x10000);
	D_printf("MSDOS: whole segment of ES at %x copy to DOS at %x for %#x\n",
		src, dst, len);
	MEMCPY_DOS2DOS(dst, src, len);
    }
    return 0;
}

/*
 * DANG_BEGIN_FUNCTION msdos_post_extender
 *
 * This function is called after return from real mode DOS service
 * All real mode segment registers are changed to protected mode selectors
 * And if client\'s data buffer is above 1MB, necessary buffer copying
 * is performed.
 *
 * DANG_END_FUNCTION
 */

int msdos_post_extender(struct sigcontext_struct *scp, int intr)
{
    int update_mask = ~0;
#define PRESERVE1(rg) (update_mask &= ~(1 << rg##_INDEX))
#define PRESERVE2(rg1, rg2) (update_mask &= ~((1 << rg1##_INDEX) | (1 << rg2##_INDEX)))
#define SET_REG(rg, val) (PRESERVE1(rg), _##rg = (val))
    D_printf("MSDOS: post_extender: int 0x%x ax=0x%04x\n", intr, _LWORD(eax));

    if (MSDOS_CLIENT.user_dta_sel && intr == 0x21 ) {
	switch (_HI(ax)) {	/* functions use DTA */
	case 0x11: case 0x12:	/* find first/next using FCB */
	case 0x4e: case 0x4f:	/* find first/next */
	    MEMCPY_DOS2DOS(DTA_over_1MB, DTA_under_1MB, 0x80);
	    break;
	}
    }

    if (need_copy_dseg(scp, intr)) {
	unsigned short my_ds;
	unsigned int src, dst;
	int len;
	my_ds = TRANS_BUFFER_SEG;
	src = SEGOFF2LINEAR(my_ds, 0);
	dst = GetSegmentBase(_ds);
	len = min((int)(GetSegmentLimit(_ds) + 1), 0x10000);
	D_printf("MSDOS: DS seg at %x copy back at %x for %#x\n",
		src, dst, len);
	MEMCPY_DOS2DOS(dst, src, len);
    }

    if (need_copy_eseg(scp, intr)) {
	unsigned short my_es;
	unsigned int src, dst;
	int len;
	my_es = TRANS_BUFFER_SEG;
	src = SEGOFF2LINEAR(my_es, 0);
	dst = GetSegmentBase(_es);
	len = min((int)(GetSegmentLimit(_es) + 1), 0x10000);
	D_printf("MSDOS: ES seg at %x copy back at %x for %#x\n",
		src, dst, len);
	MEMCPY_DOS2DOS(dst, src, len);
    }

    switch (intr) {
    case 0x10:			/* video */
	if (_LWORD(eax) == 0x1130) {
	    /* get current character generator infor */
	    SET_REG(es,	ConvertSegmentToDescriptor(REG(es)));
	}
	break;
    case 0x15:
	/* we need to save regs at int15 because AH has the return value */
	if (_HI(ax) == 0xc0) { /* Get Configuration */
                if (REG(eflags)&CF)
                        break;
                SET_REG(es, ConvertSegmentToDescriptor(REG(es)));
        }
	break;
    case 0x2f:
	switch (_LWORD(eax)) {
	    case 0x4310:
                MSDOS_CLIENT.XMS_call = MK_FARt(REG(es), LWORD(ebx));
                SET_REG(es, dpmi_sel());
                SET_REG(ebx, DPMI_SEL_OFF(MSDOS_XMS_call));
		break;
	}
	break;

    case 0x21:
	switch (_HI(ax)) {
	case 0x00:		/* psp kill */
	    PRESERVE1(eax);
	    break;
	case 0x09:		/* print String */
	case 0x1a:		/* set DTA */
	    PRESERVE1(edx);
	    break;
	case 0x11: case 0x12:	/* findfirst/next using FCB */
 	case 0x13:		/* Delete using FCB */
 	case 0x16:		/* Create usring FCB */
 	case 0x17:		/* rename using FCB */
 	    PRESERVE1(edx);
	    MEMCPY_DOS2DOS(GetSegmentBase(_ds) + D_16_32(_edx),
			SEGOFF2LINEAR(REG(ds), LWORD(edx)), 0x50);
	    break;

	case 0x29:		/* Parse a file name for FCB */
	    PRESERVE2(esi, edi);
	    MEMCPY_DOS2DOS(GetSegmentBase(_ds) + D_16_32(_esi),
		/* Warning: SI altered, assume old value = 0, don't touch. */
			    SEGOFF2LINEAR(REG(ds), 0), 0x100);
	    SET_REG(esi, _esi + LWORD(esi));
	    MEMCPY_DOS2DOS(GetSegmentBase(_es) + D_16_32(_edi),
			    SEGOFF2LINEAR(REG(es), LWORD(edi)),  0x50);
	    break;

	case 0x2f:		/* GET DTA */
	    if (SEGOFF2LINEAR(REG(es), LWORD(ebx)) == DTA_under_1MB) {
		if (!MSDOS_CLIENT.user_dta_sel)
		    error("Selector is not set for the translated DTA\n");
		SET_REG(es, MSDOS_CLIENT.user_dta_sel);
		SET_REG(ebx, MSDOS_CLIENT.user_dta_off);
	    } else {
		SET_REG(es, ConvertSegmentToDescriptor(REG(es)));
		/* it is important to copy only the lower word of ebx
		 * and make the higher word zero, so do it here instead
		 * of relying on dpmi.c */
		SET_REG(ebx, LWORD(ebx));
	    }
	    break;

	case 0x34:		/* Get Address of InDOS Flag */
	case 0x35:		/* GET Vector */
	case 0x52:		/* Get List of List */
	    SET_REG(es, ConvertSegmentToDescriptor(REG(es)));
	    break;

	case 0x39:		/* mkdir */
	case 0x3a:		/* rmdir */
	case 0x3b:		/* chdir */
	case 0x3c:		/* creat */
	case 0x3d:		/* Dos OPEN */
	case 0x41:		/* unlink */
	case 0x43:		/* change attr */
	case 0x4e:		/* find first */
	case 0x5b:		/* Create */
	    PRESERVE1(edx);
	    break;

	case 0x50:		/* Set PSP */
	    PRESERVE1(ebx);
	    break;

	case 0x6c:		/*  Extended Open/Create */
	    PRESERVE1(esi);
	    break;

	case 0x55:		/* create & set PSP */
	    PRESERVE1(edx);
	    if (!in_dos_space(_LWORD(edx), 0)) {
		MEMCPY_DOS2DOS(GetSegmentBase(_LWORD(edx)),
		    SEGOFF2LINEAR(LWORD(edx), 0), 0x100);
	    }
	    break;

	case 0x26:		/* create PSP */
	    PRESERVE1(edx);
	    MEMCPY_DOS2DOS(GetSegmentBase(_LWORD(edx)),
		SEGOFF2LINEAR(LWORD(edx), 0), 0x100);
	  break;

        case 0x59:		/* Get EXTENDED ERROR INFORMATION */
	    if(LWORD(eax) == 0x22) { /* only this code has a pointer */
		SET_REG(es, ConvertSegmentToDescriptor(REG(es)));
	    }
	    break;
	case 0x38:
	    if (_LWORD(edx) != 0xffff) { /* get country info */
		PRESERVE1(edx);
		if (LWORD(eflags) & CF)
		    break;
		/* FreeDOS copies only 0x18 bytes */
		MEMCPY_DOS2DOS(GetSegmentBase(_ds) +
		    D_16_32(_edx), SEGOFF2LINEAR(REG(ds), LWORD(edx)), 0x18);
	    }
	    break;
	case 0x47:		/* get CWD */
	    PRESERVE1(esi);
	    if (LWORD(eflags) & CF)
		break;
	    snprintf((char *)(GetSegmentBaseAddress(_ds) +
			D_16_32(_esi)), 0x40, "%s",
		        SEG_ADR((char *), ds, si));
	    D_printf("MSDOS: CWD: %s\n",(char *)(GetSegmentBaseAddress(_ds) +
			D_16_32(_esi)));
	    break;
#if 0
	case 0x48:		/* allocate memory */
	    if (LWORD(eflags) & CF)
		break;
	    SET_REG(eax, ConvertSegmentToDescriptor(LWORD(eax)));
	    break;
#endif
	case 0x4b:		/* EXEC */
	    if (CURRENT_ENV_SEL)
		WRITE_ENV_SEL(ConvertSegmentToDescriptor(CURRENT_ENV_SEL));
	    D_printf("DPMI: Return from DOS exec\n");
	    break;
	case 0x51:		/* get PSP */
	case 0x62:
	    {/* convert environment pointer to a descriptor*/
		unsigned short psp = LWORD(ebx);
		if (psp == CURRENT_PSP && MSDOS_CLIENT.user_psp_sel) {
		    SET_REG(ebx, MSDOS_CLIENT.user_psp_sel);
		} else {
		    SET_REG(ebx, ConvertSegmentToDescriptor_lim(psp, 0xff));
		}
	    }
	    break;
	case 0x53:		/* Generate Drive Parameter Table  */
	    PRESERVE2(esi, ebp);
	    MEMCPY_DOS2DOS(GetSegmentBase(_es) + D_16_32(_ebp),
			    SEGOFF2LINEAR(REG(es), LWORD(ebp)),
			    0x60);
	    break ;
	case 0x56:		/* rename */
	    PRESERVE2(edx, edi);
	    break ;
	case 0x5d:
	    if (_LO(ax) == 0x06 || _LO(ax) == 0x0b) /* get address of DOS swappable area */
				/*        -> DS:SI                     */
		SET_REG(ds, ConvertSegmentToDescriptor(REG(ds)));
	    break;
	case 0x63:		/* Get Lead Byte Table Address */
	    /* _LO(ax)==0 is to test for 6300 on input, LO(ax)==0 for success*/
	    if (_LO(ax) == 0 && LO(ax) == 0)
	        SET_REG(ds, ConvertSegmentToDescriptor(REG(ds)));
	    break;

	case 0x3f:
	    unset_io_buffer();
	    PRESERVE2(edx, ecx);
	    break;
	case 0x40:
	    unset_io_buffer();
	    PRESERVE2(edx, ecx);
	    break;
	case 0x5f:		/* redirection */
	    switch (_LO(ax)) {
	    case 0: case 1:
		break ;
	    case 2 ... 6:
		PRESERVE2(esi, edi);
		MEMCPY_DOS2DOS(GetSegmentBase(_ds)
			+ D_16_32(_esi),
			SEGOFF2LINEAR(REG(ds), LWORD(esi)),
			0x100);
		MEMCPY_DOS2DOS(GetSegmentBase(_es)
			+ D_16_32(_edi),
			SEGOFF2LINEAR(REG(es), LWORD(edi)),
			0x100);
	    }
	    break;
	case 0x60:		/* Canonicalize file name */
	    PRESERVE2(esi, edi);
	    MEMCPY_DOS2DOS(GetSegmentBase(_es)
			+ D_16_32(_edi),
			SEGOFF2LINEAR(REG(es), LWORD(edi)),
			0x80);
	    break;
	case 0x65:		/* internationalization */
	    PRESERVE2(edi, edx);
	    if (LWORD(eflags) & CF)
		break;
    	    switch (_LO(ax)) {
		case 1 ... 7:
		    MEMCPY_DOS2DOS(GetSegmentBase(_es)
			+ D_16_32(_edi),
			SEGOFF2LINEAR(REG(es), LWORD(edi)),
			LWORD(ecx));
		    break;
		case 0x21:
		case 0xa1:
		    MEMCPY_DOS2DOS(GetSegmentBase(_ds)
			+ D_16_32(_edx),
			SEGOFF2LINEAR(REG(ds), LWORD(edx)),
			_LWORD(ecx));
		    break;
		case 0x22:
		case 0xa2:
		    strcpy((void *)GetSegmentBaseAddress(_ds)
			+ D_16_32(_edx),
			SEG_ADR((void *), ds, dx));
		    break;
	    }
	    break;
	case 0x71:		/* LFN functions */
        switch (_LO(ax)) {
        case 0x3B:
        case 0x41:
        case 0x43:
	    PRESERVE1(edx);
            break;
        case 0x4E:
	    PRESERVE1(edx);
            /* fall thru */
        case 0x4F:
	    PRESERVE1(edi);
            if (LWORD(eflags) & CF)
                break;
            MEMCPY_DOS2DOS(GetSegmentBase(_es)
                     + D_16_32(_edi),
                     SEGOFF2LINEAR(REG(es), LWORD(edi)),
                        0x13E);
            break;
        case 0x47:
	    PRESERVE1(esi);
            if (LWORD(eflags) & CF)
                break;
	    snprintf((char *)(GetSegmentBaseAddress(_ds) +
			D_16_32(_esi)), MAX_DOS_PATH, "%s",
		        SEG_ADR((char *), ds, si));
            break;
	case 0x60:
	    PRESERVE2(esi, edi);
	    if (LWORD(eflags) & CF)
		break;
	    snprintf((void *)GetSegmentBaseAddress(_es) +
		D_16_32(_edi), MAX_DOS_PATH, "%s",
		SEG_ADR((char *), es, di));
	    break;
        case 0x6c:
	    PRESERVE1(esi);
            break;
        case 0xA0:
            PRESERVE2(edx, edi);
            if (LWORD(eflags) & CF)
                break;
            snprintf((void *)GetSegmentBaseAddress(_es) +
                     D_16_32(_edi), _LWORD(ecx), "%s",
                     SEG_ADR((char *), es, di));
            break;
        case 0xA6:
            PRESERVE1(edx);
            if (LWORD(eflags) & CF)
                break;
            MEMCPY_DOS2DOS(GetSegmentBase(_ds) +
                           D_16_32(_edx), SEGOFF2LINEAR(REG(ds), LWORD(edx)), 0x34);
            break;
        };

	default:
	    break;
	}
	break;
    case 0x25:			/* Absolute Disk Read */
    case 0x26:			/* Absolute Disk Write */
	/* the flags should be pushed to stack */
	if (MSDOS_CLIENT.is_32) {
	    _esp -= 4;
	    *(uint32_t *)(GetSegmentBaseAddress(_ss) + _esp - 4) =
	      REG(eflags);
	} else {
	    _esp -= 2;
	    *(unsigned short *)(GetSegmentBaseAddress(_ss) +
	      _LWORD(esp) - 2) = LWORD(eflags);
	}
	break;
    case 0x33:			/* mouse */
	switch (_LWORD(eax)) {
	case 0x09:		/* Set Mouse Graphics Cursor */
	case 0x14:		/* swap call back */
	    PRESERVE1(edx);
	    break;
	case 0x19:		/* Get User Alternate Interrupt Address */
	    SET_REG(ebx, ConvertSegmentToDescriptor(LWORD(ebx)));
	    break;
	default:
	    break;
	}
	break;
    }
    restore_ems_frame();
    return update_mask;
}

int msdos_pre_rm(struct sigcontext_struct *scp)
{
  unsigned int lina = SEGOFF2LINEAR(_CS, _IP) - 1;
  void *sp = GetSegmentBaseAddress(_ss) + D_16_32(_esp);

  if (lina == DPMI_ADD + HLT_OFF(MSDOS_mouse_callback)) {
    if (!ValidAndUsedSelector(MSDOS_CLIENT.mouseCallBack.selector)) {
      D_printf("MSDOS: ERROR: mouse callback to unused segment\n");
      return 0;
    }
    D_printf("MSDOS: starting mouse callback\n");
    rm_to_pm_regs(scp, ~0);
    _ds = ConvertSegmentToDescriptor(REG(ds));
    _cs = MSDOS_CLIENT.mouseCallBack.selector;
    _eip = MSDOS_CLIENT.mouseCallBack.offset;

    if (MSDOS_CLIENT.is_32) {
	unsigned int *ssp = sp;
	*--ssp = dpmi_sel();
	*--ssp = DPMI_SEL_OFF(MSDOS_return_from_pm);
	_esp -= 8;
    } else {
	unsigned short *ssp = sp;
	*--ssp = dpmi_sel();
	*--ssp = DPMI_SEL_OFF(MSDOS_return_from_pm);
	_LWORD(esp) -= 4;
    }

  } else if (lina == DPMI_ADD + HLT_OFF(MSDOS_PS2_mouse_callback)) {
    unsigned short *rm_ssp;
    if (!ValidAndUsedSelector(MSDOS_CLIENT.PS2mouseCallBack.selector)) {
      D_printf("MSDOS: ERROR: PS2 mouse callback to unused segment\n");
      return 0;
    }
    D_printf("MSDOS: starting PS2 mouse callback\n");

    _cs = MSDOS_CLIENT.PS2mouseCallBack.selector;
    _eip = MSDOS_CLIENT.PS2mouseCallBack.offset;

    rm_ssp = MK_FP32(LWORD(ss), LWORD(esp) + 4 + 8);

    if (MSDOS_CLIENT.is_32) {
	unsigned int *ssp = sp;
	*--ssp = *--rm_ssp;
	D_printf("data: 0x%x ", *ssp);
	*--ssp = *--rm_ssp;
	D_printf("0x%x ", *ssp);
	*--ssp = *--rm_ssp;
	D_printf("0x%x ", *ssp);
	*--ssp = *--rm_ssp;
	D_printf("0x%x\n", *ssp);
	*--ssp = dpmi_sel();
	*--ssp = DPMI_SEL_OFF(MSDOS_return_from_pm);
	_esp -= 24;
    } else {
	unsigned short *ssp = sp;
	*--ssp = *--rm_ssp;
	D_printf("data: 0x%x ", *ssp);
	*--ssp = *--rm_ssp;
	D_printf("0x%x ", *ssp);
	*--ssp = *--rm_ssp;
	D_printf("0x%x ", *ssp);
	*--ssp = *--rm_ssp;
	D_printf("0x%x\n", *ssp);
	*--ssp = dpmi_sel();
	*--ssp = DPMI_SEL_OFF(MSDOS_return_from_pm);
	_LWORD(esp) -= 12;
    }
  }
  return 1;
}

void msdos_post_rm(struct sigcontext_struct *scp)
{
  pm_to_rm_regs(scp, ~0);
}

int msdos_pre_pm(struct sigcontext_struct *scp)
{
  if (_eip==1+DPMI_SEL_OFF(MSDOS_XMS_call)) {
    D_printf("MSDOS: XMS call to 0x%x:0x%x\n",
	MSDOS_CLIENT.XMS_call.segment, MSDOS_CLIENT.XMS_call.offset);
    pm_to_rm_regs(scp, ~0);
    REG(cs) = DPMI_SEG;
    REG(eip) = DPMI_OFF + HLT_OFF(MSDOS_return_from_rm);
    fake_call_to(MSDOS_CLIENT.XMS_call.segment, MSDOS_CLIENT.XMS_call.offset);
  } else {
    error("MSDOS: unknown pm call %#x\n", _eip);
    return 0;
  }
  return 1;
}

void msdos_post_pm(struct sigcontext_struct *scp)
{
  rm_to_pm_regs(scp, ~0);
}


int msdos_fault(struct sigcontext_struct *scp)
{
    struct sigcontext_struct new_sct;
    int reg;
    unsigned int segment;
    unsigned short desc;

    D_printf("MSDOS: msdos_fault, err=%#lx\n",_err);
    if ((_err & 0xffff) == 0) {	/*  not a selector error */
    /* Why should we "fix" the NULL dereferences? */
    /* Because the unmodified Win3.1 kernel (not WinOS2) needs this */
    /* Yes, but only when LDT is read-only, and then it doesn't work anyway.
     * So lets disable it again and see if someone else needs this. */
#if 0
	char fixed = 0;
	unsigned char * csp;

	csp = (unsigned char *) SEL_ADR(_cs, _eip);

	/* see if client wants to access control registers */
	if (*csp == 0x0f) {
	  if (cpu_trap_0f(csp, scp)) return 1;	/* 1=handled */
	}

	switch (*csp) {
	case 0x2e:		/* cs: */
	    break;		/* do nothing */
	case 0x36:		/* ss: */
	    break;		/* do nothing */
	case 0x26:		/* es: */
	    if (_es == 0) {
		D_printf("MSDOS: client tries to use use gdt 0 as es\n");
		_es = ConvertSegmentToDescriptor(0);
		fixed = 1;
	    }
	    break;
	case 0x64:		/* fs: */
	    if (_fs == 0) {
		D_printf("MSDOS: client tries to use use gdt 0 as fs\n");
		_fs = ConvertSegmentToDescriptor(0);
		fixed = 1;
	    }
	    break;
	case 0x65:		/* gs: */
	    if (_gs == 0) {
		D_printf("MSDOS: client tries to use use gdt 0 as es\n");
		_gs = ConvertSegmentToDescriptor(0);
		fixed = 1;
	    }
	    break;
	case 0xf2:		/* REPNE prefix */
	case 0xf3:		/* REP, REPE */
	    /* this might be a string insn */
	    switch (*(csp+1)) {
	    case 0xaa: case 0xab:		/* stos */
	    case 0xae: case 0xaf:	        /* scas */
		/* only use es */
		if (_es == 0) {
		    D_printf("MSDOS: client tries to use use gdt 0 as es\n");
		    _es = ConvertSegmentToDescriptor(0);
		    fixed = 1;
		}
		break;
	    case 0xa4: case 0xa5:		/* movs */
	    case 0xa6: case 0xa7:         /* cmps */
		/* use both ds and es */
		if (_es == 0) {
		    D_printf("MSDOS: client tries to use use gdt 0 as es\n");
		    _es = ConvertSegmentToDescriptor(0);
		    fixed = 1;
		}
		if (_ds == 0) {
		    D_printf("MSDOS: client tries to use use gdt 0 as ds\n");
		    _ds = ConvertSegmentToDescriptor(0);
		    fixed = 1;
		}
		break;
	    }
	    break;
	case 0x3e:		/* ds: */
	default:		/* assume default is using ds, but if the */
				/* client sets ss to 0, it is totally broken */
	    if (_ds == 0) {
		D_printf("MSDOS: client tries to use use gdt 0 as ds\n");
		_ds = ConvertSegmentToDescriptor(0);
		fixed = 1;
	    }
	    break;
	}
	return fixed;
#else
	return 0;
#endif
    }

    /* now it is a invalid selector error, try to fix it if it is */
    /* caused by an instruction such as mov Sreg,r/m16            */

    copy_context(&new_sct, scp, 0);
    reg = decode_modify_segreg_insn(&new_sct, 1, &segment);
    if (reg == -1)
      return 0;

    if (ValidAndUsedSelector(segment)) {
	/*
	 * The selector itself is OK, but the descriptor (type) is not.
	 * We cannot fix this! So just give up immediately and dont
	 * screw up the context.
	 */
	D_printf("MSDOS: msdos_fault: Illegal use of selector %#x\n", segment);
	return 0;
    }

    D_printf("MSDOS: try mov to a invalid selector 0x%04x\n", segment);

#if 1
    /* only allow using some special GTD\'s */
    if ((segment != 0x0040) && (segment != 0xa000) &&
	(segment != 0xb000) && (segment != 0xb800) &&
	(segment != 0xc000) && (segment != 0xe000) &&
	(segment != 0xf000) && (segment != 0xbf8) &&
	(segment != 0xf800) && (segment != 0xff00) &&
	(segment != 0x38))
	return 0;
#endif

    switch (segment) {
	case 0x38:
	    /* dos4gw sets VCPI descriptors 0x28, 0x30, 0x38 */
	    /* The 0x38 is the "flat data" segment (0,4G) */
	    desc = ConvertSegmentToDescriptor_lim(0, 0xffffffff);
	    break;
	default:
	    /* any other special cases? */
	    desc = (reg != cs_INDEX ? ConvertSegmentToDescriptor(segment) :
		ConvertSegmentToCodeDescriptor(segment));
    }
    if (!desc)
	return 0;

    /* OKay, all the sanity checks passed. Now we go and fix the selector */
    copy_context(scp, &new_sct, 0);
    switch (reg) {
    case es_INDEX:
	_es = desc;
	break;
    case cs_INDEX:
	_cs = desc;
	break;
    case ss_INDEX:
	_ss = desc;
	break;
    case ds_INDEX:
	_ds = desc;
	break;
    case fs_INDEX:
	_fs = desc;
	break;
    case gs_INDEX:
	_gs = desc;
	break;
    }

    /* let's hope we fixed the thing, and return */
    return 1;
}
