PAGE    ,132
        TITLE   DXMAIN.ASM -- Main Module for Dos Extender

; Copyright (c) Microsoft Corporation 1988-1991. All Rights Reserved.

;****************************************************************
;*                                                              *
;*      DXMAIN.ASM      -   Dos Extender Main Module            *
;*                                                              *
;****************************************************************
;*                                                              *
;*  Module Description:                                         *
;*                                                              *
;*  This module contains the main routines for the Dos          *
;*  Extender.  This is based on code written for Microsoft      *
;*  by Murray Sargent of Scroll Systems from Tucson Arizona.    *
;*                                                              *
;*  The Dos Extender provides support to allows specially       *
;*  written programs to run in protected mode mode on the       *
;*  80286 and 80386 under MS-DOS.  The following areas of       *
;*  support are provided to accomplish this:                    *
;*                                                              *
;*  Program Loading and Initialization                          *
;*      This involves creating a program segment prefix and     *
;*      then loading and relocating the exe file.  When         *
;*      loading an exe for protected mode operation, it is      *
;*      necessary to create segment descriptors for all         *
;*      segments used by the program and to then substitute     *
;*      the corresponding selectors when fixing up the segment  *
;*      references in the code.                                 *
;*                                                              *
;*  Dos Function Call Support                                   *
;*      Since Dos must execute in real mode, it is necessary    *
;*      to perform mode switching into real mode and the back   *
;*      to protected mode when the application makes Dos calls. *
;*      Also, any far pointers that are parameters to the       *
;*      function must be converted from the selector:offset     *
;*      form that the application uses to a segment:offset form *
;*      that Dos can use, with the corresponding data being     *
;*      buffered from the application's extended memory address *
;*      space to Dos's real mode address space.                 *
;*                                                              *
;*  Other Interrupt Support                                     *
;*      Hardware interrupts are processed in real mode, and     *
;*      so the Dos Extender performs mode switching on each     *
;*      interrupt.  Also other system resources (such as the    *
;*      mouse driver and the bios) are entered through software *
;*      interrupts, and require the same kind of buffering      *
;*      and parameter translation that the Dos functions        *
;*      require.                                                *
;*                                                              *
;*  Extended Memory Management                                  *
;*      The protected mode application has access to the full   *
;*      address space of the machine, and a memory manager is   *
;*      provided that duplicates the functions of the Dos       *
;*      memory manager over the entire address space of the     *
;*      machine.                                                *
;*                                                              *
;****************************************************************
;*  Revision History:                                           *
;*                                                              *
;*  08/08/90 earleh DOSX and Client privilege ring determined   *
;*      by equate in pmdefs.inc                                 *
;*  03/23/90 davidw   Added the reflecting of it 23h, ^C.       *
;*  11/09/89 jimmat   Added more IOCTL 0Dh support for Windows. *
;*  10/11/89 jimmat   Changed hooking of Int 1,2,3 under a      *
;*                    debugger to work better with CVW.         *
;*  07/28/89 jimmat   Fixed Int 21h/56h (Rename), fixed Int 21  *
;*                    calls that just returned a pointer.       *
;*  06/07/89 jimmat   Fixed length of FCB moves and special     *
;*                    case hooking Int 1Eh.                     *
;*  05/30/89 jimmat   Completed Int 21h/5Ah processing.         *
;*  04/25/89 jimmat   Added support for undocumented INT 21h    *
;*                    5Fh/05 DOS call.                          *
;*  04/12/89 jimmat   Allow one level of nested DOS calls to    *
;*                    support PM critical error handlers        *
;*  04/10/89 jimmat   Supported INT 21h/5Eh & 5Fh--also small   *
;*                    clean-up of the dosentry/dosexit code.    *
;*  04/05/89 jimmat   Fixed MOVDAT FCB length check.            *
;*  04/04/89 jimmat   Stop reflecting real mode software ints   *
;*                    to protect mode.  This is how Windows/386 *
;*                    works, and it fixes a problem with DOS    *
;*                    networks since the real mode redirector   *
;*                    expects to have access to the DOS stack,  *
;*                    not a DOS extender interrupt stack frame. *
;*  03/28/89 jimmat   Incorporated bug fixes from GeneA         *
;*  03/17/89 jimmat   Some code clean-up and debugging checks   *
;*  03/15/89 jimmat   Minor changes to run child in ring 1      *
;*  02/22/89 (GeneA): removed dead code and data left over      *
;*      from the Murray Sargent/SST days.                       *
;*  02/22/89 (GeneA): moved handlers for all interrupts but     *
;*      Int 21h to DXINTR.ASM.  Fixed problem with re-entrancy  *
;*      caused when the other interrupts were executed while    *
;*      in DOS.  (int 15h was causing the specific problem).    *
;*  02/14/89 (GeneA): fixed bug in IntExitMisc.  Was storing    *
;*      return value in rmrg.xes, changed to pmrg.xes.          *
;*  02/10/89 (GeneA): changed Dos Extender from small model to  *
;*      medium model.                                           *
;*  12/01/88 (GeneA): Changed name of mentry and mexit to       *
;*      IntEntryMouse and IntExitMouse.  Added functions        *
;*      IntEntryMisc and IntExitMisc to handle entry and        *
;*      exit processing for BIOS INT 15h.                       *
;*  11/20/88 (GeneA): modified DFSetVector so that is checks to *
;*      see if a vector has already been hooked before saving   *
;*      the old value in the real mode interrupt vector shadow  *
;*      buffer.                                                 *
;*  09/15/88 (GeneA):   created by extracting code from the     *
;*      SST debugger modules DOSXTND.ASM, VIRTMD.ASM,           *
;*      VRTUTIL.ASM, and INTERRPT.ASM                           *
;*                                                              *
;****************************************************************

        .286p
        .287

; -------------------------------------------------------
;           INCLUDE FILE DEFINITIONS
; -------------------------------------------------------

        .xlist
        .sall
include segdefs.inc
include gendefs.inc
include pmdefs.inc
include intmac.inc
include bop.inc
include dpmi.inc
include hostdata.inc
        .list

; -------------------------------------------------------
;           GENERAL SYMBOL DEFINITIONS
; -------------------------------------------------------

; STKSTR  -- stack layout structure for user registers in the pmrg and
; rmrg arrays in the data segment DXDATA.  pmrg is an exact replica of the pm
; user registers on entry to the Dos Extender (DE).  rmrg is a translated
; version used to communicate with the real-mode world.  The rmrg array is
; initially set equal to the pm user values by the instructions push ds, push
; es, pusha.  The pmrg array es and ds are inevitably set equal to the pm user
; values and its general register values are defined if data translations may be
; required (int-10/21).

stkstr  struc           ;Level-0 Stack structure. bp is set = sp here
xdi     dw      ?
xsi     dw      ?
xbp     dw      ?
xasp    dw      ?       ;Alternate sp
xbx     dw      ?
xdx     dw      ?
xcx     dw      ?
xax     dw      ?       ;pusha pushes xax to xdi
xes     dw      ?
xds     dw      ?
stkstr  ends


; -------------------------------------------------------
;   This structure describes the EXEC parameter block used
;   by MS-DOS function 4Bh.

execblk struc           ;INT-21 ah=4bh EXEC parameter block
evrnmt  dw      ?       ;Paragraph of environment string to be passed
cmdptr  dd      ?       ;Ptr to command line to be placed at PSP+80h
fcb1ptr dd      ?       ;Ptr to default FCB to be passed at PSP+5ch
fcb2ptr dd      ?       ;Ptr to default FCB to be passed at PSP+6ch
xsssp   dd      ?       ;Initial program stack ss:sp
xcsip   dd      ?       ;Program entry point cs:ip
execblk ends

; -------------------------------------------------------
;           EXTERNAL SYMBOL DEFINITIONS
; -------------------------------------------------------

        extrn   gtpara:NEAR
        extrn   GetSegmentAddress:NEAR
        extrn   EnterRealMode:NEAR
        extrn   EnterProtectedMode:NEAR
        extrn   SelOff2SegOff:NEAR
        extrn   ParaToLDTSelector:NEAR
        extrn   NSetSegmentDscr:FAR
        extrn   ChildTerminationHandler:NEAR

; -------------------------------------------------------
;               DATA SEGMENT DEFINITIONS
; -------------------------------------------------------

DXDATA  segment

        extrn   selGDT:WORD
        extrn   segPSPChild:WORD
        extrn   npXfrBuf1:WORD
        extrn   rgbXfrBuf0:BYTE
IFDEF WOW_x86
        extrn   FastBop:FWORD
ENDIF

        extrn   segDXCode:word
        extrn   segCurrentHostData:word
; -------------------------------------------------------
;           General DOS EXTENDER Variables
; -------------------------------------------------------

pmdta   dw      2 dup(?)    ;PM DTA.  Used for getting dir info

EntryFlag db    -1          ;Flag to check for nested DOS interrupts

rcount  dw      ?       ;Remaining requested file byte count to read/write
ccount  dw      ?       ;Current count of total read/written

;=======================================================================
;Keep from here thru Iend in the following order:

        align   2
Ibegin  label   byte            ;start of PMIntrDos nested 'Instance' data

        dw      128 dup(?)      ;Extra stack for real mode

rmrg    stkstr  <>              ;Corresponding real-mode set
rmivip  dw      ?               ;Real-mode interrupt-vector offset
rmivcs  dw      ?               ;Real-mode interrupt-vector segment
rmivfl  dw      ?               ;Real-mode interrupt flags to be used
rmroff  dw      ?               ;Real-mode return address offset
rmrseg  dw      ?               ;Real-mode return address segment
rmflags dw      ?               ;flags

pmrg    stkstr  <>              ;Protected-mode user registers

        public  pmusrss, pmusrsp

pmusrsp dw      ?       ;PM user sp
pmusrss dw      ?       ;PM user ss

enxseg  dw      ?       ;transfer segment used on dos function exit

Iend    label   byte    ;end of PMIntrDos nested 'Instance' data
;=======================================================================

ILENGTH equ     Iend - Ibegin   ;length of instance data

Isave   db      ILENGTH dup (?) ;instance data save area

DXDATA  ends


; -------------------------------------------------------
;               CODE SEGMENT VARIABLES
; -------------------------------------------------------

DXCODE  segment

        extrn   segDXData:WORD
        extrn   selDgroup:WORD

DXCODE  ends

DXPMCODE    segment

        extrn   selDgroupPM:WORD
        extrn   segDXCodePM:WORD
        extrn   segDXDataPM:WORD

; -------------------------------------------------------
;           Dos Function Parameter Tables
; -------------------------------------------------------

; The Dos Extender provides parameter buffering and translation
; on entry to and exit from Dos system calls.  The following table
; is used to describe the operations to be performed for this process.
; The basic idea is that there is an entry parameter code and an
; exit paramter code.  This code describes whether any data buffering
; is necessary.  If buffering is necessary, it describes the nature of
; the data being transferred (e.g. ASCIIZ string, FCB, etc.) which is
; used to determine the transfer length.  The entry/exit code also
; describes which temporary buffer to use for the transfer, and which
; user register (e.g. DS:DX, ES:BX, etc) points to the data to transfer.
;
; The data transfers described by this table is sufficient to handle
; the majority of the Dos system calls.  However, any Dos call which
; requires the transfer of more than one buffer of data, or which requires
; that additional processing be performed other than simply copying a
; buffer is handled with special case code.


; The following symbols define various parts of the entry/exit codes
; used in the parameter table.

; Nibble 0 of a transfer word is a code that specifies the length of the
; transfer as follows:

; 0       no xfer
; 1       FCB                     to/from         (f,10,11,12,13,16,17,23,24,29
; 2       PTR$                    from            (1b,1c,34)
; 3       ASCZ                    to/from         (39,3a,3b,3c,3d,41,43,4b,4e,56
;                                                  5a,5b)
; 4       DOLLAR terminated       to DOS          (9)
; 5       AX$                     from            (3f)
; 6       CX$                     to/from         (3f,40,44)
; 7       KEYBUF                  to/from DOS     (a)
; 8       Country INFO (34)       to/from         (38)
; 9       EXTERR (22)             to              (5d)
; a       DIRectory (64)          from            (47)
; b       EXEC parm (14)          to              (4b)
; c       file MATCH (43)         to/from         (4e,4f)
; d       CMND line (128)         to              (29)
; e       PALETTE (17)            to              (int-10/1002)
; f       VBIOS (64)              from            (int-10/1b)
;
;
; Nibble 1 specifies the segment value transferred as follows (DOS entries
; affected are listed in parentheses):
;
; Segment ptr
;
; 0       none
; 1       ds:dx   to/from DOS     (9,a,f,10,11,12,13,14,16,17,21,22,23,24,25,27
;                                  28,39,3a,3b,3c,3d,3f,40,41,43,44,4b,4e,4f,56
;                                  5a,5b)
; 2       DTA     to/from         (11,12,14,15,21,22,27,28,4e)
; 3       ds:bx   from            (1b,1c)         (Allocation table info)
; 4       ds:si   to/from         (29,47)         (Parse FN, get dir)
; 5       es:di   to/from         (29,56)         (Parse FN, rename)
; 6       es:bx   to/from         (2f,35,4b)      (Get DTA, intvct, EXEC)
; 7       es      to              (49,4a)         (RAM allocation)
;
; Byte 1 (high byte) on has meanings:
;
; bit 0 = 1       (A0) use small data area 0 (limited to 60h bytes)
; bit 1 = 1       (A1) use large area 1 (4K)
; bit 2 = 1       (RP) Real/Protect mode transfer needed (for int 21 ah = 3f/40)
; bit 7 = 1/0     (EX) exitcd/entrycd


entrycd record  EnArea:4,EnSegCod:4,EnLenCod:4
exitcd  record  ExArea:8,ExSegCod:4,ExLenCod:4

;Length codes:

FCB     equ     1
PTR$    equ     2       ;only supports DSBX & ESBX for now (and DSSI if DBCS)
ASCZ    equ     3
DOL     equ     4
AX$     equ     5
CX$     equ     6
KYB     equ     7
INFO    equ     8       ;Constant length transfers from here down vvvvvvvv
EXTERR  equ     9
DIR     equ     0Ah
EXEC    equ     0Bh
MTCH    equ     0Ch
CMD     equ     0Dh
PALETTE equ     0Eh
VBIOS   equ     0Fh     ;Constant length transfers from here up ^^^^^^^^^^

;Segment codes:

DSDX    equ     1
DTA     equ     2
DSBX    equ     3
DSSI    equ     4
ESDI    equ     5       ;Also used by int-10/ah=1bh
ESBX    equ     6       ;Also used by int-10/ah=1ch
ES$     equ     7
ESDX    equ     8       ;Used by int-10/ah=10,12, int-33/ah=22,23
ESBP    equ     9       ;Used by int-10/ah=11,13


;RAM area codes:

A0      equ     1
A1      equ     2
RP      equ     4
EX      equ     80h


pmrmxfr entrycd <>              ;0 - Program Terminate
        exitcd  <>
        entrycd <>              ;1 - Keyboard Input
        exitcd  <>
        entrycd <>              ;2 - Display output
        exitcd  <>
        entrycd <>              ;3 - Auxiliary Input
        exitcd  <>
        entrycd <>              ;4 - Auxiliary Output
        exitcd  <>
        entrycd <>              ;5 - Printer Output
        exitcd  <>
        entrycd <>              ;6 - Direct Console I/O
        exitcd  <>
        entrycd <>              ;7 - Direct Console Input Without Echo
        exitcd  <>
        entrycd <>              ;8 - Console Input Without Echo
        exitcd  <>
        entrycd <A1,DSDX,DOL>   ;9 - Print string
        exitcd  <>
        entrycd <A1,DSDX,KYB>   ;0A - Buffered Keyboard Input
        exitcd  <EX+A0,DSDX,KYB>
        entrycd <>              ;0B - Check Standard Input Status
        exitcd  <>
        entrycd <>              ;0C - Clear Kbd Buffer and Invoke Kbd Function
        exitcd  <>
        entrycd <>              ;0D - Disk Reset
        exitcd  <>
        entrycd <>              ;0E - Select Disk
        exitcd  <>
        entrycd <>              ;0F - Open File        ** Unsupported! **
        exitcd  <>
        entrycd <>              ;10 - Close File       ** Unsupported! **
        exitcd  <>
        entrycd <A0,DSDX,FCB>   ;11 - Search for First Entry
        exitcd  <EX+A1,DTA,FCB>
        entrycd <A0,DSDX,FCB>   ;12 - Search for Next Entry
        exitcd  <EX+A1,DTA,FCB>
        entrycd <A0,DSDX,FCB>   ;13 - Delete File
        exitcd  <>
        entrycd <>              ;14 - Sequential Read  ** Unsupported! **
        exitcd  <>
        entrycd <>              ;15 - Sequential Write ** Unsupported! **
        exitcd  <>
        entrycd <>              ;16 - Create File      ** Unsupported! **
        exitcd  <>
        entrycd <A0,DSDX,FCB>   ;17 - Rename File
        exitcd  <>
        entrycd <>              ;18 - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;19 - Current Disk
        exitcd  <>
        entrycd <A1,DSDX,>      ;1A - Set Disk Transfer Address
        exitcd  <>
        entrycd <>              ;1B - Allocation Table Info
        exitcd  <,DSBX,PTR$>
        entrycd <>              ;1C - Alloc Table Info for Specific Device
        exitcd  <,DSBX,PTR$>
        entrycd <>              ;1D - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;1E - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;1F - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;20 - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;21 - Random Read      ** Unsupported! **
        exitcd  <>
        entrycd <>              ;22 - Random Write     ** Unsupported! **
        exitcd  <>
        entrycd <>              ;23 - File Size        ** Unsupported! **
        exitcd  <>
        entrycd <>              ;24 - Set Relative Record Field ** Unsupported! **
        exitcd  <>
        entrycd <,DSDX,>        ;25 - Set Interrupt Vector (0/ds:dx/0)
        exitcd  <>
        entrycd <,DSDX,>        ;26 - Create new PSP
        exitcd  <>
        entrycd <>              ;27 - Random Block Read  ** Unsupported! **
        exitcd  <>
        entrycd <>              ;28 - Random Block Write ** Unsupported! **
        exitcd  <>
        entrycd <A0,DSSI,ASCZ>  ;29 - Parse Filename
        exitcd  <EX+A1,ESDI,FCB>
        entrycd <>              ;2A - Get Date
        exitcd  <>
        entrycd <>              ;2B - Set Date
        exitcd  <>
        entrycd <>              ;2C - Get Time
        exitcd  <>
        entrycd <>              ;2D - Set Time
        exitcd  <>
        entrycd <>              ;2E - Set/Reset Verify Switch
        exitcd  <>
        entrycd <>              ;2F - Get Disk Transfer Address
        exitcd  <EX+A0,ESBX,>
        entrycd <>              ;30 - Get DOS Version Number
        exitcd  <>
        entrycd <>              ;31 - Terminate and Stay Resident
        exitcd  <>
        entrycd <>              ;32 - Get Drive Parameter Block
        exitcd  <,DSBX,PTR$>
        entrycd <>              ;33 - Ctrl-Break Check
        exitcd  <>
        entrycd <>              ;34 - Get InDOS flag address
        exitcd  <,ESBX,PTR$>
        entrycd <>              ;35 - Get Interrupt Vector
        exitcd  <EX,ESBX,>
        entrycd <>              ;36 - Get Disk Free Space
        exitcd  <>
        entrycd <>              ;37 - Used Internally by DOS
        exitcd  <>
        entrycd <A1,DSDX,>      ;38 - Set/Get Country Dependent Info
        exitcd  <EX+A1,DSDX,INFO>
        entrycd <A0,DSDX,ASCZ>  ;39 - MKDIR
        exitcd  <>
        entrycd <A0,DSDX,ASCZ>  ;3A - RMDIR
        exitcd  <>
        entrycd <A0,DSDX,ASCZ>  ;3B - CHDIR
        exitcd  <>
        entrycd <A0,DSDX,ASCZ>  ;3C - Create a File
        exitcd  <>
        entrycd <A0,DSDX,ASCZ>  ;3D - Open a File
        exitcd  <>
        entrycd <>              ;3E - Close a File Handle
        exitcd  <>
        entrycd <RP,DSDX,>      ;3F - Read from a File or Device
        exitcd  <EX+RP,DSDX,AX$>
        entrycd <RP,DSDX,CX$>   ;40 - Write to a File or Device
        exitcd  <>
        entrycd <A0,DSDX,ASCZ>  ;41 - Delete a File from a Specified Directory
        exitcd  <>
        entrycd <>              ;42 - Move File Read/Write Pointer
        exitcd  <>
        entrycd <A0,DSDX,ASCZ>  ;43 - Change File Mode
        exitcd  <>
        entrycd <>              ;44 - I/O Control for Devices
        exitcd  <>               ;See ioctlw for write
        entrycd <>              ;45 - Duplicate a File Handle
        exitcd  <>
        entrycd <>              ;46 - Force a Duplicate of a File Handle
        exitcd  <>
        entrycd <A0,DSSI,>      ;47 - Get Current Directory
        exitcd  <EX+A0,DSSI,ASCZ>
        entrycd <>              ;48 - Allocate Memory
        exitcd  <>
        entrycd <,ES$,>         ;49 - Free Allocated Memory
        exitcd  <>
        entrycd <,ES$,>         ;4A - Modify Allocated Memory Blocks
        exitcd  <>
        entrycd <A0,DSDX,ASCZ>  ;4B - Load or Execute a Program (EXEC)
        exitcd  <>
        entrycd <>              ;4C - Terminate a Process
        exitcd  <>
        entrycd <>              ;4D - Get Return Code of a Sub-process
        exitcd  <>
        entrycd <A0,DSDX,ASCZ>  ;4E - Find First Matching File
        exitcd  <EX+A1,DTA, MTCH>
        entrycd <A1,DTA,MTCH>   ;4F - Find Next Matching File
        exitcd  <EX+A1,DTA, MTCH>
        entrycd <,ESBX,>        ;50 - Set current PSP (code restores bx)
        exitcd  <>
        entrycd <>              ;51 - Get current PSP
        exitcd  <>
        entrycd <>              ;52 - Get Pointer to SysInit Variables
        exitcd  <,ESBX,PTR$>
        entrycd <A1,DSSI,DIR>   ;53 - Set Drive Parameter Block
        exitcd  <>
        entrycd <>              ;54 - Get Verify Setting
        exitcd  <>
        entrycd <,DSDX,>        ;55 - Duplicate PSP
        exitcd  <>
        entrycd <A0,DSDX,ASCZ>  ;56 - Rename a File
        exitcd  <>
        entrycd <>              ;57 - Get/Set a File's Date and Time
        exitcd  <>
        entrycd <>              ;58 - Get/Set Allocation Strategy
        exitcd  <>
        entrycd <>              ;59 - Get Extended Error
        exitcd  <>
        entrycd <A0,DSDX,ASCZ>  ;5A - Create Temporary File
        exitcd  <EX+A0,DSDX,ASCZ>
        entrycd <A0,DSDX,ASCZ>  ;5B - Create New File
        exitcd  <>
        entrycd <>              ;5C - Lock/Unlock File Access
        exitcd  <>
        entrycd <A0,DSDX,EXTERR>  ;5D - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;5E - Network Machine Name/Printer Setup
        exitcd  <>
        entrycd <>              ;5F - Get/Make Assign-List Entry
        exitcd  <>
        entrycd <>              ;60 - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;61 - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;62 - Get PSP Address
        exitcd  <>
        entrycd <>              ;63 - Get Lead Byte Table  ** Unsupported! **
        exitcd  <>
        entrycd <>              ;64 - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;65 - Get Extended Country Info
        exitcd  <EX+A1,ESDI,CX$>;     ** Only Partially Supported **
        entrycd <>              ;66 - Get/Set Code Page
        exitcd  <>
        entrycd <>              ;67 - Set Handle Count
        exitcd  <>
        entrycd <>              ;68 - Commit File
        exitcd  <>
        entrycd <>              ;69 - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;6A - Used Internally by DOS
        exitcd  <>
        entrycd <>              ;6B - Used Internally by DOS
        exitcd  <>
        entrycd <A0,DSSI,ASCZ>  ;6C - Extended Open File
        exitcd  <>

MaxInt21 equ    06Ch            ;max supported Int 21h function

UnSupported entrycd <>          ;for unsupported DOS calls
            exitcd  <>

if DEBUG   ;------------------------------------------------------------

; Table of partially supported/unsupported/unknown Int 21h functions

ifdef DBCS
tblBad21  db    18h,1Dh,1Eh,1Fh,20h,37h,5Dh,60h,61h
          db    64h,65h,6Ah,6Bh,0
else
tblBad21  db    18h,1Dh,1Eh,1Fh,20h,37h,5Dh,60h,61h,63h
          db    64h,65h,6Ah,6Bh,0
endif
endif   ;DEBUG  --------------------------------------------------------
;
; For compatibility with WIN386, the following FCB calls are failed
; unconditionally.
;
MIN_REAL_BAD_21 equ     0fh
MAX_REAL_BAD_21 equ     28h

tblRealBad21    db 0fh,10h,14h,15h,16h,21h,22h,23h,24h,27h,28h,0



; Special codes for special INT 21h functions

int215E         entrycd <A0,DSDX,>              ;5E/00 - Get Machine Name
                exitcd  <EX+A0,DSDX,ASCZ>
                entrycd <A0,DSDX,ASCZ>          ;5E/01 - Set Machine name
                exitcd  <>
                entrycd <A0,DSSI,CX$>           ;5E/02 - Set Printer Setup Str
                exitcd  <>
                entrycd <A0,ESDI,>              ;5E/03 - Get Printer Setup Str
                exitcd  <EX+A0,ESDI,CX$>

int215F02       entrycd <A0,DSSI>               ;5F/02 - Get Redir List Entry
                exitcd  <EX+A0,DSSI,ASCZ>
                entrycd <A0,DSSI,ASCZ>          ;5F/03 - Set Redir List Entry
                exitcd  <>
                entrycd <A0,DSSI,ASCZ>          ;5F/04 - Cancel Redirection
                exitcd  <>
                entrycd <A0,DSSI>               ;5F/05 - Get Redir List Entry
                exitcd  <EX+A0,DSSI,ASCZ>

ifdef DBCS
int2163         entrycd <>              ;63/00 - Get Lead Byte Table Entry
                exitcd  <,DSSI,PTR$>
endif ; DBCS

int21esdi       entrycd <A1,ESDI,ASCZ>          ;56 & 5F/02&03&05 eXtra buffer
                exitcd  <EX+A1,ESDI,ASCZ>


;
; We only use the entry code from the following.  If we don't
; we trash the stack in applications like borland c++
;
int21pfn        entrycd <A1,ESDI,FCB>           ;29, fcb buffer
                exitcd  <>


; Additional tables for run time support associated with register
; translation and buffering.

;                 8   9       a    b    c     d     e      f
;               INFO EXTERR  DIR  EXEC MATCH CMND PALETTE VBIOS
mvcnt   db      34,   22,    40h, 0Eh,  43,   80h,  17,    64

;                 1    2     3     4     5     6    7     8     9
;               DSDX, DTA, DSBX, DSSI, ESDI, ESBX, ES$, esdx, esbp
regoffs dw       xdx,  0,   xbx,  xsi,  xdi,  xbx,  0,   xdx,  xbp

DXPMCODE    ends

; -------------------------------------------------------
        subttl  Main Program
        page
; -------------------------------------------------------
;               MAIN PROGRAM
; -------------------------------------------------------


DXPMCODE  segment
        assume  cs:DXPMCODE

; -------------------------------------------------------
;   PMIntrDos -- This function is the main handler for int 21h calls
;       that require special case processing.  Most interrupts
;       go through the interrupt reflector (PMIntrReflector in
;       dxintr.asm).  DOS int 21h interrupts are vectored here.
;
;       This routine performs any register manipulation, data buffering
;       etc. needed to pass the interrupt parameters from the
;       protected mode caller to the real mode handler.  Register
;       manipulation and data transfers can occur either on entry
;       to the interrupt handler or on exit from the interrupt
;       handler (to fix up return values.)
;
;   Input:  normal registers for Dos calls
;   Output: normal register returns for Dos calls
;   Errors: normal Dos errors
;   Uses:   In general, all registers are preserved.  For interrupts where
;           register translation or buffering occurs, some registers may
;           be changed, depending on the interrupt and its parameters.

        assume  ds:NOTHING,es:NOTHING,ss:NOTHING
        public  PMIntrDos

PMIntrDos       proc    far

        push    ds
        push    ax
        mov     ax,SEL_DXDATA OR STD_RING
        mov     ds,ax
        assume  ds:DGROUP
        mov     ax,ss
        mov     pmusrss,ax
        mov     ax,sp
        add     ax,4
        mov     pmusrsp,ax
        pop     ax

        FBOP    BOP_DPMI,XlatInt21Call,FastBop
; If we get here, the api wasn't translated.  The translation code will
; simulate the iret
        assume  ds:nothing

NotXlated:
        cld                     ;Code assumes direction flag is cleared

; Save PM user ds, es, and flags, and switch to DOS extender stack

        push    ax              ;Save caller's AX
        push    bp
        mov     bp,sp           ;[bp] = bp ax ip cs fl - 286 int gates assumed
                                ;        0  2  4  6  8
        push    es

        mov     es,selDgroupPM          ;Address our DGROUP
        assume  es:DGROUP


; Check for nested DOS interrupts--this code was written with the assumption
; that we would never be reentered, but it turns out that we may need to
; support nested DOS calls from a critical error handler.  If this is one
; of those ultra-rare occasions, save our previous 'instance' data in Isave.

        inc     EntryFlag       ;The normal case will be to jump
        jz      @f

        push    cx              ;Being reentered, save last instance data
        push    di
        push    si

        mov     cx,ILENGTH                      ;NOTE!!!  The next movs has
        mov     si,offset DGROUP:Ibegin         ;  an es override, if ints
        mov     di,offset DGROUP:Isave          ;  are enabled, an interrupt
        rep movs byte ptr [di],byte ptr es:[si] ;  on this instr can 'forget'
                                                ;  about the es override with
        pop     si                              ;  some processors
        pop     di
        pop     cx
@@:

; Start saving callers state.

        mov     pmrg.xds,ds     ;Save PM user ds
        mov     ax,[bp+8]       ;Get ax = flags when int occurred
        mov     rmflags,ax      ;Store flags for real-mode handler

        pop     pmrg.xes        ;Save PM user es

        pop     bp
        pop     ax              ;Recover user ax. [sp] = ip cs fl

; At this point all general registers (but not ES) have the user's values

        push    es              ;Address DGROUP, user's DS already
        pop     ds              ;  saved in pmrg.xds
        assume  ds:DGROUP

        mov     pmusrss,ss      ;Save PM user stack ptr
        mov     pmusrsp,sp      ;[sp] = ds ip cs fl
                                ;        0  2  4  6
        mov     pmrg.xsi,si     ;Save PM si since need to use before pusha

        push    ds              ;Switch to rmrg stack for this routine
        pop     ss
        mov     sp,offset DGROUP:rmflags        ;PM flags already on stack
        FSTI                     ;We don't really need interrupts disabled


; Setup iret frames for iret'ing to real-mode handler and for that handler
; returning to the DOS extender

        pop     si              ;Get rmflags
        and     si,not 4100h    ;Kill NT, TF
        push    si              ;Push flags for iret to BackFromDOS
        push    segDXCodePM     ;Push return cs:ip for iret
        push    offset BackFromDOS

        and     si,not 0200h    ;Kill IF
        push    si              ;Push flags for iret to real-mode handler

        sub     sp,4                    ; make room for stack frame
        push    bp
        mov     bp,sp
        push    es
        push    ax

        mov     ax,SEL_RMIVT OR STD_RING
        mov     es,ax
        mov     ax,es:[21h*4]
        mov     [bp + 2],ax
        mov     ax,es:[21h*4 + 2]
        mov     [bp + 4],ax
        pop     ax
        pop     es
        pop     bp


; Setup protected mode and real mode copies of registers.

        mov     si,pmrg.xsi     ;Restore si
        push    ds              ;Save space for real mode ds
        push    es              ;  and es
        pusha                   ;Save user general registers

        mov     si,offset DGROUP:rmrg   ;Copy user values to PM set for
        mov     di,offset DGROUP:pmrg   ;  reference (real-mode set may change)
        mov     cx,8            ;8 general registers (es and ds already stored)
        rep     movsw

        mov     ax,segDXDataPM  ;ax = DOS extender real-mode dgroup segment
        mov     rmrg.xds,ax     ;Default real-mode data segments
        mov     rmrg.xes,ax     ;  (dosentry may change them)

        mov     ax,rmrg.xax

if DEBUG   ;------------------------------------------------------------

; Check for partially supported/unsupported/unknown DOS calls

        cmp     ah,0DCh                 ;krnl286 is doing this now, quit
        jz      goodfunc                ;  complaining about it

        cmp     ax,5D0Ah                ;Set Extended Error is the only
        jz      goodfunc                ;5Dh function handled properly!

        cmp     ah,MaxInt21             ;is the request within our range?
        ja      badfunc

        mov     bx,offset DXPMCODE:tblBad21
@@:
        cmp     ah,cs:[bx]
        jb      goodfunc
        jz      badfunc
        inc     bx
        jmp     short @b

badfunc: Trace_Out "Possible Unsupported DOS Call! AX=#AX"

goodfunc:

endif   ;DEBUG  --------------------------------------------------------

; Check for FCB calls that we fail unconditionally (because WIN386 does.)

        cmp     ah,MIN_REAL_BAD_21
        jb      goodfunc1
        cmp     ah,MAX_REAL_BAD_21
        ja      goodfunc1

mov     bx,offset DXPMCODE:tblRealBad21
@@:
        cmp     ah,cs:[bx]
        je      badfunc1
        inc     bx
        cmp     byte ptr cs:[bx],0
        jz      goodfunc1               ; Ran off end of table.
        jmp     short @b

badfunc1:

if DEBUG
        Debug_Out "Unsupported DOS Call! AX=#AX"
endif   ;DEBUG

        or      byte ptr rmflags,1      ; Set CF
        call    xfrflg
        jmp     LeaveDOSExtender

goodfunc1:


; int 21 entry register translations and data transfers
        cmp     ah,00h          ;old style DOS Exit call?
        jnz     @f
        call    DosExitCall     ;sets CY if it handles the call, otherwise
        jnc     @f              ;  do it normally...
        jmp     LeaveDOSExtender
@@:
        ;
        ; Handle terminate specially.  We mess with the PSP here to set
        ; up a terminate vector we like.  We don't do anything special for
        ; TSR (31h)
        ;
        cmp     ah,4ch          ; terminate?
        jnz     @f

        call    TerminateProcess
@@:

        cmp     ah, 5dh         ; check for unsupported 5d codes
        jnz     short @f
        cmp     al, 0ah
        jz      short @f
        jmp     LeaveDOSExtender
@@:

        mov     rcount,0        ;Default no remaining bytes to read/write
        mov     ccount,0        ;No bytes read or written yet

        cmp     ah,3Fh          ;If read
        jz      @f
        cmp     ah,40h          ;  or write,
        jnz     TransferLoop
@@:
        mov     cx,pmrg.xcx     ;  initialize remaining count = requested value
        mov     rcount,cx


; Start of loop for doing large read/write transfers

TransferLoop:

        call    dosentry        ;Do selector translations, data buffering


; Come here after entry register translations and data transfers are complete

        SwitchToRealMode        ;Switch back to real mode.  ds = es = rm dgroup
                                ;Stack is at same place in memory

; Set registers to possibly translated values and iret to real-mode DOS.
; DOS then irets to BackFromDOS.

        popa                    ;Set appropriate general register values

        pop     es
        pop     ds

        public  GoingToDOS

GoingToDOS:                     ;for debugging, etc.

        iret                    ;invoke real mode DOS

        assume  ds:NOTHING, es:NOTHING, ss:NOTHING


; Return here from real-mode interrupt handler (DOS)

        public  BackFromDOS

BackFromDOS:

        pushf                   ;Save return flags (to rmflags)
        cld                     ;  (better safe than sorry)

        push    cs              ;Push return cs:ip for multiple xfers
        push    offset BackFromDOS

        sub     sp,2*3          ;Bypass room for iret to interrupt handler
                                ; (to keep stack layout the same as on entry)

        push    ds              ;Save register set
        push    es
        pusha

        mov     ds,segDXData
        assume  ds:DGROUP


;  "push" iret frame for real mode int 21 rtn in case we need to do it again

        mov     ax,rmflags
        and     ax,not 4300h    ;Kill NT, TF, and IF
        mov     rmivfl,ax

        xor     ax,ax
        mov     es,ax
        mov     ax,word ptr es:[21h*4+2]
        mov     rmivcs,ax
        mov     ax,word ptr es:[21h*4]
        mov     rmivip,ax


; Switch back to protected mode

        SwitchToProtectedMode   ;Switch back to protected mode
        assume  ds:DGROUP,es:DGROUP

        FSTI                     ;Don't need ints disabled

        call    xfrflg          ;Transfer relevant return flags over to pm iret frame

        mov     ax,pmrg.xax     ;Recover AX from caller


; Perform any int-21 selector translations, data buffering

        call    dosexit


; Check for large xfers (Read File 3Fh, Write File 40h, some IOCTL 44h)

        cmp     rcount,0        ;Read/write more bytes?
        jz      TransferDone

        mov     cx,rmrg.xax     ;Maybe. cx = count transferred (if 3Fh or 40h)
        mov     ax,pmrg.xax     ;Restore entry code
        mov     rmrg.xax,ax

        cmp     ah,40h          ;Write?
        jnz     @f

        sub     rcount,cx       ;Yes. Written all originally requested?
        jz      TransferDone

        cmp     cx,rmrg.xcx     ;  No. Written all last specified?
        jz      @f              ;    Yes. Go do some more

        mov     ax,ccount       ;A large write has failed!  ccount has already
        sub     ax,rmrg.xcx     ;  been updated assuming success, back out the
        add     ax,cx                   ;  attempted xfer amount, and add in
        jmp     short TransferCount     ;  the actual, then split
@@:
        jmp     TransferLoop    ;Yep (or 3Fh or 44h). Do another xfer

TransferDone:
        mov     ax,ccount       ;Multiple count xfer?
        or      ax,ax
        jz      LeaveDOSExtender

TransferCount:
        mov     rmrg.xax,ax     ;Yes update return amount
        mov     ax,pmrg.xcx
        mov     rmrg.xcx,ax     ;Restore initial request count


; Restore possibly translated registers and to return to pm caller

        public  LeaveDOSExtender

LeaveDOSExtender:

        popa                    ;Restore possibly changed user registers

        mov     ss,pmusrss      ;Restore pm user stack
        mov     sp,pmusrsp
        assume  ss:NOTHING

        push    pmrg.xds        ;push user seg regs on user stack
        push    pmrg.xes

        dec     EntryFlag       ;dec nested entry flag - normal case is to jmp
        jnz     NotNested
                                ;If this was a nested DOS call (from
        push    cx              ;  a critical error handler), restore
        push    si              ;  the state for the prior DOS call
        push    di              ;  which is still in progress

        mov     cx,ds           ;make darn sure es -> DGROUP
        mov     es,cx

        cld                                     ;NOTE: we need to retreive
        mov     cx,ILENGTH                      ;  all current user registers
        mov     di,offset DGROUP:Ibegin         ;  before moving this data
        mov     si,offset DGROUP:Isave
        rep movsb

        pop     di
        pop     si
        pop     cx

NotNested:
        pop     es              ;restore user seg regs
        pop     ds
        assume  ds:NOTHING,es:NOTHING

        public  DOSXiret
DOSXiret:                       ;for debugging, etc.

        iret                    ;return to caller

PMIntrDos       endp


; -------------------------------------------------------
;   DOSENTRY    -- This function performs buffering and register
;       translation for entry into MS-DOS functions.
;
;   Input:      AX:  AX value at time of INT 21h
;   Output:
;   Errors:
;   Uses:

        assume  ds:DGROUP,es:DGROUP,ss:DGROUP

dosentry:

        cmp     ah,26h          ;Create new PSP?
        jnz     @f
        mov     si,rmrg.xdx     ;yes, translate selector to paragraph
        call    gtpara
        mov     rmrg.xdx,ax
        return
@@:
        cmp     ah,53h          ;Set drive parameter block?
        jnz     @f
        push    ax
        mov     si,pmrg.xes     ;int 21h/53h has an extra parameter in ES:BP
        call    gtpara          ;  we change the selector to a segment, but
        mov     rmrg.xes,ax     ;  the segment had better already be in
        pop     ax              ;  conventional memory
        jmp     short dentr2b
@@:
        cmp     ah,50h          ;Set current PSP?
        jnz     dentr1
        mov     si,rmrg.xbx     ;Yes. Translate selector to paragraph
        call    gtpara
        mov     rmrg.xbx,ax
        return

dentr1: cmp     ah,55h          ;Duplicate PSP?
        jnz     dentr2
        mov     si,rmrg.xbx     ;Translate selector bx to paragraph
        call    gtpara
        mov     rmrg.xbx,ax
        mov     si,rmrg.xdx     ;  and dx also
        call    gtpara
        mov     rmrg.xdx,ax
        return

dentr2:
        cmp     ah,56h          ;Rename?
        jnz     dentr2a
        push    ax              ;rename has a second ASCIIZ buffer
        push    pmrg.xes        ;  pointed to by es:di -- move that
        pop     enxseg          ;  now, the ds:dx one below
        mov     ax,int21esdi
        call    gtarea          ;let the 'standard' gtarea/movdat
        mov     dx,enxseg       ;  routines take care of it
        call    movdat
        pop     ax
        jmp     short dentr2b

dentr2a:
        cmp     ah,5Fh          ;Get/Make Assign-List Entry?
        jne     dentr2a1
        call    net5Fenter      ;  Yes, may require extra buffering
        jmp     short dentr2b

dentr2a1:
        cmp     ah,29h          ; parse filename?
        jne     dentr2b

        push    ax
        push    pmrg.xes
        pop     enxseg
        mov     ax,int21pfn
        call    gtarea
        mov     dx,enxseg
        call    movdat
        pop      ax
;;        jmp     short dentr2b

dentr2b:
        call    GetEntryCd      ;ax = func entry code, di = ptr to entry cd

        or      ax,ax           ;Entry code specify something to do?
        rz

        cmp     byte ptr pmrg.xax+1,1Ah ;Store DTA?
        jnz     dentr3

        mov     pmdta,dx                ;  Yes. Save it for data returns
        push    pmrg.xds
        pop     pmdta+2
        jmp     short dentr4

dentr3: cmp     byte ptr pmrg.xax+1,4Bh ;EXEC program?
        callz   dosxec

; DENTR4 - enter with ax = entrycd/exitcd.  Translate ptr's for real-
; mode calls and transfer any data indicated.

dentr4: push    pmrg.xds
        pop     enxseg
        call    gtarea          ;Get es:di = area for transfer
        rz                      ;Something to xfer?
        mov     dx,enxseg       ;Yes. Fall thru to movdat

        errnz  <movdat-$>


; -------------------------------------------------------
;   MOVDAT      --  This routine performs the buffer transfer
;       for entry into or exit from an MS-DOS function.  The data
;       is copied from DX:SI to ES:DI.  The code in CX determines
;       the type of data being transferred, which is used to determine
;       the length.
;
;   Input:  DX:SI       - far pointer to source buffer
;           ES:DI       - far pointer to destination buffer
;           CX          - transfer length code
;   Output: none
;   Errors: none
;   Uses:   AX, BX, CS, SI, DI modified

        assume  ds:DGROUP,es:NOTHING,ss:DGROUP
        public  movdat

movdat:
        push    ds
        mov     bx,cx           ;Simple count?
        sub     bl,INFO
        jc      movda2
        cmp     bl,PALETTE-INFO ;Yes. Use pm es?
        jc      movda0
        mov     dx,pmrg.xes     ;Yes
movda0: mov     cl,mvcnt[bx]    ;cx = count
movda1: mov     ds,dx

if DEBUG   ;------------------------------------------------------------

        push    ax
        mov     ax,es
        lsl     ax,ax
        sub     ax,di
        jc      movbad
        cmp     ax,cx
        jnc     @f
movbad:
        Debug_Out "Movdat: copy beyond end of dest seg!"
@@:
        pop     ax

endif   ;DEBUG  --------------------------------------------------------

movd1a: rep     movsb           ;Move data
        pop     ds
        return

movda2: cmp     cl,CX$          ;Use pmrg.xcx?
        jnz     movda3
        mov     cx,rmrg.xcx     ;cx usually = pmrg.xcx, but in any event
                                ;  cx < CB_XFRBUF1
movd21: add     ccount,cx
        jmp     short movda1

movda3: mov     ah,0
        cmp     cl,ASCZ
        jz      movda4
        cmp     cl,DOL
        jnz     movda5
        mov     ah,"$"
movda4: mov     ds,dx
movd42: lodsb
        stosb
        cmp     al,ah
        jnz     movd42
        pop     ds
        return

movda5: cmp     cl,AX$  ;Use rmrg.xax?
        jnz     movda6
        mov     cx,rmrg.xax     ;Yes (only occurs for ah=3fh - read - on exit)
        jmp     short movd21

movda6:
        cmp     cl,FCB
        jnz     movda7
        mov     ds,dx
        mov     cl,byte ptr ds:[si]
        cmp     cl,0ffh                 ;standard or extended FCB?
        mov     cx,37                   ;standard FCB len
        jnz     movd1a
        mov     cx,44                   ;extended FCB len
        jmp     short movd1a

movda7:                         ;KYB remains
        pop     ds
        return


; -------------------------------------------------------
;   DOSEXIT     -- This function is called on exit from the MS-DOS
;       functions to perform any data buffering and register translation
;       needed.
;
;   Input:      AX: AX value at time of INT 21h
;   Output:
;   Errors:
;   Uses:

        assume  ds:DGROUP,es:DGROUP,ss:DGROUP
        public  dosexit

dosexit:
        cmp     ah,51h          ;Get current PSP?
        jz      dose0a
        cmp     ah,62h          ;Get PSP address?
        jnz     dose00
dose0a:
        mov     ax,rmrg.xbx     ;Yes. Translate segment to selector
        mov     bx,STD_DATA
        call    ParaToLDTSelector
        mov     rmrg.xbx,ax
        return

dose00: cmp     ah,2fh          ;Get current DTA?
        jnz     dose01
        mov     ax,pmdta        ;Yes. Load PM DTA into caller's registers
        mov     rmrg.xbx,ax
        mov     ax,pmdta+2
        verr    ax              ; if the dta selector is no longer valid,
        jz      @f              ; return the NULL selector instead (so we
        xor     ax,ax           ; don't GP fault in DOSX).
@@:     mov     pmrg.xes,ax
        return

dose01: cmp     ah,55h          ;Duplicate PSP?
        jnz     dosex1
        mov     ax,rmrg.xbx     ;Yes, translate segments to selectors
        mov     bx,STD_DATA
        call    ParaToLDTSelector
        mov     rmrg.xbx,ax
        mov     ax,rmrg.xdx
        mov     bx,STD_DATA
        call    ParaToLDTSelector
        mov     rmrg.xdx,ax
        return

dosex1: cmp     ah,56h          ;Rename?
        jnz     dosex2
        push    pmrg.xdi        ;Rename has a second pointer in ES:DI--we
        pop     rmrg.xdi        ;  need to restore DI here, DX below
        jmp     short dosex3

dosex2: cmp     ah,5Fh          ;Get/Make Assign-List Entry?
        callz   net5Fexit       ;  Yes, extra buffering may be needed

dosex3:
        call    GetEntryCd      ;ax=func entry code, di=ptr to entry code

        call    rstreg          ;Restore entry register?
        jz      dosex6

        cmp     byte ptr pmrg.xax+1,29h   ;Yes, Parse filename?
        jnz     dosex4

        add     ax,rmrg.xsi                   ;Yes. Increment past string
        sub     ax,offset DGROUP:rgbXfrBuf0   ;     that was parsed
        push    pmrg.xdi
        pop     rmrg.xdi                ;Restore pm di (for es:di ptr)

dosex4: mov     word ptr rmrg[si],ax    ;Restore register

        cmp     byte ptr pmrg.xax+1,4Bh ;EXEC program
        jnz     dosex6

        push    di
        mov     di,pmrg.xbx         ;Yes, restore bx too (dx restored above)
        mov     rmrg.xbx,di         ;es and ds are restored automatically
        cmp     byte ptr pmrg.xax,1 ;INT-21/4b01h (undocumented debug)?
        jnz     @f

        mov     si,npXfrBuf1       ;Yes. Pass back user ss:sp and cs:ip
        lea     si,[si].xsssp
        lea     di,[di].xsssp
        mov     es,pmrg.xes
        movsw                   ;Move ss:sp
        movsw
        movsw                   ;Move cs:ip
        movsw
@@:
        pop     di

dosex6: mov     ax,cs:[di+2]    ;Exit xfer?
        or      ax,ax
        rz

dosex7: call    CheckStatus     ;Check the DOS return status to see if the
        rnz                     ;  data should be transfered back to PM

        mov     cx,ax           ;Is a pointer being returned? (no data
        and     cl,0fh          ;  transfer)
        cmp     cl,PTR$
        jnz     dosex8

        shr     al,4            ;  yes, isolate pointer type
        mov     si,offset rmrg.xds
        mov     di,offset pmrg.xds
        cmp     al,DSBX
        jz      dosex7a
ifdef DBCS                      ; for function 63h (Get Lead Byte)
        cmp     al,DSSI
        jz      dosex7a
endif ; DBCS
        mov     si,offset rmrg.xes
        mov     di,offset pmrg.xes
dosex7a:
        mov     ax,[si]         ;  get a selector for the segment, and
        mov     bx,STD_DATA     ;  setup to return it to caller
        call    ParaToLDTSelector
        mov     [di],ax
        return

dosex8:
        push    pmrg.xds
        pop     enxseg
        call    gtarea          ;Get area for xfer from PM to DOS
        rz                      ;Something to move?

        xchg    si,di           ;Turn pointers around
        mov     dx,ds           ;dx:si -> DOS xfer area in dgroup
        mov     es,enxseg       ;es:di -> PM-caller data area
        jmp     movdat          ;Yes

; -------------------------------------------------------
;   DosExitCall -- Special processing for DOS exit call service (Int 21h/00h)
;
;       This procedure handles the obsolete Int 21h/00h terminate process
;       call in a special slimy way for Windows.  Instead of doing a 00h
;       DOS call in real mode, it hacks up the parent's PSP and does a 4Ch
;       that causes control to return to the extender, who then RETURNS TO
;       THE CALLER!!!  The caller must then do appropriate set PSP calls, etc.
;       This was implemented so pMode Windows kernel could have DOS clean up
;       after a Windows app terminates, but still have kernel get control
;       back.
;
;       Note:   This code assumes that the Parent PID field contains a
;               SELECTOR!
;
;       Note^2: If for some reason it's the DOS Extender's child that's 1800doing
;               the terminate call, we let it go through normally.
;
;   Input:      none
;   Output:     CY set if exit call processed by this routine, clear otherwise
;   Errors:
;   Uses:       none

        assume  ds:DGROUP,es:DGROUP,ss:DGROUP
        public  DosExitCall

DosExitCall     proc    near

        push    ax
        push    bx
        push    dx
        push    es

        SwitchToRealMode                ;much of this is easier in real mode
        FSTI                             ;allow interrupts

        mov     ah,51h                  ;get PSP of current task
        pushf
        FCLI
        sub     sp,8                    ; make room for stack frame
        push    bp
        mov     bp,sp
        push    es
        push    ax

        xor     ax,ax
        mov     es,ax
        mov     [bp + 8],cs
        mov     [bp + 6],word ptr (offset dec_10)
        mov     ax,es:[21h*4]
        mov     [bp + 2],ax
        mov     ax,es:[21h*4 + 2]
        mov     [bp + 4],ax
        pop     ax
        pop     es
        pop     bp
        retf

dec_10: cmp     bx,segPSPChild          ;is this our child terminating?
        jnz     @f                      ;if not, go process the call ourselves
        jmp     decTermChild            ;  yes...
@@:
        FCLI                             ;we want to preserve the current
        xor     ax,ax                   ;  rMode Int 24h handler across this
        mov     es,ax                   ;  exit call (the terminating PSP
        mov     ax,es:[24h*4]           ;  has the pMode handler address).
        mov     dx,es:[24h*4+2]         ;  So get the current Int 24h handler
        FSTI                             ;  address from the rMode IDT.

        mov     es,bx                   ;address terminating PSP
        assume  es:PSPSEG

        mov     word ptr [lpfnInt24],ax         ;point PSP to same Int 24h
        mov     word ptr [lpfnInt24+2],dx       ;  handler

        mov     ax,offset DXCODE:decTermHandler ;point PSP to our termination
        mov     word ptr [lpfnParent],ax        ;  handler
        mov     word ptr [lpfnParent+2],cs

        push    es
        mov     ax,segParentPSP         ;Windows has the PSP's parent
        push    ax                      ;  field as a selector, we need the seg
        mov     dx, [segEnviron]
        push    dx
        SwitchToProtectedMode
        assume  ds:DGROUP,es:DGROUP

        FSTI
        pop     ax                          ;get selector for environment
        or      ax,ax                       ; NULL selector (krnl386 does this)
        jnz     @f
        xor     bx,bx                       ; zero environment segment
        jmp     dec_HaveEnvSeg
@@:
        call    GetSegmentAddress
        test    bx,0fff0h                   ; >1mb?
        jz      @f                          ;   no
        xor     bx,bx                       ;   yes, zero environment segment
        xor     dx,dx
@@:
        shr     dx, 4
        shl     bx, 12
        or      bx, dx                      ;seg of environment

dec_HaveEnvSeg:
        pop     ax                          ;get parent psp off stack
        push    bx                          ;save seg of environment

        call    GetSegmentAddress       ;returns BX:DX = lma of segment
        shr     dx,4
        shl     bx,12
        or      bx,dx                   ;bx now = seg of parent psp

        SwitchToRealMode                ;back to the shadows again...
        FSTI
        pop     cx                          ;seg of environment

        pop     dx                      ;terminating PSP segment from stack
        mov     es,bx                   ;address the parent's PSP
        assume  es:PSPSEG

        mov     ax,sp
        sub     ax,12*2                 ;some magic for DOS
        mov     word ptr [lpStack],ax   ;set our stack in parent's PSP
        mov     word ptr [lpStack+2],ss

        mov     es,dx                   ;(re)address terminating PSP
        assume  es:PSPSEG
        mov     [segEnviron], cx

        mov     segParentPSP,bx         ;real DOS doesn't like selectors in
                                        ;  parent PSP field, zap it to segment

        mov     ax,pmrg.xax             ;terminate the process
        mov     ah,4Ch                  ;  with a 4Ch DOS call
        pushf
        FCLI
        sub     sp,8                    ; make room for stack frame
        push    bp
        mov     bp,sp
        push    es
        push    ax

        xor     ax,ax
        mov     es,ax
        mov     [bp + 8],cs
        mov     word ptr [bp + 6],offset decTermHandler
        mov     ax,es:[21h*4]
        mov     [bp + 2],ax
        mov     ax,es:[21h*4 + 2]
        mov     [bp + 4],ax
        pop     ax
        pop     es
        pop     bp
        retf
        assume  ds:NOTHING,es:NOTHING,ss:NOTHING        ;play it safe

decTermHandler:                         ;should return back here

        push    ss
        pop     ds

        SwitchToProtectedMode           ;back to pMode
        assume  ds:DGROUP,es:DGROUP

        FSTI

        les     bx,dword ptr pmusrsp            ;es:bx -> ip cs flag
        and     byte ptr es:[bx+2*2],not 1      ;clear CY in caller's flags

        stc                             ;exit call has been processed

dec90:
        pop     es
        pop     dx
        pop     bx
        pop     ax

        ret

EndHighSegment

BeginLowSegment

decTermChild:

        SwitchToProtectedMode
        assume  ds:DGROUP,es:DGROUP

        FSTI
        clc

        jmp     short dec90

DosExitCall     endp


; -------------------------------------------------------
;   net5Fenter  -- Additional entry processing for INT 21h/5Fh
;       functions.
;
;       INT 21h/5Fh subfunctions 2, 3, and 5 have two buffers of data to
;       transfer.  The normal DOSENTRY processing only does one, so we
;       setup the other buffer here.

        assume  ds:DGROUP,es:DGROUP,ss:DGROUP
        public  net5Fenter

net5Fenter      proc    near

        cmp     al,2                    ;This routine only works on
        rc                              ;  subfunctions 2, 3, and 5
        cmp     al,4
        rz
        cmp     al,6
        rnc

        push    ax
        mov     ax,int21esdi            ;entry code for INT 21h/5Fh extra buff
        call    gtarea                  ;let gtarea set it up
        pop     ax

        cmp     al,3                    ;Make redirection function?
        rnz

; 5F/03 needs a buffer copied down to A1, but it's non standard in that
; the buffer contains two (count'em 2) asciiz strings

        push    ax
        push    cx
        push    ds

        mov     ds,pmrg.xes             ;user's ES:DI -> buffer, gtarea sets
        xor     ah,ah                   ;  up our si to have user's di
        mov     cl,2

@@:     lodsb                           ;copy one asciiz string
        stosb
        cmp     al,ah
        jnz     @b

        dec     cl                      ;   and then the other
        jnz     @b

        pop     ds
        pop     cx
        pop     ax
        return

net5Fenter      endp


; -------------------------------------------------------
;   net5Fexit  -- Additional exit processing for INT 21h/5Fh
;       functions.
;
;       INT 21h/5Fh subfunctions 2, 3, & 5 have 2 buffers of data to transfer.
;       The normal DOSENTRY processing only does one, so do the other
;       buffer here.

        assume  ds:DGROUP,es:DGROUP,ss:DGROUP
        public  net5Fexit

net5Fexit       proc    near

        cmp     al,2                    ;This routine only works on
        rc                              ;  subfunctions 2, 3 and 5
        cmp     al,4
        rz
        cmp     al,6
        rnc

        push    pmrg.xdi                ;Restore protected mode DI register
        pop     rmrg.xdi

        cmp     al,2                    ;Get redirection function?
        jz      @f
        cmp     al,5
        rnz
@@:

; 5F/02 & 05 need a buffer copied from A1

        test    byte ptr rmflags,1      ;Success? (carry flag)
        rnz                             ;  No, don't transfer anything

        push    ax

        mov     ax,int21esdi+2          ;exit code for int 21/5F extra buffer
        push    pmrg.xes
        pop     enxseg
        call    gtarea                  ;let gtarea setup the move
        xchg    si,di
        mov     dx,ds
        mov     es,enxseg
        call    movdat                  ;  and let movdat move it

        pop     ax
        return

net5Fexit       endp


; -------------------------------------------------------
;   RSTREG      -- This function sets up to restore the original
;       protected-mode registers.  This cleans up after register
;       translations performed when going into the or returning
;       from an MS-DOS call.  On entry, AX contains the entry code
;       value from the entry/exit operations table.  If this code
;       implies that a register needs to be restored this function
;       will return with NZ true and AX = original register value
;       and SI pointing to the appropriate location in the PMRG array.
;       If no register needs to be restored, return with Z true.
;
;   Input:  AX      - entry code value
;   Output: NZ true if register needs to be restores
;           AX      - register value to restore
;           SI      - pointer into PMRG to the register image
;           ZR true if no restoration needed
;   Errors: none
;   Uses:   AX, SI modified

        assume  ds:DGROUP,es:DGROUP,ss:DGROUP
        public  rstreg

rstreg: or      ax,ax
        rz

        shr     ax,3
        and     ax,1Eh
        rz

        cmp     al,2*DTA        ;DTA?
        rz

        xchg    si,ax           ;No. Restore appropriate register, e.g., dx
        mov     si,regoffs[si-2]
        mov     ax,word ptr pmrg[si]
        return


; -------------------------------------------------------
;   GTAREA      -- This function examines the entry code/exit code
;       parameter and determines if any data transfer needs to be
;       performed.  If so, it sets up pointers and length codes for
;       the transfer.
;       There are two transfer buffers used.  The A0 buffer is 60h bytes
;       long and the A1 buffer is CB_XFRBUF1 bytes (about 4k) long.
;
;   Input:  AX      - entry code/exit code
;   Output: NZ true if something needs to be transferred
;           SI      - offset of source pointer
;           DI      - offset of destination pointer
;           ENXSEG  - segment for caller's buffer
;                     (source on entry, destination on exit)
;           CX      - transfer length/type code
;   Errors: none
;   Uses:   AX, CX, SI, DI, ES modified
;           ENXSEG modified

        assume  ds:DGROUP,es:DGROUP,ss:DGROUP
        public  gtarea

gtarea:
        test    ah,RP           ;Real/PM xfer (ah = 3f/40)?
        jz      gtare2

        mov     si,pmrg.xds     ;Yes. *si:pmrg.xdx = pm caller's xfer area
        and     si,SELECTOR_INDEX
        test    ah,EX           ;Exit call?
        jz      gtare1
        push    es
        push    ax
        mov     es,selGDT
        mov     al,es:[si].adrBaseHigh
        mov     ah,es:[si].adrbBaseHi386
        test    ax,0FFF0h                   ; check for transfer to extended
                                            ; memory
        pop     ax
        pop     es
        jnz     @f
        jmp     gtar54
@@:
        mov     cx,rmrg.xax     ;Yes. cx = amount read/written
        sub     rcount,cx       ;Update remaining count
        cmp     cx,rmrg.xcx     ;All that was requested?
        jnc     gtare3
        mov     rcount,0        ;No: done
        jmp     short gtare3

gtare1: push    ax              ;Read/Write entry
        mov     ax,si
        mov     dx,pmrg.xdx     ;ax:dx = selector:offset of buffer
        call    SelOff2SegOff   ;translate to seg:off if in conventional mem
        jnz     gtar12
        mov     rmrg.xds,ax     ;store corresponding paragraph
        mov     rmrg.xdx,dx     ;  and offset
        pop     ax
        jmp     short gtar54    ;No more setup needed

gtar12: pop     ax              ;XRAM/RRAM read/write entry
        mov     cx,rcount       ;Try to xfer remaining amount
        cmp     cx,CB_XFRBUF1   ;Too much to fit in buffer?
        jbe     gtar14
        mov     cx,CB_XFRBUF1   ;Yes, only transfer a buffer size
gtar14: mov     rmrg.xcx,cx
        jmp     short gtare3

gtare2: test    ah,A0+A1        ;xfer area?
        jz      gtare4

gtare3: mov     di,offset DGROUP:rgbXfrBuf0     ;Point at small buffer (90h bytes)
        test    ah,1            ;Area 0 (small area) ?
        jnz     gtare4
        mov     di,npXfrBuf1       ;No. Point at large buffer (4K)

gtare4: push    ax              ;Store ptr to communication area for DOS
        mov     si,di
        shr     al,3            ;Get al = 2 * data ptr type, e.g., DSDX (ds:dx)
        and     al,1Eh          ;al = word offset for data ptr type
        jz      gtare7
        cmp     al,2*DTA        ;DOS Data Transfer Area?
        jnz     gtare5
        mov     si,pmdta        ;Yes, load DTA offset
        push    pmdta+2         ;and the segment
        pop     enxseg
        jmp     short gtare7

gtare5: cmp     al,2*ES$
        jnz     gtare6
        test    ah,80h          ;INT-21 49/4A. Ignore exit call
        mov     ax,0
        jnz     gtar52
        mov     si,pmrg.xes     ;Entry call. si = RAM xfer selector
        call    gtpara          ;Get paragraph given by [si].gdt
        jnz     gtar52          ;XRAM?
        mov     rmrg.xes,ax     ;No, store RRAM paragraph
gtar52: pop     cx              ;Kill saved ptr

gtar54: xor     cx,cx   ;RZ with zero count, i.e., no RM/PM xfer needed
        mov     rcount,cx
        return

gtare6: test    ah,80h          ;Entry?
        cbw
        push    ax
        xchg    di,ax           ;Save real-mode area offset
        mov     di,regoffs[di-2]        ;di = offset of saved register value
        mov     si,word ptr pmrg[di]    ;si = saved value
        jnz     gtar62
        mov     word ptr rmrg[di],ax    ;Entry. Store real-mode area offset
        cmp     byte ptr pmrg.xax+1,29h ;Parse filename?
        jnz     gtar62
        mov     cx,npXfrBuf1       ;Yes. Use npXfrBuf1 for FCB info
        mov     word ptr rmrg.xdi,cx
gtar62: xchg    di,ax           ;Restore di = real-mode area offset

        pop     ax
        cmp     ax,ESDI*2
        jne     gtare7

        push    pmrg.xes
        pop     enxseg

gtare7: pop     cx              ;Recover entrycd/exitcd
        and     cx,0Fh          ;RNZ if something to xfer
        rz                      ;No

        mov     dx,pmrg.xds     ;Xfer needed. dx = original XRAM data selector
        cmp     cl,AX$          ;Use rmrg.xax?
        jz      gtare8
        cmp     cl,CX$          ;Use pmrg.xcx?
        rnz                     ;No, just RNZ


;Return dx:0 = pmrg.xds:si+ccount, where dx is a scratch selector
;and si = 0.  This ensures that dx:si can address a bufferful of bytes
;without overflowing si (sort of like a huge ptr).

gtare8: xchg    ax,si           ;ax = original offset
        mov     si,dx           ;si = pmrg.xds (PM data selector)
        and     si,SELECTOR_INDEX
        xor     dx,dx           ;dxax = original offset in 32-bit form
        add     ax,ccount
        adc     dx,0            ;dxax += ccount (current xfer count)
        push    es
        mov     es,selGDT       ;Point at GDT
        add     ax,es:[si].adrBaseLow
        adc     dl,es:[si].adrBaseHigh  ;dxax absolute XRAM address
        adc     dh,es:[si].adrbBaseHi386
        mov     si,SEL_DOSSCR
        cCall   NSetSegmentDscr,<si,dx,ax,0,0ffffh,STD_DATA>
        pop     es

; original code... changed to line below to fix file read problem.
; may cause other bugs.....
;       mov     dx,si           ;Return scratch selector with 0 offset since
;
        or      si,SELECTOR_TI
        mov     enxseg,si       ;Return scratch selector with 0 offset since
        xor     si,si           ; it points directly to the transfer location
        or      sp,sp           ;RNZ to indicate xfer needed
        return

; -------------------------------------------------------
;   GetEntryCd  --  This routine puts the entry code for the
;                   DOS function into ax.
;
;   Input:  AH      - MS-DOS function number
;   Output: AX      - entry code for function
;           DI      - address of entry code returned
;
;   Errors: none
;   Uses:   AX, DI modified

        assume  ds:DGROUP,es:DGROUP,ss:DGROUP
        public  GetEntryCd

GetEntryCd      proc    near

        push    bx

        cmp     ah,MaxInt21     ;Check for unsupported DOS call
        jbe     @f
        mov     di,offset DXPMCODE:Unsupported
        jmp     gec90
@@:
        mov     di,offset DXPMCODE:pmrmxfr

ifdef DBCS
        cmp     ah,63h          ;Get Lead Byte Table?
        jnz     gec10

if DEBUG   ;------------------------------------------------------------

        cmp     al,2
        jbe     gec15

        Debug_Out "Int 21h/63h Unsupported Function (#AX)"
        jmp     short gec80
gec15:

endif   ;DEBUG  --------------------------------------------------------

        cmp     al,0            ;Int 21/63/00 is special
        jne     gec80

        mov     di,offset DXPMCODE:int2163
        jmp     short gec90
gec10:
ENDIF ; DBCS

gec20:  cmp     ah,5Eh          ;Network Machine Name/Printer Setup Str?
        jnz     gec40

if DEBUG   ;------------------------------------------------------------

        cmp     al,3
        jna     gec25

        Debug_Out "Int 21h/5Eh Unsupported Function (#AX)"
        jmp     short gec80
gec25:

endif   ;DEBUG  --------------------------------------------------------

        cmp     al,3            ;Int 21-5E/00-03 are special
        ja      gec80

        mov     bl,al
        mov     di,offset DXPMCODE:int215E
        jmp     short gec85


gec40:  cmp     ah,5Fh          ;Get/Make Assign-List Entry?
        jnz     gec80

if DEBUG   ;------------------------------------------------------------

        cmp     al,5
        ja      @f
        cmp     al,2
        jnb     gec45
@@:
        cmp     al,30h          ;Register based.  Get Redirector version
                                ;used by Lanman Enhanced.
        je      gec80
        Debug_Out "Int 21h/5Fh Unsupported Function (#AX)"
        jmp     short gec80
gec45:

endif   ;DEBUG  --------------------------------------------------------

        cmp     al,2            ;Int 21/5F/02-05 are special
        jb      gec80
        cmp     al,5
        ja      gec80

        mov     bl,al
        sub     bl,2
        mov     di,offset DXPMCODE:int215F02
        jmp     short gec85


gec80:  mov     bl,ah
gec85:  xor     bh,bh
        shl     bx,2

        add     di,bx                   ;Address of entry code
gec90:
        mov     ax,word ptr cs:[di]     ;The entry code itself
        pop     bx
        return

GetEntryCd      endp


; -------------------------------------------------------
;   CheckStatus --  This routine determines if data should be copied
;                   back to protect mode by checking the DOS function
;                   return status.
;
;   Input:  none
;   Output: NZ    - if data should NOT be copied, Z otherwise
;
;   Errors: none
;   Uses:   none

        assume  ds:DGROUP,es:DGROUP,ss:DGROUP
        public  CheckStatus

CheckStatus     proc    near

; For now, only worry about the functions that return variable length
; results, like ASCIIZ strings.

        cmp     byte ptr pmrg.xax+1,32h
        jz      @f
        cmp     byte ptr pmrg.xax+1,47h         ;Get Current Directory
        jz      @f
        cmp     byte ptr pmrg.xax+1,5Ah         ;Create Temporary File
        jz      @f
        cmp     byte ptr pmrg.xax+1,5Eh         ;Get Machine Name/Printer Str
        jc      cks90
        cmp     byte ptr pmrg.xax+1,5Fh         ;Get Redirection Entry
        ja      cks90
@@:
        test    byte ptr rmflags,1      ;Carry set?
        return

cks90:  cmp     al,al                   ;Assume status is okay (or doesn't
        return                          ;  matter) -- set Z and return

CheckStatus     endp


; -------------------------------------------------------
;   DOSXEC      -- This function performs the transfer of the
;       DOS exec parameter block on entry to DOS function 4Bh.
;
; transfer int-21 ah = 4b0x EXEC block and associated strings
; to Area 1.  This area is laid out partly analogously to a PSP as follows:
;
; 0-1f    EXEC block defined according to the execblk struc above
; 20-2f   FCB1
; 30-3f   FCB2
; 40-bf   command line
;
; cx, si, di changed.

        assume  ds:DGROUP,es:DGROUP,ss:DGROUP
        public  dosxec

dosxec:
        push    ax              ;Save entrcd code
        push    bx
        push    dx

        mov     cx,10h          ;Setup parameter block.  Xfer pm user block first
        mov     bx,npXfrBuf1    ;Point at larger buffer
        mov     di,bx
        mov     dx,segDXDataPM
        mov     rmrg.xbx,di     ;int-21 4b0x expects es:bx -> exec block
        mov     si,pmrg.xbx     ;npXfrBuf1:0 - 1f (xtra room for undocumented)
        mov     ds,pmrg.xes
        rep     movsw           ;copy caller's exec param block to our buffer

; Copy FCB1 down if the user has specified one.

dsxc20: mov     ax,word ptr es:[bx].fcb1ptr
        cmp     ax,0FFFFh
        jz      dsxc22
        or      ax,word ptr es:[bx].fcb1ptr+2
        jz      dsxc22

        lds     si,es:[bx].fcb1ptr      ;Get pointer to FCB1
        mov     word ptr es:[bx].fcb1ptr,di      ;store new pointer in the copy of the
        mov     word ptr es:[bx].fcb1ptr+2,dx    ; exec block we are building
        mov     cl,8                    ;copy FCB1 down to our buffer
        rep     movsw
        jmp     short dsxc24
dsxc22: add     di,10h

; Copy FCB2 down if the user has specified one.

dsxc24: mov     ax,word ptr es:[bx].fcb2ptr
        cmp     ax,0FFFFh
        jz      dsxc26
        or      ax,word ptr es:[bx].fcb2ptr+2
        jz      dsxc26

        lds     si,es:[bx].fcb2ptr      ;Move FCB2
        mov     word ptr es:[bx].fcb2ptr,di
        mov     word ptr es:[bx].fcb2ptr+2,dx
        mov     cl,8
        rep     movsw
        jmp     short dsxc30
dsxc26: add     di,10h

; Copy the command line down.

dsxc30: lds     si,es:[bx].cmdptr       ;Move command line
        mov     word ptr es:[bx].cmdptr,di
        mov     word ptr es:[bx].cmdptr+2,dx
        mov     cl,[si]
        inc     cx              ;Include count
        inc     cx              ;Include final CR not included in count
        rep     movsb

; Now, we need to set up the enviroment table to be passed to the
; child program.

        mov     di,bx           ;di = npXfrBuf1
        mov     dx,es:[di]      ;Setup desired environment
        or      dx,dx           ;Use parent's environment?
        jnz     dosxegotenv

; pick up the environment segment from the current PDB. It's a selector,
; so it has to be translated.
        push    bx
        push    di
        push    es
        mov     ax,SEL_DXDATA OR STD_RING
        mov     ds,ax

        SwitchToRealMode                    ;much of this is easier in real mode

        mov     ah,51h                      ;get PSP of current task
        pushf
        sub     sp,8                    ; make room for stack frame
        push    bp
        mov     bp,sp
        push    es
        push    ax

        xor     ax,ax
        mov     es,ax
        mov     [bp + 8],cs
        mov     [bp + 6],word ptr (offset dosxeret)
        mov     ax,es:[21h*4]
        mov     [bp + 2],ax
        mov     ax,es:[21h*4 + 2]
        mov     [bp + 4],ax
        pop     ax
        pop     es
        pop     bp
        retf
dosxeret:
        assume  es:PSPSEG
        mov     es, bx                      ;current PSP
        mov     dx, es:[segEnviron]         ;get environment (currently selector)
        push    dx                          ;save over call (bugbug is this needed?)
        SwitchToProtectedMode
        pop     dx                          ;bugbug is this needed?

        pop     es
        pop     di
        pop     bx


dosxegotenv:
        xor     si,si           ;No. Setup to copy desired environment down
        mov     ds,dx           ;ds = dx has caller's selector. Use 0 offset
        add     di,100h
        shr     di,4            ;Convert offset to paragraph
        mov     dx,segDXDataPM
        add     dx,di           ;dx = absolute paragraph within larger buffer
        shl     di,4            ;Convert back (with low nibble cleared)
        mov     cx,CB_XFRBUF1   ;Max room available for environment
        sub     cx,100h

dosxe2: lodsw                   ;Copy environment down
        stosw
        or      ax,ax
        jz      dosxe4
        dec     si              ;Check every byte offset in environment for double 0
        dec     di
        loop    dosxe2
        xor     dx,dx           ;Environment too large for buffer: use parent's
                                ;Issue error message? Program might run with parent's
                                ; and not with desired monster environment
dosxe4: push    es              ;Fix up parameter block entry
        pop     ds              ;ds:dgroup
        mov     [bx].evrnmt,dx

        pop     dx
        pop     bx
        pop     ax              ;Restore entrcd code
        return


; -------------------------------------------------------
;   XFRFLG      -- This function will transfer the relevant real-mode
;       flags to the protected mode IRET return information on the
;       stack for returning to the protected mode caller.
;
;   Input:
;   Output:
;   Errors:
;   Uses:   AX, CX, DI modified

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  xfrflg

xfrflg: push    es
        les     di,dword ptr pmusrsp    ;es:[di] = ip cs fl (assume 80286)
        mov     ax,rmflags
        mov     cx,es:[di+2*2]          ;Get pm user entry flags
        and     ch,not 19h              ;Only allow real-mode program to
        and     ah,18h                  ; change OF and DF in high byte of flags
        or      ah,ch
        mov     es:[di+2*2],ax
        pop     es
        return

; -------------------------------------------------------
        subttl      Handlers for Special Case Dos Functions
        page
; -------------------------------------------------------
;       HANDLERS FOR SPECIAL CASE DOS FUNCTIONS
; -------------------------------------------------------

; -------------------------------------------------------
;   Terminate process -- This routine replaces the apps
;       termination vector in the PSP with ours.  This allows
;       us to clean up the dos extender.
;
;   Entry: nothing
;   Exit: nothing
;   Uses: none
;
        assume  ds:dgroup,es:nothing
        public TerminateProcess
TerminateProcess proc near
        pusha
        push    es
        ;
        ; Get the childs PSP (bugbug do we need to get the current psp?)
        ;

        SwitchToRealMode

        mov     ah,51h
        pushf
        sub     sp,8                    ; make room for stack frame
        push    bp
        mov     bp,sp
        push    es
        push    ax

        xor     ax,ax
        mov     es,ax
        mov     [bp + 8],cs
        mov     [bp + 6],offset tp_10
        mov     ax,es:[21h*4]
        mov     [bp + 2],ax
        mov     ax,es:[21h*4 + 2]
        mov     [bp + 4],ax
        pop     ax
        pop     es
        pop     bp
        retf

tp_10:  FSTI

        ;
        ; Change the termination vector to point to the dos extender
        ;
        mov     es,bx
        mov     bx,es:[0ah]
        mov     ax,es:[0ch]
        mov     cx,offset ChildTerminationHandler
        mov     es:[0ah],cx
        mov     cx,segDXCode
        mov     es:[0ch],cx

        ;
        ; Save the old termination vector for restoration later
        ;
        mov     cx,segCurrentHostData
        mov     es,cx
        mov     word ptr es:[HdPspTerminate],bx
        mov     word ptr es:[HdPspTerminate+2],ax

        SwitchToProtectedMode

        pop     es
        popa
        ret

TerminateProcess endp

DXPMCODE  ends

;****************************************************************

        end