/****************************************************************/
/*                                                              */
/*                          inthndlr.c                          */
/*                                                              */
/*    Interrupt Handler and Function dispatcher for Kernel      */
/*                                                              */
/*                      Copyright (c) 1995                      */
/*                      Pasquale J. Villani                     */
/*                      All Rights Reserved                     */
/*                                                              */
/* This file is part of DOS-C.                                  */
/*                                                              */
/* DOS-C is free software; you can redistribute it and/or       */
/* modify it under the terms of the GNU General Public License  */
/* as published by the Free Software Foundation; either version */
/* 2, or (at your option) any later version.                    */
/*                                                              */
/* DOS-C is distributed in the hope that it will be useful, but */
/* WITHOUT ANY WARRANTY; without even the implied warranty of   */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See    */
/* the GNU General Public License for more details.             */
/*                                                              */
/* You should have received a copy of the GNU General Public    */
/* License along with DOS-C; see the file COPYING.  If not,     */
/* write to the Free Software Foundation, 675 Mass Ave,         */
/* Cambridge, MA 02139, USA.                                    */
/*                                                              */
/****************************************************************/
#define MAIN

#include "portab.h"
#include "globals.h"
#include "nls.h"

#ifdef VERSION_STRINGS
BYTE *RcsId =
    "$Id: inthndlr.c,v 1.96 2006/05/20 20:50:44 mceric Exp $";
#endif

#ifdef TSC
STATIC VOID StartTrace(VOID);
static bTraceNext = FALSE;
#endif

#if 0                           /* Very suspicious, passing structure by value??
                                   Deactivated -- 2000/06/16 ska */
/* Special entry for far call into the kernel                           */
#pragma argsused
VOID FAR int21_entry(iregs UserRegs)
{
  int21_handler(UserRegs);
}
#endif

/* Structures needed for int 25 / int 26 */
struct HugeSectorBlock {
  ULONG blkno;
  WORD nblks;
  BYTE FAR *buf;
};

/* Normal entry.  This minimizes user stack usage by avoiding local     */
/* variables needed for the rest of the handler.                        */
/* this here works on the users stack !! and only very few functions 
   are allowed                                                          */
VOID ASMCFUNC int21_syscall(iregs FAR * irp)
{
  Int21AX = irp->AX;

  switch (irp->AH)
  {
    /* Set Interrupt Vector                                         */
    case 0x25:
      setvec(irp->AL, (intvec)MK_FP(irp->DS, irp->DX));
      break;

      /* DosVars - get/set dos variables                              */
    case 0x33:
      switch (irp->AL)
      {
          /* Get Ctrl-C flag                                      */
        case 0x00:
          irp->DL = break_ena;
          break;

          /* Set Ctrl-C flag                                      */
        case 0x01:
          break_ena = irp->DL & 1;
          break;

        case 0x02:             /* andrew schulman: get/set extended control break  */
          {
            UBYTE tmp = break_ena;
            break_ena = irp->DL & 1;
            irp->DL = tmp;
          }
          break;

          /* Get Boot Drive                                       */
        case 0x05:
          irp->DL = BootDrive;
          break;

          /* Get (real) DOS-C version                             */
        case 0x06:
          irp->BL = os_major;
          irp->BH = os_minor;
          irp->DL = rev_number;
          irp->DH = version_flags;      /* bit3:runs in ROM,bit 4: runs in HMA */
          break;

        case 0x03:             /* DOS 7 does not set AL */
        case 0x07:             /* neither here */

        default:               /* set AL=0xFF as error, NOT carry */
          irp->AL = 0xff;
          break;

	  /* set FreeDOS returned version for int 21.30 from BX */
	case 0xfc:             /* 0xfc ... 0xff are FreeDOS extensions */
	  os_setver_major = irp->BL;
	  os_setver_minor = irp->BH;
	  break;

          /* Toggle DOS-C rdwrblock trace dump                    */
#ifdef DEBUG
        case 0xfd:
          bDumpRdWrParms = !bDumpRdWrParms;
          break;
#endif

          /* Toggle DOS-C syscall trace dump                      */
#ifdef DEBUG
        case 0xfe:
          bDumpRegs = !bDumpRegs;
          break;
#endif

          /* Get DOS-C release string pointer                     */
        case 0xff:
          irp->DX = FP_SEG(os_release);
          irp->AX = FP_OFF(os_release);
      }
      break;

    /* Get Interrupt Vector                                           */
    case 0x35:
    {
      intvec p = getvec(irp->AL);
      irp->ES = FP_SEG(p);
      irp->BX = FP_OFF(p);
      break;
    }

      /* Set PSP                                                      */
    case 0x50:
      cu_psp = irp->BX;
      break;

      /* Get PSP                                                      */
    case 0x51:
      /* UNDOCUMENTED: return current psp                             */
    case 0x62:
      irp->BX = cu_psp;

      /* Normal DOS function - DO NOT ARRIVE HERE          */
 /* default: */
  }
}

#ifdef WITHFAT32
      /* DOS 7.0+ FAT32 extended functions */
int int21_fat32(lregs *r)
{
  COUNT rc;
  
  switch (r->AL)
  {
    /* Get extended drive parameter block */
    case 0x02:
    {
      struct dpb FAR *dpb;
      struct xdpbdata FAR *xddp;
    
      if (r->CX < sizeof(struct xdpbdata))
        return DE_INVLDBUF;

      dpb = GetDriveDPB(r->DL, &rc);
      if (rc != SUCCESS)
        return rc;

      /* hazard: no error checking! */
      flush_buffers(dpb->dpb_unit);
      dpb->dpb_flags = M_CHANGED;       /* force reread of drive BPB/DPB */
    
      if (media_check(dpb) < 0)
        return DE_INVLDDRV;
    
      xddp = MK_FP(r->ES, r->DI);
      
      fmemcpy(&xddp->xdd_dpb, dpb, sizeof(struct dpb));
      xddp->xdd_dpbsize = sizeof(struct dpb);

      /* if it doesn't look like an extended DPB, fill in those fields */
      if (!ISFAT32(dpb) && dpb->dpb_xsize != dpb->dpb_size)
      {
        xddp->xdd_dpb.dpb_nfreeclst_un.dpb_nfreeclst_st.dpb_nfreeclst_hi =
          (dpb->dpb_nfreeclst == 0xFFFF ? 0xFFFF : 0);
        dpb16to32(&xddp->xdd_dpb);
        xddp->xdd_dpb.dpb_xfatsize = dpb->dpb_fatsize;
        xddp->xdd_dpb.dpb_xcluster = (dpb->dpb_cluster == 0xFFFF ?
                       0xFFFFFFFFuL : dpb->dpb_cluster);
      }
      break;
    }
    /* Get extended free drive space */
    case 0x03:
    {
      struct xfreespace FAR *xfsp = MK_FP(r->ES, r->DI);
    
      if (r->CX < sizeof(struct xfreespace))
        return DE_INVLDBUF;

      rc = DosGetExtFree(MK_FP(r->DS, r->DX), xfsp);
      if (rc != SUCCESS)
        return rc;
      xfsp->xfs_datasize = sizeof(struct xfreespace);
      xfsp->xfs_version.actual = 0;
      break;
    }
    /* Set DPB to use for formatting */
    case 0x04:
    {
      struct xdpbforformat FAR *xdffp = MK_FP(r->ES, r->DI);
      struct dpb FAR *dpb;
      if (r->CX < sizeof(struct xdpbforformat))
      {
        return DE_INVLDBUF;
      }
      dpb = GetDriveDPB(r->DL, &rc);
      if (rc != SUCCESS)
        return rc;
      
      xdffp->xdff_datasize = sizeof(struct xdpbforformat);
      xdffp->xdff_version.actual = 0;
    
      switch ((UWORD) xdffp->xdff_function)
      {
        case 0x00:
        {
          ULONG nfreeclst = xdffp->xdff_f.setdpbcounts.nfreeclst;
          ULONG cluster = xdffp->xdff_f.setdpbcounts.cluster;
          if (ISFAT32(dpb))
          {
            if ((dpb->dpb_xfsinfosec == 0xffff
                 && (nfreeclst != 0 || cluster != 0))
                || nfreeclst == 1 || nfreeclst > dpb->dpb_xsize
                || cluster == 1 || cluster > dpb->dpb_xsize)
            {
              return DE_INVLDPARM;
            }
            dpb->dpb_xnfreeclst = nfreeclst;
            dpb->dpb_xcluster = cluster;
            write_fsinfo(dpb);
          }
          else
          {
            if ((unsigned)nfreeclst == 1 || (unsigned)nfreeclst > dpb->dpb_size ||
                (unsigned)cluster == 1 || (unsigned)cluster > dpb->dpb_size)
            {
              return DE_INVLDPARM;
            }
            dpb->dpb_nfreeclst = (UWORD)nfreeclst;
            dpb->dpb_cluster = (UWORD)cluster;
          }
          break;
        }
        case 0x01:
        {
          ddt *pddt = getddt(r->DL);
          fmemcpy(&pddt->ddt_bpb, xdffp->xdff_f.rebuilddpb.bpbp,
                  sizeof(bpb));
        }
        case 0x02:
        {
        rebuild_dpb:
            /* hazard: no error checking! */
          flush_buffers(dpb->dpb_unit);
          dpb->dpb_flags = M_CHANGED;
          
          if (media_check(dpb) < 0)
            return DE_INVLDDRV;
          break;
        }
        case 0x03:
        {
          struct buffer FAR *bp;
          bpb FAR *bpbp;
          DWORD newmirroring =
            xdffp->xdff_f.setmirroring.newmirroring;
      
          if (newmirroring != -1
              && (ISFAT32(dpb)
                  && (newmirroring & ~(0xf | 0x80))))
          {
            return DE_INVLDPARM;
          }
          xdffp->xdff_f.setmirroring.oldmirroring =
            (ISFAT32(dpb) ? dpb->dpb_xflags : 0);
          if (newmirroring != -1 && ISFAT32(dpb))
          {
            bp = getblock(1, dpb->dpb_unit);
            bp->b_flag &= ~(BFR_DATA | BFR_DIR | BFR_FAT);
            bp->b_flag |= BFR_VALID | BFR_DIRTY;
            bpbp = (bpb FAR *) & bp->b_buffer[BT_BPB];
            bpbp->bpb_xflags = (UWORD)newmirroring;
          }
          goto rebuild_dpb;
        }
        case 0x04:
        {
          struct buffer FAR *bp;
          bpb FAR *bpbp;
          DWORD rootclst = xdffp->xdff_f.setroot.newrootclst;
          if (!ISFAT32(dpb)
              || (rootclst != -1
                  && (rootclst == 1
                      || (ULONG)rootclst > dpb->dpb_xsize)))
          {
            return DE_INVLDPARM;
          }
          xdffp->xdff_f.setroot.oldrootclst = dpb->dpb_xrootclst;
          if (rootclst != -1)
          {
            bp = getblock(1, dpb->dpb_unit);
            bp->b_flag &= ~(BFR_DATA | BFR_DIR | BFR_FAT);
            bp->b_flag |= BFR_VALID | BFR_DIRTY;
            bpbp = (bpb FAR *) & bp->b_buffer[BT_BPB];
            bpbp->bpb_xrootclst = rootclst;
          }
          goto rebuild_dpb;
        }
      }
    
      break;
    }
    /* Extended absolute disk read/write */
    /* TODO(vlp) consider using of the 13-14th bits of SI */
    case 0x05:
    {
      struct HugeSectorBlock FAR *SectorBlock =
        (struct HugeSectorBlock FAR *)MK_FP(r->DS, r->BX);
      UBYTE mode;
      
      if (r->CX != 0xffff || ((r->SI & 1) == 0 && r->SI != 0)
          || (r->SI & ~0x6001))
      {
        return DE_INVLDPARM;
      }
    
      if (r->DL > lastdrive || r->DL == 0)
        return -0x207;
    
      if (r->SI == 0)
        mode = DSKREADINT25;
      else
        mode = DSKWRITEINT26;
    
      r->AX =
        dskxfer(r->DL - 1, SectorBlock->blkno, SectorBlock->buf,
                SectorBlock->nblks, mode);
    
      if (mode == DSKWRITEINT26)
        if (r->AX <= 0)
          setinvld(r->DL - 1);
      
      if (r->AX > 0)
        return -0x20c;
      break;
    }
  default:
    return DE_INVLDFUNC;
  }
  return SUCCESS;
}
#endif

VOID ASMCFUNC int21_service(iregs FAR * r)
{
  COUNT rc;
  long lrc;
  lregs lr; /* 8 local registers (ax, bx, cx, dx, si, di, ds, es) */

#define FP_DS_DX (MK_FP(lr.DS, lr.DX))
#define FP_ES_DI (MK_FP(lr.ES, lr.DI))

#define CLEAR_CARRY_FLAG()  r->FLAGS &= ~FLG_CARRY
#define SET_CARRY_FLAG()    r->FLAGS |= FLG_CARRY

  ((psp FAR *) MK_FP(cu_psp, 0))->ps_stack = (BYTE FAR *) r;

  fmemcpy(&lr, r, sizeof(lregs) - 4);
  lr.DS = r->DS;
  lr.ES = r->ES;

dispatch:

#ifdef DEBUG
  if (bDumpRegs)
  {
    fmemcpy(&error_regs, user_r, sizeof(iregs));
    printf("System call (21h): %02x\n", user_r->AX);
    dump_regs = TRUE;
    dump();
  }
#endif

  if ((lr.AH >= 0x38 && lr.AH <= 0x4F) || (lr.AH >= 0x56 && lr.AH <= 0x5c) ||
      (lr.AH >= 0x5e && lr.AH <= 0x60) || (lr.AH >= 0x65 && lr.AH <= 0x6a) ||
      lr.AH == 0x6c)
  {
    CLEAR_CARRY_FLAG();
    if (lr.AH != 0x59)
      CritErrCode = SUCCESS;
  }
  /* Clear carry by default for these functions */


  /* Check for Ctrl-Break */
  if (break_ena || (lr.AH >= 1 && lr.AH <= 5) || (lr.AH >= 8 && lr.AH <= 0x0b))
    check_handle_break(&syscon);

  /* The dispatch handler                                         */
  switch (lr.AH)
  {
      /* int 21h common error handler                                 */
    case 0x64:
    	goto error_invalid;

      /* case 0x00:   --> Simulate a DOS-4C-00 */

      /* Read Keyboard with Echo                      */
    case 0x01:
  DOS_01:
      lr.AL = read_char_stdin(TRUE);
      write_char_stdout(lr.AL);
      break;

      /* Display Character                                            */
    case 0x02:
  DOS_02:
      lr.AL = lr.DL;
      write_char_stdout(lr.AL);
      break;

      /* Auxiliary Input                                                      */
    case 0x03:
    {
      int sft_idx = get_sft_idx(STDAUX);
      lr.AL = read_char(sft_idx, sft_idx, TRUE);
      break;
    }

      /* Auxiliary Output                                                     */
    case 0x04:
      write_char(lr.DL, get_sft_idx(STDAUX));
      break;
      /* Print Character                                                      */
    case 0x05:
      write_char(lr.DL, get_sft_idx(STDPRN));
      break;

      /* Direct Console I/O                                            */
    case 0x06:
  DOS_06:
      if (lr.DL != 0xff)
        goto DOS_02;

      lr.AL = 0x00;
      r->FLAGS |= FLG_ZERO;
      if (StdinBusy()) {
        DosIdle_int();
        break;
      }

      r->FLAGS &= ~FLG_ZERO;
      /* fall through */

      /* Direct Console Input                                         */
    case 0x07:
  DOS_07:
      lr.AL = read_char_stdin(FALSE);
      break;

      /* Read Keyboard Without Echo                                   */
    case 0x08:
  DOS_08:
      lr.AL = read_char_stdin(TRUE);
      break;

      /* Display String                                               */
    case 0x09:
      {
        unsigned char c;
        unsigned char FAR *bp = FP_DS_DX;

        while ((c = *bp++) != '$')
          write_char_stdout(c);

        lr.AL = c;
      }
      break;

      /* Buffered Keyboard Input                                      */
    case 0x0a:
  DOS_0A:
      read_line(get_sft_idx(STDIN), get_sft_idx(STDOUT), FP_DS_DX);
      break;

      /* Check Stdin Status                                           */
    case 0x0b:
      lr.AL = 0xFF;
      if (StdinBusy())
        lr.AL = 0x00;
      break;

      /* Flush Buffer, Read Keyboard                                 */
    case 0x0c:
    {
      struct dhdr FAR *dev = sft_to_dev(get_sft(STDIN));
      if (dev)
        con_flush(&dev);
      switch (lr.AL)
      {
      case 0x01: goto DOS_01;
      case 0x06: goto DOS_06;
      case 0x07: goto DOS_07;
      case 0x08: goto DOS_08;
      case 0x0a: goto DOS_0A;
      }
      lr.AL = 0x00;
      break;
    }

      /* Reset Drive                                                  */
    case 0x0d:
      flush();
      break;

      /* Set Default Drive                                            */
    case 0x0e:
      lr.AL = DosSelectDrv(lr.DL);
      break;

    case 0x0f:
      lr.AL = FcbOpen(FP_DS_DX, O_FCB | O_LEGACY | O_OPEN | O_RDWR);
      break;

    case 0x10:
      lr.AL = FcbClose(FP_DS_DX);
      break;

    case 0x11:
      lr.AL = FcbFindFirstNext(FP_DS_DX, TRUE);
      break;

    case 0x12:
      lr.AL = FcbFindFirstNext(FP_DS_DX, FALSE);
      break;

    case 0x13:
      lr.AL = FcbDelete(FP_DS_DX);
      break;

    case 0x14:
      /* FCB read */
      lr.AL = FcbReadWrite(FP_DS_DX, 1, XFR_READ);
      break;

    case 0x15:
      /* FCB write */
      lr.AL = FcbReadWrite(FP_DS_DX, 1, XFR_WRITE);
      break;

    case 0x16:
      lr.AL = FcbOpen(FP_DS_DX, O_FCB | O_LEGACY | O_CREAT | O_TRUNC | O_RDWR);
      break;

    case 0x17:
      lr.AL = FcbRename(FP_DS_DX);
      break;

    default:
#ifdef DEBUG
      printf("Unsupported INT21 AH = 0x%x, AL = 0x%x.\n", lr.AH, lr.AL);
#endif
      /* Fall through. */

      /* CP/M compatibility functions                                 */
    case 0x18:
    case 0x1d:
    case 0x1e:
    case 0x20:
#ifndef TSC
    case 0x61:
#endif
    case 0x6b:
      lr.AL = 0;
      break;

      /* Get Default Drive                                            */
    case 0x19:
      lr.AL = default_drive;
      break;

      /* Set DTA                                                      */
    case 0x1a:
      dta = FP_DS_DX;
      break;

      /* Get Default Drive Data                                       */
    case 0x1b:
      lr.DL = 0;
      /* fall through */

      /* Get Drive Data                                               */
    case 0x1c:
      {
        BYTE FAR *p;

        p = FatGetDrvData(lr.DL, &lr.AL, &lr.CX, &lr.DX);
        lr.DS = FP_SEG(p);
        lr.BX = FP_OFF(p);
      }
      break;

      /* Get default DPB                                              */
      /* case 0x1f: see case 0x32 */

      /* Random read using FCB: fields not updated
         (XFR_RANDOM should not be used here) */
    case 0x21:
      lr.AL = FcbRandomIO(FP_DS_DX, XFR_READ);
      break;

      /* Random write using FCB */
    case 0x22:
      lr.AL = FcbRandomIO(FP_DS_DX, XFR_WRITE);
      break;

      /* Get file size in records using FCB */
    case 0x23:
      lr.AL = FcbGetFileSize(FP_DS_DX);
      break;

      /* Set random record field in FCB */
    case 0x24:
      FcbSetRandom(FP_DS_DX);
      break;

      /* Set Interrupt Vector                                         */
      /* case 0x25: handled above (re-entrant)                        */

      /* Dos Create New Psp                                           */
    case 0x26:
      new_psp(lr.DX, r->CS);
      break;

      /* Read random record(s) using FCB */
    case 0x27:
      lr.AL = FcbRandomBlockIO(FP_DS_DX, &lr.CX, XFR_READ | XFR_FCB_RANDOM);
      break;

      /* Write random record(s) using FCB */
    case 0x28:
      lr.AL = FcbRandomBlockIO(FP_DS_DX, &lr.CX, XFR_WRITE | XFR_FCB_RANDOM);
      break;

      /* Parse File Name                                              */
    case 0x29:
      lr.SI = FcbParseFname(&lr.AL, MK_FP(lr.DS, lr.SI), FP_ES_DI);
      break;

      /* Get Date                                                     */
    case 0x2a:
      lr.AL = DosGetDate((struct dosdate *)&lr.CX);
      break;

      /* Set Date                                                     */
    case 0x2b:
      lr.AL = DosSetDate ((struct dosdate*)&lr.CX) == SUCCESS ? 0 : 0xFF;
      break;

      /* Get Time                                                     */
    case 0x2c:
      DosGetTime((struct dostime *)&lr.CL);
      break;

      /* Set Time                                                     */
    case 0x2d:
      lr.AL = DosSetTime ((struct dostime*)&lr.CX) == SUCCESS ? 0 : 0xFF;
      break;

      /* Set verify flag                                              */
    case 0x2e:
      verify_ena = lr.AL & 1;
      break;

      /* Get DTA                                                      */
    case 0x2f:
      lr.ES = FP_SEG(dta);
      lr.BX = FP_OFF(dta);
      break;

      /* Get (editable) DOS Version                                   */
    case 0x30:
      lr.AL = os_setver_major;
      lr.AH = os_setver_minor;
      lr.BH = OEM_ID;
      lr.BL = REVISION_SEQ;
      lr.CX = 0; /* do not set this to a serial number!
                    32RTM won't like non-zero values   */

      if (ReturnAnyDosVersionExpected)
      {
        /* TE for testing purpose only and NOT 
           to be documented:
           return programs, who ask for version == XX.YY
           exactly this XX.YY. 
           this makes most MS programs more happy.
         */
        UBYTE FAR *retp = MK_FP(r->cs, r->ip);

        if (retp[0] == 0x3d &&  /* cmp ax, xxyy */
            (retp[3] == 0x75 || retp[3] == 0x74))       /* je/jne error    */
        {
          lr.AL = retp[1];
          lr.AH = retp[2];
        }
        else if (retp[0] == 0x86 &&     /* xchg al,ah   */
                 retp[1] == 0xc4 && retp[2] == 0x3d &&  /* cmp ax, xxyy */
                 (retp[5] == 0x75 || retp[5] == 0x74))  /* je/jne error    */
        {
          lr.AL = retp[4];
          lr.AH = retp[3];
        }

      }

      break;

      /* Keep Program (Terminate and stay resident) */
    case 0x31:
      DosMemChange(cu_psp, lr.DX < 6 ? 6 : lr.DX, 0);
      return_code = lr.AL | 0x300;
      tsr = TRUE;
      return_user();
      break;

      /* Get default BPB */
    case 0x1f:
      /* Get DPB                                                      */
    case 0x32:
      /* r->DL is NOT changed by MS 6.22 */
      /* INT21/32 is documented to reread the DPB */
      {
        int drv = (lr.DL == 0 || lr.AH == 0x1f) ? default_drive : lr.DL - 1;
        struct dpb FAR *dpb = get_dpb(drv);
        
        if (dpb == NULL)
        {
          CritErrCode = -DE_INVLDDRV;
          lr.AL = 0xFF;
          break;
        }
        /* hazard: no error checking! */        
        flush_buffers(dpb->dpb_unit);
        dpb->dpb_flags = M_CHANGED;     /* force flush and reread of drive BPB/DPB */

#ifdef WITHFAT32
        if (media_check(dpb) < 0 || ISFAT32(dpb))
#else
        if (media_check(dpb) < 0)
#endif
        {
          lr.AL = 0xff;
          CritErrCode = -DE_INVLDDRV;
          break;
        }
        lr.DS = FP_SEG(dpb);
        lr.BX = FP_OFF(dpb);
        lr.AL = 0;
      }

      break;
/*
    case 0x33:  
    see int21_syscall
*/
      /* Get InDOS flag                                               */
    case 0x34:
      lr.ES = FP_SEG(&InDOS);
      lr.BX = FP_OFF(&InDOS);
      break;

      /* Get Interrupt Vector                                         */
      /* case 0x35: handled above (reentrant)                         */

      /* Dos Get Disk Free Space                                      */
    case 0x36:
      lr.AX = DosGetFree(lr.DL, &lr.BX, &lr.CX, &lr.DX);
      break;

      /* Undocumented Get/Set Switchar                                */
    case 0x37:
      switch (lr.AL)
      {
          /* Get switch character */
        case 0x00:
          lr.DL = switchar;
          lr.AL = 0x00;
          break;

          /* Set switch character */
        case 0x01:
          switchar = lr.DL;
          lr.AL = 0x00;
          break;

        default:
          goto error_invalid;
      }
      break;

      /* Get/Set Country Info                                         */
    case 0x38:
      {
        UWORD cntry = lr.AL;

        if (cntry == 0xff)
          cntry = lr.BX;

        if (0xffff == lr.DX)
        {
          /* Set Country Code */
          rc = DosSetCountry(cntry);
        }
        else
        {
          if (cntry == 0)
            cntry--;
          /* Get Country Information */
          rc = DosGetCountryInformation(cntry, FP_DS_DX);
          if (rc >= SUCCESS)
          {
            /* HACK FIXME */
            if (cntry == (UWORD) - 1)
              cntry = 1;
            /* END OF HACK */
            lr.AX = lr.BX = cntry;
          }
        }
        goto short_check;
      }

      /* Dos Create Directory                                         */
    case 0x39:
      /* Dos Remove Directory                                         */
    case 0x3a:
      rc = DosMkRmdir(FP_DS_DX, lr.AH);
      goto short_check;

      /* Dos Change Directory                                         */
    case 0x3b:
      rc = DosChangeDir(FP_DS_DX);
      goto short_check;

      /* Dos Create File                                              */
    case 0x3c:
      lrc = DosOpen(FP_DS_DX, O_LEGACY | O_RDWR | O_CREAT | O_TRUNC, lr.CL);
      goto long_check;

      /* Dos Open                                                     */
    case 0x3d:
      lrc = DosOpen(FP_DS_DX, O_LEGACY | O_OPEN | lr.AL, 0);
      goto long_check;

      /* Dos Close                                                    */
    case 0x3e:
      rc = DosClose(lr.BX);
      goto short_check;

      /* Dos Read                                                     */
    case 0x3f:
      lrc = DosRead(lr.BX, lr.CX, FP_DS_DX);
      goto long_check;

      /* Dos Write                                                    */
    case 0x40:
      lrc = DosWrite(lr.BX, lr.CX, FP_DS_DX);
      goto long_check;

      /* Dos Delete File                                              */
    case 0x41:
      rc = DosDelete((BYTE FAR *) FP_DS_DX, D_ALL);
      goto short_check;

      /* Dos Seek                                                     */
    case 0x42:
      if (lr.AL > 2)
        goto error_invalid;
      lrc = DosSeek(lr.BX, (LONG)((((ULONG) (lr.CX)) << 16) | lr.DX), lr.AL);
      if (lrc == -1)
      {
        lrc = DE_INVLDHNDL;
      }
      else
      {
        lr.DX = (UWORD)(lrc >> 16);
        lrc = (UWORD) lrc;
      }
      goto long_check;

      /* Get/Set File Attributes                                      */
    case 0x43:
      switch (lr.AL)
      {
        case 0x00:
          rc = DosGetFattr((BYTE FAR *) FP_DS_DX);
          if (rc >= SUCCESS)
            lr.CX = rc;
          break;

        case 0x01:
          rc = DosSetFattr((BYTE FAR *) FP_DS_DX, lr.CX);
          lr.AX = lr.CX;
          break;

        default:
          goto error_invalid;
      }
      goto short_check;

      /* Device I/O Control                                           */
    case 0x44:
      rc = DosDevIOctl(&lr);      /* can set critical error code! */

      if (rc < SUCCESS)
      {
        lr.AX = -rc;
        if (rc != DE_DEVICE && rc != DE_ACCESS)
          CritErrCode = lr.AX;
        goto error_carry;
      }
      break;

      /* Duplicate File Handle                                        */
    case 0x45:
      lrc = DosDup(lr.BX);
      goto long_check;

      /* Force Duplicate File Handle                                  */
    case 0x46:
      rc = DosForceDup(lr.BX, lr.CX);
      goto short_check;

      /* Get Current Directory                                        */
    case 0x47:
      rc = DosGetCuDir(lr.DL, MK_FP(lr.DS, lr.SI));
      lr.AX = 0x0100;         /*jpp: from interrupt list */
      goto short_check;

      /* Allocate memory */
    case 0x48:
      if ((rc = DosMemAlloc(lr.BX, mem_access_mode, &lr.AX, &lr.BX)) < 0)
      {
        DosMemLargest(&lr.BX);
        if (DosMemCheck() != SUCCESS)
          panic("MCB chain corrupted");
        goto error_exit;
      }
      lr.AX++;   /* DosMemAlloc() returns seg of MCB rather than data */
      break;

      /* Free memory */
    case 0x49:
      if ((rc = DosMemFree(lr.ES - 1)) < SUCCESS)
      {
        if (DosMemCheck() != SUCCESS)
          panic("MCB chain corrupted");
        goto error_exit;
      }
      break;

      /* Set memory block size */
    case 0x4a:
        if (DosMemCheck() != SUCCESS)
          panic("before 4a: MCB chain corrupted");

      if ((rc = DosMemChange(lr.ES, lr.BX, &lr.BX)) < 0)
      {
#if 0
        if (cu_psp == lr.ES)
        {
          psp FAR *p = MK_FP(cu_psp, 0);
          p->ps_size = lr.BX + cu_psp;
        }
#endif
        if (DosMemCheck() != SUCCESS)
          panic("after 4a: MCB chain corrupted");
        goto error_exit;
      }
      lr.AX = lr.ES; /* Undocumented MS-DOS behaviour expected by BRUN45! */
      break;

      /* Load and Execute Program */
    case 0x4b:
      break_flg = FALSE;

      rc = DosExec(lr.AL, MK_FP(lr.ES, lr.BX), FP_DS_DX);
      goto short_check;

      /* Terminate Program                                            */
    case 0x00:
      lr.AX = 0x4c00;

      /* End Program                                                  */
    case 0x4c:
      if (((psp FAR *)MK_FP(cu_psp, 0))->ps_parent == cu_psp)
        break;
      tsr = FALSE;
      rc = 0;
      if (ErrorMode)
      {
        ErrorMode = FALSE;
        rc = 0x200;
      }
      else if (break_flg)
      {
        break_flg = FALSE;
        rc = 0x100;
      }
      return_code = lr.AL | rc;
      if (DosMemCheck() != SUCCESS)
        panic("MCB chain corrupted");
#ifdef TSC
      StartTrace();
#endif
      return_user();
      break;

      /* Get Child-program Return Value                               */
    case 0x4d:
      lr.AX = return_code;
      /* needs to be cleared (RBIL) */
      return_code = 0;
      break;

      /* Dos Find First                                               */
    case 0x4e:
      /* dta for this call is set on entry.  This     */
      /* needs to be changed for new versions.        */
      rc = DosFindFirst(lr.CX, FP_DS_DX);
      lr.AX = 0;
      goto short_check;

      /* Dos Find Next                                                */
    case 0x4f:
      /* dta for this call is set on entry.  This     */
      /* needs to be changed for new versions.        */
      rc = DosFindNext();
      lr.AX = 0;
      goto short_check;
/*
    case 0x50:  
    case 0x51:
    see int21_syscall
*/
      /* ************UNDOCUMENTED************************************* */
      /* Get List of Lists                                            */
    case 0x52:
      lr.ES = FP_SEG(&DPBp);
      lr.BX = FP_OFF(&DPBp);
      break;

    case 0x53:
      /*  DOS 2+ internal - TRANSLATE BIOS PARAMETER BLOCK TO DRIVE PARAM BLOCK */
      bpb_to_dpb((bpb FAR *) MK_FP(lr.DS, lr.SI),
                 (struct dpb FAR *)MK_FP(lr.ES, r->BP)
#ifdef WITHFAT32
                 , (lr.CX == 0x4558 && lr.DX == 0x4152)
#endif
          );
      break;

      /* Get verify state                                             */
    case 0x54:
      lr.AL = verify_ena;
      break;

      /* ************UNDOCUMENTED************************************* */
      /* Dos Create New Psp & set p_size                              */
    case 0x55:
      child_psp(lr.DX, cu_psp, lr.SI);
      cu_psp = lr.DX;
      break;

      /* Dos Rename                                                   */
    case 0x56:
      rc = DosRename(FP_DS_DX, FP_ES_DI);
      goto short_check;

      /* Get/Set File Date and Time                                   */
    case 0x57:
      switch (lr.AL)
      {
        case 0x00:
          rc = DosGetFtime((COUNT) lr.BX,       /* Handle               */
                           &lr.DX,        /* FileDate             */
                           &lr.CX);       /* FileTime             */
          break;

        case 0x01:
          rc = DosSetFtime((COUNT) lr.BX,       /* Handle               */
                           (date) lr.DX,        /* FileDate             */
                           (time) lr.CX);       /* FileTime             */
          break;

        default:
          goto error_invalid;
      }
      goto short_check;

      /* Get/Set Allocation Strategy                                  */
    case 0x58:
      switch (lr.AL)
      {
        case 0x00:
          lr.AL = mem_access_mode;
          lr.AH = 0;
          break;

        case 0x01:
          if (lr.BL > LAST_FIT_U ||                 /* 0x82       */
              (lr.BL & FIT_MASK) > LAST_FIT)        /* 0x3f, 0x02 */
            goto error_invalid;
          mem_access_mode = lr.BL;
          break;

        case 0x02:
          lr.AL = uppermem_link;
          break;

        case 0x03:
	  if (uppermem_root != 0xffff)	  /* always error if not exists */
          {
            DosUmbLink(lr.BX);
            break;
          }
          /* else fall through */

        default:
          goto error_invalid;
#ifdef DEBUG
        case 0xff:
          show_chain();
          break;
#endif
      }
      break;

      /* Get Extended Error */
    case 0x59:
      lr.AX = CritErrCode;
      lr.ES = FP_SEG(CritErrDev);
      lr.DI = FP_OFF(CritErrDev);
      lr.CH = CritErrLocus;
      lr.BH = CritErrClass;
      lr.BL = CritErrAction;
      break;

      /* Create Temporary File */
    case 0x5a:
      lrc = DosMkTmp(FP_DS_DX, lr.CX);
      goto long_check;

      /* Create New File */
    case 0x5b:
      lrc = DosOpen(FP_DS_DX, O_LEGACY | O_RDWR | O_CREAT, lr.CX);
      goto long_check;

/* /// Added for SHARE.  - Ron Cemer */
      /* Lock/unlock file access */
    case 0x5c:
      rc = DosLockUnlock
           (lr.BX, ((unsigned long)lr.CX << 16) | lr.DX,
                   ((unsigned long)lr.SI << 16) | lr.DI, lr.AL != 0);
      if (rc != SUCCESS)
        goto error_exit;
      break;
/* /// End of additions for SHARE.  - Ron Cemer */

      /* UNDOCUMENTED: server, share.exe and sda function             */
    case 0x5d:
      switch (lr.AL)
      {
          /* Remote Server Call */
        case 0x00:
          fmemcpy(&lr, FP_DS_DX, sizeof(lr));
          goto dispatch;

        case 0x06:
          lr.DS = FP_SEG(internal_data);
          lr.SI = FP_OFF(internal_data);
          lr.CX = swap_indos - internal_data;
          lr.DX = swap_always - internal_data;
          CLEAR_CARRY_FLAG();
          break;

        case 0x07:
        case 0x08:
        case 0x09:
          rc = remote_printredir(lr.DX, Int21AX);
          CLEAR_CARRY_FLAG();
          if (rc != SUCCESS)
            goto error_exit;
          break;

          /* Set Extended Error */
        case 0x0a:
          {
            #define er (* MK_PTR (lregs, lr.DS, lr.DX))
            CritErrCode        = er.AX;
            CritErrDev         = MK_FP(er.ES, er.DI);
            CritErrLocus       = er.CH;
            CritErrClass       = er.BH;
            CritErrAction      = er.BL;
            #undef er
            CLEAR_CARRY_FLAG();
            break;
          }

        default:
          CritErrCode = SUCCESS;
          goto error_invalid;
      }
      break;

    case 0x5e:
      switch (lr.AL)
      {
        case 0x00:
          lr.CX = get_machine_name(FP_DS_DX);
          break;

        case 0x01:
          set_machine_name(FP_DS_DX, lr.CX);
          break;

        default:
          rc = (int)network_redirector_mx(REM_PRINTSET, &lr, (void *)Int21AX);
          goto short_check;
      }
      break;

    case 0x5f:
      if (lr.AL == 7 || lr.AL == 8)
      {
        if (lr.DL < lastdrive)
        {
          struct cds FAR *cdsp = CDSp + lr.DL;
          if (FP_OFF(cdsp->cdsDpb))	/* letter of physical drive?	*/
          {
            cdsp->cdsFlags &= ~CDSPHYSDRV;
            if (lr.AL == 7)
              cdsp->cdsFlags |= CDSPHYSDRV;
            break;
          }
        }
        rc = DE_INVLDDRV;
        goto error_exit;
      }
      else
      {
        rc = (int)network_redirector_mx(REM_DOREDIRECT, &lr, (void *)Int21AX);
        /* the remote function manipulates *r directly !,
           so we should not copy lr to r here            */
        if (rc != SUCCESS)
        {
          CritErrCode = -rc;      /* Maybe set */
          SET_CARRY_FLAG();
        }
        r->AX = -rc;
        goto real_exit;
      }

    case 0x60:                 /* TRUENAME */
      rc = DosTruename(MK_FP(lr.DS, lr.SI), adjust_far(FP_ES_DI));
      goto short_check;

#ifdef TSC
      /* UNDOCUMENTED: no-op                                          */
      /*                                                              */
      /* DOS-C: tsc support                                           */
    case 0x61:
#ifdef DEBUG
      switch (lr.AL)
      {
        case 0x01:
          bTraceNext = TRUE;
          break;

        case 0x02:
          bDumpRegs = FALSE;
          break;
      }
#endif
      lr.AL = 0x00;
      break;
#endif

      /* UNDOCUMENTED: return current psp                             
         case 0x62: is in int21_syscall
         lr.BX = cu_psp;
         break;
       */

      /* UNDOCUMENTED: Double byte and korean tables                  */
    case 0x63:
      {
#if 0
        /* not really supported, but will pass.                 */
        lr.AL = 0x00;           /*jpp: according to interrupt list */
        /*Bart: fails for PQDI and WATCOM utilities: 
           use the above again */
#endif
        switch (lr.AL)
        {
          case 0:
            lr.DS = FP_SEG(&nlsDBCSHardcoded);
            lr.SI = FP_OFF(&nlsDBCSHardcoded);
            break;
          case 1: /* set Korean Hangul input method to DL 0/1 */
            lr.AL = 0xff;	/* flag error (AL would be 0 if okay) */
            break;
          case 2: /* get Korean Hangul input method setting to DL */
            lr.AL = 0xff;	/* flag error, do not set DL */
            break;
          default:	/* is this the proper way to handle invalid AL? */
            rc = -1;
            goto error_exit;
        }
        break;
      }
/*
    case 0x64:
      see above (invalid)
*/

      /* Extended country info                                        */
    case 0x65:
      switch (lr.AL)
      {
        case 0x20:             /* upcase single character */
          lr.DL = DosUpChar(lr.DL);
          break;
        case 0x21:             /* upcase memory area */
          DosUpMem(FP_DS_DX, lr.CX);
          break;
        case 0x22:             /* upcase ASCIZ */
          DosUpString(FP_DS_DX);
          break;
        case 0xA0:             /* upcase single character of filenames */
          lr.DL = DosUpFChar(lr.DL);
          break;
        case 0xA1:             /* upcase memory area of filenames */
          DosUpFMem(FP_DS_DX, lr.CX);
          break;
        case 0xA2:             /* upcase ASCIZ of filenames */
          DosUpFString(FP_DS_DX);
          break;
        case 0x23:             /* check Yes/No response */
          lr.AX = DosYesNo(lr.DL);
          break;
        default:
#ifdef NLS_DEBUG
          if ((rc = DosGetData(lr.AL, lr.BX, lr.DX, lr.CX, FP_ES_DI)) < 0)
          {
            printf("DosGetData() := %d\n", rc);
            goto error_exit;
          }
          printf("DosGetData() returned successfully\n");
          break;
#else
          rc = DosGetData(lr.AL, lr.BX, lr.DX, lr.CX, FP_ES_DI);
          goto short_check;
#endif
      }
      break;

      /* Code Page functions */
    case 0x66:
        switch (lr.AL)
        {
          case 1:
            rc = DosGetCodepage(&lr.BX, &lr.DX);
            break;
          case 2:
            rc = DosSetCodepage(lr.BX, lr.DX);
            break;

          default:
            goto error_invalid;
        }
        if (rc != SUCCESS)
          goto error_exit;
        break;

      /* Set Max file handle count */
    case 0x67:
      rc = SetJFTSize(lr.BX);
      goto short_check;

      /* Flush file buffer -- COMMIT FILE.  */
    case 0x68:
    case 0x6a:
      rc = DosCommit(lr.BX);
      goto short_check;

      /* Get/Set Serial Number */
    case 0x69:
      rc = (lr.BL == 0 ? default_drive : lr.BL - 1);
      if (lr.AL < 2)
      {
        if (get_cds(rc) == NULL)
        {
          rc = DE_INVLDDRV;
          goto error_exit;
        }
        if (get_dpb(rc) != NULL)
        {
          UWORD saveCX = lr.CX;
          lr.CX = lr.AL == 0 ? 0x0866 : 0x0846;
          lr.AL = 0x0d;
          rc = DosDevIOctl(&lr);
          lr.CX = saveCX;
          goto short_check;
        }
      }
      goto error_invalid;
/*
    case 0x6a: see case 0x68
    case 0x6b: dummy func: return AL=0
*/
      /* Extended Open-Creat, not fully functional. (bits 4,5,6 of BH) */
    case 0x6c:
      /* high nibble must be <= 1, low nibble must be <= 2 */
      if ((lr.DL & 0xef) > 0x2)
        goto error_invalid;
      lrc = DosOpen(MK_FP(lr.DS, lr.SI),
                    (lr.BX & 0x70ff) | ((lr.DL & 3) << 8) |
                    ((lr.DL & 0x10) << 6), lr.CL);
      if (lrc >= SUCCESS)
        /* action */
        lr.CX = (UWORD)(lrc >> 16);
      goto long_check;

      /* case 0x6d and above not implemented : see default; return AL=0 */

#ifdef WITHFAT32
      /* LFN functions - fail with "function not supported" error code */
    case 0x71:
      lr.AL = 00;
      goto error_carry;

      /* DOS 7.0+ FAT32 extended functions */
    case 0x73:
      CLEAR_CARRY_FLAG();
      CritErrCode = SUCCESS;
      rc = int21_fat32(&lr);
      goto short_check;
#endif

#ifdef WITHLFNAPI
      /* FreeDOS LFN helper API functions */
    case 0x74:
      {
        switch (lr.AL)
        {
          /* Allocate LFN inode */
          case 0x01:
            rc = lfn_allocate_inode();
            break;
          /* Free LFN inode */
          case 0x02:
            rc = lfn_free_inode(lr.BX);
            break;
          /* Setup LFN inode */
          case 0x03:
            rc = lfn_setup_inode(lr.BX, ((ULONG)lr.CX << 16) | lr.DX, ((ULONG)lr.SI << 16) | lr.DI);
            break;
          /* Create LFN entries */
          case 0x04:
            rc = lfn_create_entries(lr.BX, (lfn_inode_ptr)FP_DS_DX);
            break;
          /* Read next LFN */
          case 0x05:
            rc = lfn_dir_read(lr.BX, (lfn_inode_ptr)FP_DS_DX);
            break;
          /* Write SFN pointed by LFN inode */
          case 0x06:
            rc = lfn_dir_write(lr.BX);
            break;
          default:
            goto error_invalid;
        }
        lr.AX = rc;
        CLEAR_CARRY_FLAG();
        goto short_check;
      }
#endif
  }
  goto exit_dispatch;
long_check:
  if (lrc >= SUCCESS)
  {
    lr.AX = (UWORD)lrc;
    goto exit_dispatch;
  }
  rc = (int)lrc;
short_check:
  if (rc < SUCCESS)
    goto error_exit;
  goto exit_dispatch;
error_invalid:
  rc = DE_INVLDFUNC;
error_exit:
  lr.AX = -rc;
  if (CritErrCode == SUCCESS)
    CritErrCode = lr.AX;      /* Maybe set */
error_carry:
  SET_CARRY_FLAG();
exit_dispatch:
  fmemcpy(r, &lr, sizeof(lregs) - 4);
  r->DS = lr.DS;
  r->ES = lr.ES;
real_exit:;

#ifdef DEBUG
  if (bDumpRegs)
  {
    fmemcpy(&error_regs, user_r, sizeof(iregs));
    dump_regs = TRUE;
    dump();
  }
#endif
}

#if 0
        /* No kernel INT-23 handler required no longer -- 1999/04/15 ska */
/* ctrl-Break handler */
#pragma argsused
VOID INRPT FAR int23_handler(int es, int ds, int di, int si, int bp,
                             int sp, int bx, int dx, int cx, int ax,
                             int ip, int cs, int flags)
{
  tsr = FALSE;
  return_mode = 1;
  return_code = -1;
  mod_sto(CTL_C);
  DosMemCheck();
#ifdef TSC
  StartTrace();
#endif
  return_user();
}
#endif

struct int25regs {
  UWORD es, ds;
  UWORD di, si, bp, sp;
  UWORD bx, dx, cx, ax;
  UWORD flags, ip, cs;
};

/* 
    this function is called from an assembler wrapper function 
*/
VOID ASMCFUNC int2526_handler(WORD mode, struct int25regs FAR * r)
{
  ULONG blkno;
  UWORD nblks;
  BYTE FAR *buf;
  UBYTE drv;

  if (mode == 0x26)
    mode = DSKWRITEINT26;
  else
    mode = DSKREADINT25;

  drv = r->ax;

  if (drv >= lastdrive)
  {
    r->ax = 0x201;
    SET_CARRY_FLAG();
    return;
  }

#ifdef WITHFAT32
  {
    struct dpb FAR *dpbp = get_dpb(drv);
    if (dpbp != NULL && ISFAT32(dpbp))
    {
      r->ax = 0x207;
      SET_CARRY_FLAG();
      return;
    }
  }
#endif

  nblks = r->cx;
  blkno = r->dx;

  buf = MK_FP(r->ds, r->bx);

  if (nblks == 0xFFFF)
  {
    /*struct HugeSectorBlock FAR *lb = MK_FP(r->ds, r->bx); */
    blkno = ((struct HugeSectorBlock FAR *)buf)->blkno;
    nblks = ((struct HugeSectorBlock FAR *)buf)->nblks;
    buf = ((struct HugeSectorBlock FAR *)buf)->buf;
  }

  InDOS++;

  r->ax = dskxfer(drv, blkno, buf, nblks, mode);

  CLEAR_CARRY_FLAG();
  if (r->ax != 0)
  {
    SET_CARRY_FLAG();
    if (mode == DSKWRITEINT26)
      setinvld(drv);
  }
  --InDOS;
}

/*
VOID int25_handler(struct int25regs FAR * r) { int2526_handler(DSKREAD,r); }
VOID int26_handler(struct int25regs FAR * r) { int2526_handler(DSKWRITE,r); }
*/

#ifdef TSC
STATIC VOID StartTrace(VOID)
{
  if (bTraceNext)
  {
#ifdef DEBUG
    bDumpRegs = TRUE;
#endif
    bTraceNext = FALSE;
  }
#ifdef DEBUG
  else
    bDumpRegs = FALSE;
#endif
}
#endif

/* this function is called from an assembler wrapper function
   and serves the internal dos calls - int2f/12xx and int2f/4a01,4a02.
*/
struct int2f12regs {
#ifdef I386
#ifdef __WATCOMC__
  /* UWORD gs, fs;  ** GS/FS are protected through SI/DI */
#else
  UWORD high_edx,
#ifdef _MSC_VER
        high_ecx,
#else /* __BORLANDC__ */
        high_ebx,
#endif
        high_eax;
#endif
#endif
  UWORD es, ds;
  UWORD di, si, bp;
  xreg b, d, c, a;
  UWORD ip, cs, flags;
  UWORD callerARG1;             /* used if called from INT2F/12 */
};

/* WARNING: modifications in `r' are used outside of int2F_12_handler()
 * On input r.AX==0x12xx, 0x4A01 or 0x4A02
 */
VOID ASMCFUNC int2F_12_handler(struct int2f12regs r)
{
  COUNT rc;
  long lrc;

  if (r.AH == 0x4a)
  {
    size_t size = 0, offs = 0xffff;

    r.ES = offs;
    if (FP_SEG(firstAvailableBuf) == offs) /* HMA present? */
    {
      offs = FP_OFF(firstAvailableBuf);
      size = ~offs;                        /* BX for query HMA   */
      if (r.AL == 0x02)                    /* allocate HMA space */
      {
        if (r.BX < size)
          size = r.BX;
        AllocateHMASpace(offs, offs+size);
        firstAvailableBuf += size;
      }
    }
    r.DI = offs;
    r.BX = size;
    return;
  }

  switch (r.AL)
  {
    case 0x00:                 /* installation check */
      r.AL = 0xff;
      break;

    case 0x03:                 /* get DOS data segment */
      r.DS = FP_SEG(&nul_dev);
      break;

    case 0x06:                 /* invoke critical error */

      /* code, drive number, error, device header */
      r.AL = CriticalError(r.callerARG1 >> 8,
                           (r.callerARG1 & (EFLG_CHAR << 8)) ? 0 :
                           r.callerARG1 & 0xff, r.DI, MK_FP(r.BP, r.SI));
      break;

    case 0x08:                 /* decrease SFT reference count */
      {
        sft FAR *p = MK_FP(r.ES, r.DI);

        r.AX = p->sft_count;

        if (--p->sft_count == 0)
          --p->sft_count;
      }
      break;

    case 0x0c:                 /* perform "device open" for device, set owner for FCB */

      if (lpCurSft->sft_flags & SFT_FDEVICE)
      {
        request rq;

        rq.r_unit = 0;
        rq.r_status = 0;
        rq.r_command = C_OPEN;
        rq.r_length = sizeof(request);
        execrh((request FAR *) & rq, lpCurSft->sft_dev);
      }

      /* just do it always, not just for FCBs */
      lpCurSft->sft_psp = cu_psp;
      break;

    case 0x0d:                 /* get dos date/time */

      r.AX = dos_getdate();
      r.DX = dos_gettime();
      break;

    case 0x11:                 /* normalise ASCIIZ filename */
    {
      char c;
      char FAR *s = MK_FP(r.DS, r.SI);
      char FAR *t = MK_FP(r.ES, r.DI);

      do
      {
        c = *s++;
        /* uppercase character */
        /* for now, ASCII only because nls.c cannot handle DS!=SS */
        if (c >= 'a' && c <= 'z')
          c -= 'a' - 'A';
        else if (c == '/')
          c = '\\';
        *t++ = c;
      }
      while (c);
      break;
    }

    case 0x12:                 /* get length of asciiz string */

      r.CX = fstrlen(MK_FP(r.ES, r.DI)) + 1;

      break;

    case 0x13:
      /* uppercase character */  
      /* for now, ASCII only because nls.c cannot handle DS!=SS */
      r.AL = (unsigned char)r.callerARG1;
      if (r.AL >= 'a' && r.AL <= 'z')
        r.AL -= 'a' - 'A';
      break;

    case 0x16:
      /* get address of system file table entry - used by NET.EXE
         BX system file table entry number ( such as returned from 2F/1220)
         returns
         ES:DI pointer to SFT entry
         BX relative entry number within SFT */
      {
        int rel_idx = idx_to_sft_(r.BX);

        if (rel_idx == -1)
        {
          r.FLAGS |= FLG_CARRY;
          break;
        }
        r.FLAGS &= ~FLG_CARRY;
        r.BX = rel_idx;
        r.ES = FP_SEG(lpCurSft);
        r.DI = FP_OFF(lpCurSft);
        break;
      }

    case 0x17:                 /* get current directory structure for drive - used by NET.EXE
                                   STACK: drive (0=A:,1=B,...)
                                   ; returns
                                   ;   CF set if error
                                   ;   DS:SI pointer to CDS for drive
                                   ; 
                                   ; called like
                                   ;   push 2 (c-drive)
                                   ;   mov ax,1217
                                   ;   int 2f
                                   ;
                                   ; probable use: get sizeof(CDSentry)
                                 */
      {
        struct cds FAR *cdsp = get_cds(r.callerARG1 & 0xff);

        if (cdsp == NULL)
        {
          r.FLAGS |= FLG_CARRY;
          break;
        }
        r.DS = FP_SEG(cdsp);
        r.SI = FP_OFF(cdsp);
        r.FLAGS &= ~FLG_CARRY;
        break;
      }

    case 0x18:                 /* get caller's registers */

      r.DS = FP_SEG(user_r);
      r.SI = FP_OFF(user_r);
      break;

    case 0x1b:                 /* #days in February - valid until 2099. */

      r.AL = (r.CL & 3) ? 28 : 29;
      break;

    case 0x20:                 /* get job file table entry */
      {
        psp FAR *p = MK_FP(cu_psp, 0);
        unsigned char FAR *idx;

        if (r.BX >= p->ps_maxfiles)
        {
          r.AL = -DE_INVLDHNDL;
          r.FLAGS |= FLG_CARRY;
          break;
        }
        idx = &p->ps_filetab[r.BX];
        r.FLAGS &= ~FLG_CARRY;
        r.ES = FP_SEG(idx);
        r.DI = FP_OFF(idx);
      }
      break;

    case 0x21:                 /* truename */

      DosTruename(MK_FP(r.DS, r.SI), MK_FP(r.ES, r.DI));

      break;

    case 0x23:                 /* check if character device */
      {
        struct dhdr FAR *dhp;

        dhp = IsDevice((BYTE FAR *) DirEntBuffer.dir_name);

        if (dhp == NULL)
        {
          r.FLAGS |= FLG_CARRY;
          break;
        }
        r.BH = dhp->dh_attr;
        r.FLAGS &= ~FLG_CARRY;
      }
      break;

    case 0x25:                 /* get length of asciiz string */

      r.CX = fstrlen(MK_FP(r.DS, r.SI)) + 1;
      break;

    case 0x26:                 /* open file */
      r.FLAGS &= ~FLG_CARRY;
      CritErrCode = SUCCESS;
      lrc = DosOpen(MK_FP(r.DS, r.DX), O_LEGACY | O_OPEN | r.CL, 0);
      goto long_check;

    case 0x27:                 /* close file */
      r.FLAGS &= ~FLG_CARRY;
      CritErrCode = SUCCESS;
      rc = DosClose(r.BX);
      goto short_check;

    case 0x28:                 /* move file pointer */
      /*
       * RBIL says: "sets user stack frame pointer to dummy buffer,
       * moves BP to AX, performs LSEEK, and restores frame pointer"
       * We obviously don't do it like that. Does this do any harm?! --L.G.
       */
      r.FLAGS &= ~FLG_CARRY;
      CritErrCode = SUCCESS;
      if (r.BP < 0x4200 || r.BP > 0x4202)
        goto error_invalid;
#if 0
      {
        sft FAR *s = get_sft(r.BX);
        if ((rc = _SftSeek(s, MK_ULONG(r.CX, r.DX), r.BP & 0xff)) >= SUCCESS)
        {
          r.DX = hiword (s->sft_posit);
          r.AX = loword (s->sft_posit);
        }
      }
      goto short_check;
#else
        goto error_invalid;
#endif

    case 0x29:                 /* read from file */
      r.FLAGS &= ~FLG_CARRY;
      CritErrCode = SUCCESS;
      lrc = DosRead(r.BX, r.CX, MK_FP(r.DS, r.DX));
      goto long_check;

    case 0x2a:                 /* Set FastOpen but does nothing. */

      r.FLAGS &= ~FLG_CARRY;
      break;

    case 0x2c:                 /* added by James Tabor For Zip Drives
                                   Return Null Device Pointer          */
      /* by UDOS+RBIL: get header of SECOND device driver in device chain, 
         omitting the NUL device TE */
      r.BX = FP_SEG(nul_dev.dh_next);
      r.AX = FP_OFF(nul_dev.dh_next);

      break;

    case 0x2e:                 /* GET or SET error table addresse - ignored
                                   called by MS debug with  DS != DOSDS, printf
                                   doesn't work!! */
      break;

    case 0x2f:
      if (r.DX)
      {
        os_setver_major = r.DL;
        os_setver_minor = r.DH;
      }
      else
      {
        os_setver_major = os_major;
        os_setver_minor = os_minor;
      }
      break;

    default:
      if (r.AL <= 0x31)
      {
        put_string("unimplemented internal dos function INT2F/12");
        put_unsigned(r.AL, 16, 2);
        put_string("\n");
        r.FLAGS |= FLG_CARRY;
      }
  }
  return;
long_check:
  if (lrc >= SUCCESS)
  {
    r.AX = (UWORD)lrc;
    return;
  }
  rc = (int)lrc;
short_check:
  if (rc < SUCCESS)
    goto error_exit;
  return;
error_invalid:
  rc = DE_INVLDFUNC;
error_exit:
  r.AX = -rc;
  if (CritErrCode == SUCCESS)
    CritErrCode = r.AX;      /* Maybe set */
  r.FLAGS |= FLG_CARRY;
}

/*
 * 2000/09/04  Brian Reifsnyder
 * Modified interrupts 0x25 & 0x26 to return more accurate error codes.
 */

