 \SAMPCODE
 \SAMPCODE\ADVMSDOS
 \SAMPCODE\ADVMSDOS\CHAP03
 \SAMPCODE\ADVMSDOS\CHAP03\HELLO_C.ASM

         title   HELLO.COM --- print hello on terminal
         page    55,132

 ;
 ; HELLO-C.ASM   Demonstrates components of a
 ;               functional COM-type assembly
 ;               language program, and an MS-DOS
 ;               function call.
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;
 ; Build:  MASM HELLO-C;
 ;         LINK HELLO-C;
 ;         EXE2BIN HELLO.EXE HELLO.COM
 ;         DEL HELLO.EXE
 ;
 ; Usage:  HELLO
 ;

 stdin   equ     0               ; standard input handle
 stdout  equ     1               ; standard output handle
 stderr  equ     2               ; standard error handle

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed


 _TEXT   segment word public 'CODE'

         org     100h            ; COM files always have
                                 ; an origin of 100H

         assume  cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT

 print   proc    near            ; entry point from MS-DOS

         mov     ah,40h          ; function 40H = write
         mov     bx,stdout       ; handle for standard output
         mov     cx,msg_len      ; length of message
         mov     dx,offset msg   ; address of message
         int     21h             ; transfer to MS-DOS

         mov     ax,4c00h        ; exit, return code = 0
         int     21h             ; transfer to MS-DOS

 print   endp


 msg     db      cr,lf           ; message to display
         db      'Hello World!',cr,lf

 msg_len equ     $-msg           ; length of message


 _TEXT   ends

         end     print           ; defines entry point


 \SAMPCODE\ADVMSDOS\CHAP03\HELLO_E.ASM

         title   HELLO.EXE --- print Hello on terminal
         page    55,132
 ;
 ; HELLO-E.ASM   Demonstrates components of a
 ;               functional EXE-type assembly
 ;               language program, use of segments,
 ;               and a MS-DOS function call.
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;
 ; Build:   MASM HELLO-E;
 ;          LINK HELLO-E;
 ;
 ; Usage:   HELLO-E
 ;

 stdin   equ     0               ; standard input handle
 stdout  equ     1               ; standard output handle
 stderr  equ     2               ; standard error handle

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:_DATA,ss:STACK

 print   proc    far             ; entry point from MS-DOS

         mov     ax,_DATA        ; make our data segment
         mov     ds,ax           ; addressable...

         mov     ah,40h          ; function 40h = write
         mov     bx,stdout       ; standard output handle
         mov     cx,msg_len      ; length of message
         mov     dx,offset msg   ; address of message
         int     21h             ; transfer to MS-DOS

         mov     ax,4c00h        ; exit, return code = 0
         int     21h             ; transfer to MS-DOS

 print   endp

 _TEXT   ends


 _DATA   segment word public 'DATA'

 msg     db      cr,lf           ; message to display
         db      'Hello World!',cr,lf

 msg_len equ     $-msg           ; length of message

 _DATA   ends


 STACK   segment para stack 'STACK'

         db      128 dup (?)

 STACK   ends

         end     print           ; defines entry point


 \SAMPCODE\ADVMSDOS\CHAP05
 \SAMPCODE\ADVMSDOS\CHAP05\MOUDEMO.C

  /*
     Simple Demo of Int 33H Mouse Driver
     (C) 1988 Ray Duncan

     Compile with: CL MOUDEMO.C

     Usage:   MOUDEMO  (press both mouse buttons to exit)
 */

 #include <stdio.h>
 #include <dos.h>

 union REGS regs;

 void cls(void);                     /* function prototypes */
 void gotoxy(int, int);

 main(int argc, char *argv[])
 {
     int x,y,buttons;                /* some scratch variables */
                                     /* for the mouse state */

     regs.x.ax = 0;                  /* reset mouse driver */
     int86(0x33, &regs, &regs);      /* and check status */

     if(regs.x.ax == 0)              /* exit if no mouse */
     {   printf("\nMouse not available\n");
         exit(1);
     }

     cls();                          /* clear the screen */
     gotoxy(45,0);                   /* and show help info */
     puts("Press Both Mouse Buttons To Exit");

     regs.x.ax = 1;                  /* display mouse cursor */
     int86(0x33, &regs, &regs);

     do {
         regs.x.ax = 3;              /* get mouse position */
         int86(0x33, &regs, &regs);  /* and button status */
         buttons = regs.x.bx & 3;
         x = regs.x.cx;
         y = regs.x.dx;

         gotoxy(0,0);                /* display mouse position */
         printf("X = %3d  Y = %3d", x, y);

     } while(buttons != 3);          /* exit if both buttons down */

     regs.x.ax = 2;                  /* hide mouse cursor */
     int86(0x33, &regs, &regs);

     cls();                          /* display message and exit */
     gotoxy(0,0);
     puts("Have a Mice Day!");
 }

 /*
     Clear the screen
 */
 void cls(void)
 {
     regs.x.ax = 0x0600;             /* ROM BIOS video driver */
     regs.h.bh = 7;                  /* Int 10H Function 6 */
     regs.x.cx = 0;                  /* initializes a window */
     regs.h.dh = 24;
     regs.h.dl = 79;
     int86(0x10, &regs, &regs);
 }

 /*
     Position cursor to (x,y)
 */
 void gotoxy(int x, int y)
 {
     regs.h.dl = x;                  /* ROM BIOS video driver */
     regs.h.dh = y;                  /* Int 10H Function 2 */
     regs.h.bh = 0;                  /* positions the cursor */
     regs.h.ah = 2;
     int86(0x10, &regs, &regs);
 }


 \SAMPCODE\ADVMSDOS\CHAP05\TRYBREAK.C

 /*
     TRYBREAK.C

     Demo of BREAK.ASM Ctrl-Break and Ctrl-C
     interrupt handler, by Ray Duncan

     Build:   MASM /Mx BREAK;
              CL TRYBREAK.C BREAK.OBJ

     Usage:   TRYBREAK

 */

 #include <stdio.h>

 main(int argc, char *argv[])
 {
     int hit = 0;                    /* flag for keypress */
     int c = 0;                      /* character from keyboard */
     static int flag = 0;            /* true if Ctrl-Break
                                        or Ctrl-C detected */

     puts("\n*** TRYBREAK.C running ***\n");
     puts("Press Ctrl-C or Ctrl-Break to test handler,");
     puts("Press the Esc key to exit TRYBREAK.\n");

     capture(&flag);                 /* install new Ctrl-C and
                                        Ctrl-Break handler and
                                        pass address of flag */

     puts("TRYBREAK has captured interrupt vectors.\n");

     while(1)
     {
         hit = kbhit();              /* check for keypress */
                                     /* (MS-DOS sees Ctrl-C
                                        when keyboard polled) */

         if(flag != 0)               /* if flag is true, an */
         {                           /* interrupt has occurred */
             puts("\nControl-Break detected.\n");
             flag = 0;               /* reset interrupt flag */
         }
         if(hit != 0)                /* if any key waiting */
         {
             c = getch();            /* read key, exit if Esc */
             if( (c & 0x7f) == 0x1b) break;
             putch(c);               /* otherwise display it */
         }
     }
     release();                      /* restore original Ctrl-C
                                        and Ctrl-Break handlers */

     puts("\n\nTRYBREAK has released interrupt vectors.");
 }

 \SAMPCODE\ADVMSDOS\CHAP05\BREAK.ASM

         title   Ctrl-C & Ctrl-Break Handlers
         page    55,132

 ;
 ; Ctrl-C and Ctrl-Break handler for Microsoft C
 ; programs running on IBM PC compatibles
 ;
 ; by Ray Duncan
 ;
 ; Assemble with:  C>MASM /Mx BREAK;
 ;
 ; This module allows C programs to retain control
 ; when the user enters a Ctrl-Break or Ctrl-C.
 ; It uses Microsoft C parameter passing conventions
 ; and assumes the C small memory model.
 ;
 ; The procedure _capture is called to install
 ; a new handler for the Ctrl-C and Ctrl-Break
 ; interrupts (1BH and 23H).  _capture is passed
 ; the address of a static variable, which will be
 ; set to True by the handler whenever a Ctrl-C
 ; or Ctrl-Break is detected.  The C syntax is:
 ;
 ;               static int flag;
 ;               capture(&flag);
 ;
 ; The procedure _release is called by the C program
 ; to restore the original Ctrl-Break and Ctrl-C
 ; handler.  The C syntax is:
 ;
 ;               release();
 ;
 ; The procedure ctrlbrk is the actual interrupt
 ; handler.  It receives control when a software
 ; Int 1BH is executed by the ROM BIOS or Int 23H
 ; is executed by MS-DOS.  It simply sets the C
 ; program's variable to True (1) and returns.
 ;

 args    equ     4               ; stack offset of arguments,
                                 ; C small memory model

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed


 _TEXT   segment word public 'CODE'

         assume cs:_TEXT


         public  _capture
 _capture proc   near            ; take over Control-Break
                                 ; and Ctrl-C interrupt vectors

         push    bp              ; set up stack frame
         mov     bp,sp

         push    ds              ; save affected registers
         push    di
         push    si

                                 ; save address of
                                 ; calling program's "flag"
         mov     ax,word ptr [bp+args]
         mov     word ptr cs:flag,ax
         mov     word ptr cs:flag+2,ds

                                 ; save address of original
         mov     ax,3523h        ; Int 23H handler
         int     21h
         mov     word ptr cs:int23,bx
         mov     word ptr cs:int23+2,es

         mov     ax,351bh        ; save address of original
         int     21h             ; Int 1BH handler
         mov     word ptr cs:int1b,bx
         mov     word ptr cs:int1b+2,es

         push    cs              ; set DS:DX = address
         pop     ds              ; of new handler
         mov     dx,offset _TEXT:ctrlbrk

         mov     ax,02523H       ; set Int 23H vector
         int     21h

         mov     ax,0251bH       ; set Int 1BH vector
         int     21h

         pop     si              ; restore registers
         pop     di
         pop     ds

         pop     bp              ; discard stack frame
         ret                     ; and return to caller

 _capture endp


         public  _release
 _release proc   near            ; restore original Ctrl-C
                                 ; and Ctrl-Break handlers

         push    bp              ; save registers
         push    ds
         push    di
         push    si

         lds     dx,cs:int1b     ; get address of previous
                                 ; Int 1BH handler

         mov     ax,251bh        ; set Int 1BH vector
         int     21h

         lds     dx,cs:int23     ; get address of previous
                                 ; Int 23H handler

         mov     ax,2523h        ; set Int 23H vector
         int     21h

         pop     si              ; restore registers
         pop     di              ; and return to caller
         pop     ds
         pop     bp
         ret

 _release endp


 ctrlbrk proc    far             ; Ctrl-C and Ctrl-Break
                                 ; interrupt handler

         push    bx              ; save registers
         push    ds


         lds     bx,cs:flag      ; get address of C program's
                                 ; "flag variable"

                                 ; and set the flag "true"
         mov     word ptr ds:[bx],1

         pop     ds              ; restore registers
         pop     bx

         iret                    ; return from handler

 ctrlbrk endp


 flag    dd      0               ; far pointer to caller's
                                 ; Ctrl-Break or Ctrl-C flag

 int23   dd      0               ; address of original
                                 ; Ctrl-C handler

 int1b   dd      0               ; address of original
                                 ; Ctrl-Break handler

 _TEXT   ends

         end


 \SAMPCODE\ADVMSDOS\CHAP07
 \SAMPCODE\ADVMSDOS\CHAP07\TALK.ASM

         title   TALK --- Simple terminal emulator
         page    55,132
         .lfcond             ;list false conditionals too

 ;
 ; TALK.ASM  --- Simple IBM PC terminal emulator
 ;
 ; Copyright (c) 1988 Ray Duncan
 ;
 ; Build:  MASM TALK;
 ;         LINK TALK;
 ;
 ; Usage:  TALK
 ;

 stdin   equ     0               ; standard input handle
 stdout  equ     1               ; standard output handle
 stderr  equ     2               ; standard error handle

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed
 bsp     equ     08h             ; ASCII backspace
 escape  equ     1bh             ; ASCII escape code

 dattr   equ     07h             ; display attribute to use
                                 ; while in emulation mode

 bufsiz  equ     4096            ; size of serial port buffer

 echo    equ     0               ; 0=full-duplex, -1=half-duplex

 true    equ     -1
 false   equ     0

 com1    equ     true            ; Use COM1 if nonzero
 com2    equ     not com1        ; Use COM2 if nonzero

 pic_mask  equ   21h             ; 8259 int. mask port
 pic_eoi   equ   20h             ; 8259 EOI port

         if      com1
 com_data equ    03f8h           ; port assignments for COM1
 com_ier  equ    03f9h
 com_mcr  equ    03fch
 com_sts  equ    03fdh
 com_int  equ    0ch             ; COM1 interrupt number
 int_mask equ    10h             ; IRQ4 mask for 8259
         endif

         if      com2
 com_data equ    02f8h           ; port assignments for COM2
 com_ier  equ    02f9h
 com_mcr  equ    02fch
 com_sts  equ    02fdh
 com_int  equ    0bh             ; COM2 interrupt number
 int_mask equ    08h             ; IRQ3 mask for 8259
         endif

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:_DATA,es:_DATA,ss:STACK

 talk    proc    far             ; entry point from MS-DOS

         mov     ax,_DATA        ; make data segment addressable
         mov     ds,ax
         mov     es,ax
                                 ; initialize display for
                                 ; terminal emulator mode...

         mov     ah,15           ; get display width and
         int     10h             ; current display mode
         dec     ah              ; save display width for use
         mov     columns,ah      ; by the screen clear routine

         cmp     al,7            ; enforce text display mode
         je      talk2           ; mode 7 ok, proceed

         cmp     al,3
         jbe     talk2           ; modes 0-3 ok,proceed

         mov     dx,offset msg1
         mov     cx,msg1_len
         jmp     talk6           ; print error message and exit

 talk2:  mov     bh,dattr        ; clear screen and home cursor
         call    cls

         call    asc_enb         ; capture serial port interrupt
                                 ; vector and enable interrupts

         mov     dx,offset msg2  ; display message
         mov     cx,msg2_len     ; 'Terminal emulator running'
         mov     bx,stdout       ; BX = standard output handle
         mov     ah,40h          ; Fxn 40H = write file or device
         int     21h             ; transfer to MS-DOS

 talk3:  call    pc_stat         ; keyboard character waiting?
         jz      talk4           ; nothing waiting, jump

         call    pc_in           ; read keyboard character

         cmp     al,0            ; is it a function key?
         jne     talk32          ; not function key, jump

         call    pc_in           ; function key, discard 2nd
                                 ; character of sequence
         jmp     talk5           ; then terminate program

 talk32:                         ; keyboard character received
         if      echo
         push    ax              ; if half-duplex, echo
         call    pc_out          ; character to PC display
         pop     ax
         endif

         call    com_out         ; write char. to serial port

 talk4:  call    com_stat        ; serial port character waiting?
         jz      talk3           ; nothing waiting, jump

         call    com_in          ; read serial port character

         cmp     al,20h          ; is it control code?
         jae     talk45          ; jump if not

         call    ctrl_code       ; control code, process it

         jmp     talk3           ; check keyboard again

 talk45:                         ; non-control char. received,
         call    pc_out          ; write it to PC display

         jmp     talk4           ; see if any more waiting

 talk5:                          ; function key detected,
                                 ; prepare to terminate...

         mov     bh,07h          ; clear screen and home cursor
         call    cls

         mov     dx,offset msg3  ; display farewell message
         mov     cx,msg3_len

 talk6:  push    dx              ; save message address
         push    cx              ; and message length

         call    asc_dsb         ; disable serial port interrupts
                                 ; and release interrupt vector

         pop     cx              ; restore message length
         pop     dx              ; and address

         mov     bx,stdout       ; handle for standard output
         mov     ah,40h          ; Fxn 40H = write device
         int     21h             ; transfer to MS-DOS

         mov     ax,4c00h        ; terminate program with
         int     21h             ; return code = 0

 talk    endp


 com_stat proc   near            ; Check asynch status, returns
                                 ; Z=false if character ready
                                 ; Z=true if nothing waiting
         push    ax
         mov     ax,asc_in       ; compare ring buffer pointers
         cmp     ax,asc_out
         pop     ax
         ret                     ; return to caller

 com_stat endp


 com_in  proc    near            ; get character from serial
                                 ; port buffer, returns
                                 ; new character in AL

         push    bx              ; save register BX

 com_in1:                        ; if no char waiting, wait
         mov     bx,asc_out      ; until one is received
         cmp     bx,asc_in
         je      com_in1         ; jump, nothing waiting

         mov     al,[bx+asc_buf] ; character is ready,
                                 ; extract it from buffer

         inc     bx              ; update buffer pointer
         cmp     bx,bufsiz
         jne     com_in2
         xor     bx,bx           ; reset pointer if wrapped
 com_in2:
         mov     asc_out,bx      ; store updated pointer
         pop     bx              ; restore register BX
         ret                     ; and return to caller

 com_in  endp


 com_out proc    near            ; write character in AL
                                 ; to serial port

         push    dx              ; save register DX
         push    ax              ; save character to send
         mov     dx,com_sts      ; DX = status port address

 com_out1:                       ; check if transmit buffer
         in      al,dx           ; is empty (TBE bit = set)
         and     al,20h
         jz      com_out1        ; no, must wait

         pop     ax              ; get character to send
         mov     dx,com_data     ; DX = data port address
         out     dx,al           ; transmit the character
         pop     dx              ; restore register DX
         ret                     ; and return to caller

 com_out endp


 pc_stat proc    near            ; read keyboard status, returns
                                 ; Z=false if character ready
                                 ; Z=true if nothing waiting.
                                 ; register DX destroyed.

         mov     al,in_flag      ; if character already
         or      al,al           ; waiting, return status
         jnz     pc_stat1

         mov     ah,6            ; otherwise call MS-DOS to
         mov     dl,0ffh         ; determine keyboard status
         int     21h

         jz      pc_stat1        ; jump if no key ready

         mov     in_char,al      ; got key, save it for
         mov     in_flag,0ffh    ; "pc_in" routine

 pc_stat1:                       ; return to caller with
         ret                     ; Z flag set appropriately

 pc_stat endp


 pc_in   proc    near            ; read keyboard character,
                                 ; return it in AL
                                 ; DX may be destroyed.

         mov     al,in_flag      ; key already waiting?
         or      al,al
         jnz     pc_in1          ; yes,return it to caller

         call    pc_stat         ; try and read a character
         jmp     pc_in

 pc_in1: mov     in_flag,0       ; clear char. waiting flag
         mov     al,in_char      ; and return AL = character
         ret

 pc_in   endp


 pc_out  proc    near            ; write character in AL
                                 ; to the PC's display.

         mov     ah,0eh          ; ROM BIOS Fxn 0EH =
                                 ; "teletype output"
         push    bx              ; save register BX
         xor     bx,bx           ; assume page 0
         int     10h             ; transfer to ROM BIOS
         pop     bx              ; restore register BX
         ret                     ; and return to caller

 pc_out  endp


 cls     proc    near            ; clear display using
                                 ; char. attribute in BH
                                 ; registers AX, CX,
                                 ; and DX destroyed.

         mov     dl,columns      ; set DL,DH = X,Y of
         mov     dh,24           ; lower right corner
         mov     cx,0            ; set CL,CH=X,Y of
                                 ; upper left corner
         mov     ax,600h         ; ROM BIOS Fxn 06H =
                                 ; "scroll or initialize
                                 ; window"
         int     10h             ; transfer to ROM BIOS
         call    home            ; set cursor at (0,0)
         ret                     ; and return to caller

 cls     endp


 clreol  proc    near            ; clear from cursor to end
                                 ; of line using attribute
                                 ; in BH.  Register AX, CX,
                                 ; and DX destroyed.

         call    getxy           ; get current cursor pos'n
         mov     cx,dx           ; current position = "upper
                                 ; left corner" of window
         mov     dl,columns      ; "lower right corner" X is
                                 ; max columns, Y is same
                                 ; as upper left corner
         mov     ax,600h         ; ROM BIOS Fxn 06H =
                                 ; "scroll or initialize
                                 ; window"
         int     10h             ; transfer to ROM BIOS
         ret                     ; return to caller

 clreol  endp


 home    proc    near            ; put cursor at home position

         mov     dx,0            ; set (X,Y) = (0,0)
         call    gotoxy          ; position the cursor
         ret                     ; return to caller

 home    endp


 gotoxy  proc    near            ; position the cursor
                                 ; call with DL=X, DH=Y

         push    bx              ; save registers
         push    ax

         mov     bh,0            ; assume page 0
         mov     ah,2            ; ROM BIOS Fxn 02H =
                                 ; set cursor position
         int     10h             ; transfer to ROM BIOS

         pop     ax              ; restore registers
         pop     bx
         ret                     ; and return to caller

 gotoxy  endp


 getxy   proc    near            ; get cursor position,
                                 ; Returns DL=X, DH=Y

         push    ax              ; save registers
         push    bx
         push    cx

         mov     ah,3            ; ROM BIOS Fxn 03H =
                                 ; get cursor position
         mov     bh,0            ; assume page 0
         int     10h             ; transfer to ROM BIOS

         pop     cx              ; restore registers
         pop     bx
         pop     ax
         ret                     ; and return to caller

 getxy   endp


 ctrl_code proc  near            ; process control code
                                 ; call with AL=char

         cmp     al,cr           ; if carriage return
         je      ctrl8           ; just send it

         cmp     al,lf           ; if line feed
         je      ctrl8           ; just send it

         cmp     al,bsp          ; if backspace
         je      ctrl8           ; just send it

         cmp     al,26           ; is it cls control code?
         jne     ctrl7           ; no, jump

         mov     bh,dattr        ; cls control code, clear
         call    cls             ; screen and home cursor

         jmp     ctrl9

 ctrl7:
         cmp     al,escape       ; is it Escape character?
         jne     ctrl9           ; no, throw it away

         call    esc_seq         ; yes, emulate CRT terminal
         jmp     ctrl9

 ctrl8:  call    pc_out          ; send CR, LF, or backspace
                                 ; to the display

 ctrl9:  ret                     ; return to caller

 ctrl_code endp


 esc_seq proc    near            ; decode Televideo 950 escape
                                 ; sequence for screen control

         call    com_in          ; get next character
         cmp     al,84           ; is it clear to end of line?
         jne     esc_seq1        ; no, jump

         mov     bh,dattr        ; yes, clear to end of line
         call    clreol
         jmp     esc_seq2        ; then exit

 esc_seq1:
         cmp     al,61           ; is it cursor positioning?
         jne     esc_seq2        ; no jump

         call    com_in          ; yes, get Y parameter
         sub     al,33           ; and remove offset
         mov     dh,al

         call    com_in          ; get X parameter
         sub     al,33           ; and remove offset
         mov     dl,al
         call    gotoxy          ; position the cursor

 esc_seq2:                       ; return to caller
         ret

 esc_seq endp


 asc_enb proc    near            ; capture serial port interrupt
                                 ; vector and enable interrupt

                                 ; save address of previous
                                 ; interrupt handler...
         mov     ax,3500h+com_int ; Fxn 35H = get vector
         int     21h             ; transfer to MS-DOS
         mov     word ptr oldvec+2,es
         mov     word ptr oldvec,bx

                                 ; now install our handler...
         push    ds              ; save our data segment
         mov     ax,cs           ; set DS:DX = address
         mov     ds,ax           ; of our interrupt handler
         mov     dx,offset asc_int
         mov     ax,2500h+com_int ; Fxn 25H = set vector
         int     21h             ; transfer to MS-DOS
         pop     ds              ; restore data segment

         mov     dx,com_mcr      ; set modem control register
         mov     al,0bh          ; DTR and OUT2 bits
         out     dx,al

         mov     dx,com_ier      ; set interrupt enable
         mov     al,1            ; register on serial
         out     dx,al           ; port controller

         in      al,pic_mask     ; read current 8259 mask
         and     al,not int_mask ; set mask for COM port
         out     pic_mask,al     ; write new 8259 mask

         ret                     ; back to caller

 asc_enb endp


 asc_dsb proc    near            ; disable interrupt and
                                 ; release interrupt vector

         in      al,pic_mask     ; read current 8259 mask
         or      al,int_mask     ; reset mask for COM port
         out     pic_mask,al     ; write new 8259 mask

         push    ds              ; save our data segment
         lds     dx,oldvec       ; load address of
                                 ; previous interrupt handler
         mov     ax,2500h+com_int ; Fxn 25H = set vector
         int     21h             ; transfer to MS-DOS
         pop     ds              ; restore data segment

         ret                     ; back to caller

 asc_dsb endp


 asc_int proc    far             ; interrupt service routine
                                 ; for serial port

         sti                     ; turn interrupts back on

         push    ax              ; save registers
         push    bx
         push    dx
         push    ds

         mov     ax,_DATA        ; make our data segment
         mov     ds,ax           ; addressable

         cli                     ; clear interrupts for
                                 ; pointer manipulation

         mov     dx,com_data     ; DX = data port address
         in      al,dx           ; read this character

         mov     bx,asc_in       ; get buffer pointer
         mov     [asc_buf+bx],al ; store this character
         inc     bx              ; bump pointer
         cmp     bx,bufsiz       ; time for wrap?
         jne     asc_int1        ; no, jump
         xor     bx,bx           ; yes,reset pointer

 asc_int1:                       ; store updated pointer
         mov     asc_in,bx

         sti                     ; turn interrupts back on

         mov     al,20h          ; send EOI to 8259
         out     pic_eoi,al

         pop     ds              ; restore all registers
         pop     dx
         pop     bx
         pop     ax

         iret                    ; return from interrupt

 asc_int endp

 _TEXT   ends


 _DATA   segment word public 'DATA'

 in_char db      0               ; PC keyboard input char.
 in_flag db      0               ; <>0 if char waiting

 columns db      0               ; highest numbered column in
                                 ; current display mode (39 or 79)

 msg1    db      cr,lf
         db      'Display must be text mode.'
         db      cr,lf
 msg1_len equ $-msg1

 msg2    db      'Terminal emulator running...'
         db      cr,lf
 msg2_len equ $-msg2

 msg3    db      'Exit from terminal emulator.'
         db      cr,lf
 msg3_len equ $-msg3

 oldvec  dd      0               ; original contents of serial
                                 ; port interrupt vector

 asc_in  dw      0               ; input pointer to ring buffer
 asc_out dw      0               ; output pointer to ring buffer

 asc_buf db      bufsiz dup (?)  ; communications buffer

 _DATA   ends


 STACK   segment para stack 'STACK'

         db      128 dup (?)

 STACK   ends

         end     talk            ;  defines entry point


 \SAMPCODE\ADVMSDOS\CHAP08
 \SAMPCODE\ADVMSDOS\CHAP08\DUMP.C

 /*
     DUMP.C      Displays the binary contents of a file in
                 hex and ASCII on the standard output device.

     Compile:    C>CL DUMP.C

     Usage:      C>DUMP unit:path\filename.ext  [ >device ]

     Copyright (C) 1988 Ray Duncan
 */

 #include <stdio.h>
 #include <io.h>
 #include <fcntl.h>

 #define REC_SIZE 16             /* input file record size */

 main(int argc, char *argv[])
 {
     int fd;                     /* input file handle */
     int status = 0;             /* status from file read */
     long fileptr = 0L;          /* current file byte offset */
     char filebuf[REC_SIZE];     /* data from file */

     if(argc != 2)               /* abort if missing filename */
     {   fprintf(stderr,"\ndump: wrong number of parameters\n");
         exit(1);
     }

                                 /* open file in binary mode,
                                    abort if open fails */
     if((fd = open(argv[1],O_RDONLY | O_BINARY) ) == -1)
     {   fprintf(stderr, "\ndump: can't find file %s \n", argv[1]);
         exit(1);
     }

                                 /* read and dump records
                                    until end of file */
     while((status = read(fd,filebuf,REC_SIZE) ) != 0)
     {   dump_rec(filebuf, fileptr, status);
         fileptr += REC_SIZE;
     }

     close(fd);                  /* close input file */
     exit(0);                    /* return success code */
 }


 /*
     Display record (16 bytes) in hex and ASCII on standard output
 */

 dump_rec(char *filebuf, long fileptr, int length)
 {
     int i;                      /* index to current record */

     if(fileptr % 128 == 0)      /* display heading if needed */
         printf("\n\n       0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");

     printf("\n%04lX ",fileptr); /* display file offset */

                                 /* display hex equivalent of
                                    each byte from file */
     for(i = 0; i < length; i++)
         printf(" %02X", (unsigned char) filebuf[i]);

     if(length != 16)            /* spaces if partial record */
         for (i=0; i<(16-length); i++) printf("   ");

                                 /* display ASCII equivalent of
                                    each byte from file */
     printf("  ");
     for(i = 0; i < length; i++)
     {   if(filebuf[i] < 32 || filebuf[i] > 126) putchar('.');
         else putchar(filebuf[i]);
     }
 }

 \SAMPCODE\ADVMSDOS\CHAP08\DUMP.ASM

         title   DUMP --- display file contents
         page    55,132

 ;
 ;  DUMP --- Display contents of file in hex and ASCII
 ;
 ;  Build:   C>MASM DUMP;
 ;           C>LINK DUMP;
 ;
 ;  Usage:   C>DUMP unit:\path\filename.exe [ >device ]
 ;
 ;  Copyright 1988 Ray Duncan
 ;

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed
 tab     equ     09h             ; ASCII tab code
 blank   equ     20h             ; ASCII space code

 cmd     equ     80h             ; buffer for command tail

 blksize equ     16              ; input file record size

 stdin   equ     0               ; standard input handle
 stdout  equ     1               ; standard output handle
 stderr  equ     2               ; standard error handle


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:_DATA,es:_DATA,ss:STACK


 dump    proc    far             ; entry point from MS-DOS

         push    ds              ; save DS:0000 for final
         xor     ax,ax           ; return to MS-DOS, in case
         push    ax              ; Function 4CH can't be used.

         mov     ax,_DATA        ; make our data segment
         mov     ds,ax           ; addressable via DS register.

                                 ; check MS-DOS version
         mov     ax,3000h        ; Fxn 30H = get version
         int     21h             ; transfer to MS-DOS
         cmp     al,2            ; major version 2 or later?
         jae     dump1           ; yes, proceed

                                 ; if MS-DOS 1.x, display
                                 ; error message and exit
         mov     dx,offset msg3  ; DS:DX = message address
         mov     ah,9            ; Fxn 9 = print string
         int     21h             ; transfer to MS-DOS
         ret                     ; then exit the old way

 dump1:                          ; check if filename present
         mov     bx,offset cmd   ; ES:BX = command tail
         call    argc            ; count command arguments
         cmp     ax,2            ; are there 2 arguments?
         je      dump2           ; yes, proceed

                                 ; missing filename, display
                                 ; error message and exit
         mov     dx,offset msg2  ; DS:DX = message address
         mov     cx,msg2_len     ; CX = message length
         jmp     dump9           ; go display it

 dump2:                          ; get address of filename
         mov     ax,1            ; AX = argument number
                                 ; ES:BX still = command tail
         call    argv            ; returns ES:BX = address,
                                 ; and AX = length

         mov     di,offset fname ; copy filename to buffer
         mov     cx,ax           ; CX = length

 dump3:  mov     al,es:[bx]      ; copy one byte
         mov     [di],al
         inc     bx              ; bump string pointers
         inc     di
         loop    dump3           ; loop until string done
         mov     byte ptr [di],0 ; add terminal null byte

         mov     ax,ds           ; make our data segment
         mov     es,ax           ; addressable by ES too

                                 ; now open the file
         mov     ax,3d00h        ; Fxn 3DH = open file
                                 ; mode 0 = read only
         mov     dx,offset fname ; DS:DX = filename
         int     21h             ; transfer to MS-DOS
         jnc     dump4           ; jump, open successful

                                 ; open failed, display
                                 ; error message and exit
         mov     dx,offset msg1  ; DS:DX = message address
         mov     cx,msg1_len     ; CX = message length
         jmp     dump9           ; go display it

 dump4:  mov     fhandle,ax      ; save file handle

 dump5:                          ; read block of file data
         mov     bx,fhandle      ; BX = file handle
         mov     cx,blksize      ; CX = record length
         mov     dx,offset fbuff ; DS:DX = buffer
         mov     ah,3fh          ; Fxn 3FH = read
         int     21h             ; transfer to MS-DOS

         mov     flen,ax         ; save actual length
         cmp     ax,0            ; end of file reached?
         jne     dump6           ; no, proceed

         cmp     word ptr fptr,0 ; was this the first read?
         jne     dump8           ; no, exit normally

                                 ; display empty file
                                 ; message and exit
         mov     dx,offset msg4  ; DS:DX = message address
         mov     cx,msg4_len     ; CX = length
         jmp     dump9           ; go display it

 dump6:                          ; display heading at
                                 ; each 128 byte boundary
         test    fptr,07fh       ; time for a heading?
         jnz     dump7           ; no, proceed

                                 ; display a heading
         mov     dx,offset hdg   ; DS:DX = heading address
         mov     cx,hdg_len      ; CX = heading length
         mov     bx,stdout       ; BX = standard output
         mov     ah,40h          ; Fxn 40H = write
         int     21h             ; transfer to MS-DOS

 dump7:  call    conv            ; convert binary record
                                 ; to formatted ASCII

                                 ; display formatted output
         mov     dx,offset fout  ; DX:DX = output address
         mov     cx,fout_len     ; CX = output length
         mov     bx,stdout       ; BX = standard output
         mov     ah,40h          ; Fxn 40H = write
         int     21h             ; transfer to MS-DOS

         jmp     dump5           ; go get another record

 dump8:                          ; close input file
         mov     bx,fhandle      ; BX = file handle
         mov     ah,3eh          ; Fxn 3EH = close
         int     21h             ; transfer to MS-DOS

         mov     ax,4c00h        ; Fxn 4CH = terminate,
                                 ; return code = 0
         int     21h             ; transfer to MS-DOS

 dump9:                          ; display message on
                                 ; standard error device
                                 ; DS:DX = message address
                                 ; CX = message length
         mov     bx,stderr       ; standard error handle
         mov     ah,40h          ; Fxn 40H = write
         int     21h             ; transfer to MS-DOS

         mov     ax,4c01h        ; Fxn 4CH = terminate,
                                 ; return code = 1
         int     21h             ; transfer to MS-DOS

 dump    endp


 conv    proc    near            ; convert block of data
                                 ; from input file

         mov     di,offset fout  ; clear output format
         mov     cx,fout_len-2   ; area to blanks
         mov     al,blank
         rep stosb

         mov     di,offset fout  ; convert file offset
         mov     ax,fptr         ; to ASCII for output
         call    w2a

         mov     bx,0            ; init. buffer pointer

 conv1:  mov     al,[fbuff+bx]   ; fetch byte from buffer
         mov     di,offset foutb ; point to output area

                                 ; format ASCII part...
                                 ; store '.' as default
         mov     byte ptr [di+bx],'.'

         cmp     al,blank        ; in range 20H - 7EH?
         jb      conv2           ; jump, not alphanumeric.

         cmp     al,7eh          ; in range 20H - 7EH?
         ja      conv2           ; jump, not alphanumeric.

         mov     [di+bx],al      ; store ASCII character.

 conv2:                          ; format hex part...
         mov     di,offset fouta ; point to output area
         add     di,bx           ; base addr + (offset*3)
         add     di,bx
         add     di,bx
         call    b2a             ; convert byte to hex

         inc     bx              ; advance through record
         cmp     bx,flen         ; entire record converted?
         jne     conv1           ; no, get another byte

                                 ; update file pointer
         add     word ptr fptr,blksize

         ret

 conv    endp


 w2a     proc    near            ; convert word to hex ASCII
                                 ; call with AX = value
                                 ;           DI = addr for string
                                 ; returns AX, DI, CX destroyed

         push    ax              ; save copy of value
         mov     al,ah
         call    b2a             ; convert upper byte

         pop     ax              ; get back copy
         call    b2a             ; convert lower byte
         ret

 w2a     endp


 b2a     proc    near            ; convert byte to hex ASCII
                                 ; call with AL=binary value
                                 ;           DI=addr for string
                                 ; returns   AX, DI, CX modified

         sub     ah,ah           ; clear upper byte
         mov     cl,16
         div     cl              ; divide byte by 16
         call    ascii           ; quotient becomes the first
         stosb                   ; ASCII character
         mov     al,ah
         call    ascii           ; remainder becomes the
         stosb                   ; second ASCII character
         ret

 b2a     endp


 ascii   proc    near            ; convert value 0-0FH in AL
                                 ; into "hex ASCII" character

         add     al,'0'          ; offset to range 0-9
         cmp     al,'9'          ; is it > 9?
         jle     ascii2          ; no, jump
         add     al,'A'-'9'-1    ; offset to range A-F,

 ascii2: ret                     ; return AL = ASCII char.

 ascii   endp


 argc    proc    near            ; count command line arguments
                                 ; call with ES:BX = command line
                                 ; returns   AX = argument count

         push    bx              ; save original BX and CX
         push    cx              ;  for later
         mov     ax,1            ; force count >= 1

 argc1:  mov     cx,-1           ; set flag = outside argument

 argc2:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],cr
         je      argc3           ; exit if carriage return
         cmp     byte ptr es:[bx],blank
         je      argc1           ; outside argument if ASCII blank
         cmp     byte ptr es:[bx],tab
         je      argc1           ; outside argument if ASCII tab

                                 ; otherwise not blank or tab,
         jcxz    argc2           ; jump if already inside argument

         inc     ax              ; else found argument, count it
         not     cx              ; set flag = inside argument
         jmp     argc2           ; and look at next character

 argc3:  pop     cx              ; restore original BX and CX
         pop     bx
         ret                     ; return AX = argument count

 argc    endp


 argv    proc    near            ; get address & length of
                                 ; command line argument
                                 ; call with ES:BX = command line
                                 ;           AX    = argument no.
                                 ; returns   ES:BX = address
                                 ;           AX    = length

         push    cx              ; save original CX and DI
         push    di

         or      ax,ax           ; is it argument 0?
         jz      argv8           ; yes, jump to get program name

         xor     ah,ah           ; initialize argument counter

 argv1:  mov     cx,-1           ; set flag = outside argument

 argv2:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],cr
         je      argv7           ; exit if carriage return
         cmp     byte ptr es:[bx],blank
         je      argv1           ; outside argument if ASCII blank
         cmp     byte ptr es:[bx],tab
         je      argv1           ; outside argument if ASCII tab

                                 ; if not blank or tab...
         jcxz    argv2           ; jump if already inside argument

         inc     ah              ; else count arguments found
         cmp     ah,al           ; is this the one we're looking for?
         je      argv4           ; yes, go find its length
         not     cx              ; no, set flag = inside argument
         jmp     argv2           ; and look at next character

 argv4:                          ; found desired argument, now
                                 ; determine its length...
         mov     ax,bx           ; save param. starting address

 argv5:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],cr
         je      argv6           ; found end if carriage return
         cmp     byte ptr es:[bx],blank
         je      argv6           ; found end if ASCII blank
         cmp     byte ptr es:[bx],tab
         jne     argv5           ; found end if ASCII tab

 argv6:  xchg    bx,ax           ; set ES:BX = argument address
         sub     ax,bx           ; and AX = argument length
         jmp     argvx           ; return to caller

 argv7:  xor     ax,ax           ; set AX = 0, argument not found
         jmp     argvx           ; return to caller

 argv8:                          ; special handling for argv=0
         mov     ax,3000h        ; check if DOS 3.0 or later
         int     21h             ; (force AL=0 in case DOS 1)
         cmp     al,3
         jb      argv7           ; DOS 1 or 2, return null param.
         mov     es,es:[2ch]     ; get environment segment from PSP
         xor     di,di           ; find the program name by
         xor     al,al           ; first skipping over all the
         mov     cx,-1           ; environment variables...
         cld
 argv9:  repne scasb             ; scan for double null (can't use
         scasb                   ; (SCASW since might be odd addr.)
         jne     argv9           ; loop if it was a single null
         add     di,2            ; skip count word in environment
         mov     bx,di           ; save program name address
         mov     cx,-1           ; now find its length...
         repne scasb             ; scan for another null byte
         not     cx              ; convert CX to length
         dec     cx
         mov     ax,cx           ; return length in AX

 argvx:                          ; common exit point
         pop     di              ; restore original CX and DI
         pop     cx
         ret                     ; return to caller

 argv    endp

 _TEXT    ends


 _DATA   segment word public 'DATA'

 fname   db      64 dup (0)      ; buffer for input filespec

 fhandle dw      0               ; token from PCDOS for input file.

 flen    dw      0               ; actual length read

 fptr    dw      0               ; relative address in file

 fbuff   db      blksize dup (?) ; data from input file

 fout    db      'nnnn'          ; formatted output area
         db      blank,blank
 fouta   db      16 dup ('nn',blank)
         db      blank
 foutb   db      16 dup (blank),cr,lf
 fout_len equ    $-fout

 hdg     db      cr,lf           ; heading for each 128 bytes
         db      7 dup (blank)   ; of formatted output
         db      '0  1  2  3  4  5  6  7  '
         db      '8  9  A  B  C  D  E  F',cr,lf
 hdg_len equ     $-hdg


 msg1    db      cr,lf
         db      'dump: file not found'
         db      cr,lf
 msg1_len equ    $-msg1

 msg2    db      cr,lf
         db      'dump: missing file name'
         db      cr,lf
 msg2_len equ    $-msg2

 msg3    db      cr,lf
         db      'dump: wrong MS-DOS version'
         db      cr,lf,'$'

 msg4    db      cr,lf
         db      'dump: empty file'
         db      cr,lf
 msg4_len equ    $-msg4

 _DATA   ends


 STACK   segment para stack 'STACK'

         db      64 dup (?)

 STACK   ends

         end     dump


 \SAMPCODE\ADVMSDOS\CHAP08\INT24.ASM

         title   INT24 Critical Error Handler
         page    55,132

 ;
 ; INT24.ASM -- Replacement critical error handler
 ;
 ; Copyright (C) September 1987 Ray Duncan
 ;

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 save24  dd      0               ; previous contents of Int 24H
                                 ; critical error handler vector

                                 ; prompt message used by
                                 ; critical error handler
 prompt  db      cr,lf,'Critical Error Occurred: '
         db      'Abort, Retry, Ignore, Fail? $'

 keys    db      'aArRiIfF'      ; possible user response keys
 keys_len equ $-keys             ; (both cases of each allowed)

 codes   db      2,2,1,1,0,0,3,3 ; codes returned to MS-DOS kernel
                                 ; for corresponding response keys

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

         public  get24
 get24   proc    near            ; set Int 24H vector to point
                                 ; to new critical error handler

         push    ds              ; save segment registers
         push    es

         mov     ax,3524h        ; get address of previous
         int     21h             ; INT 24H handler and save it

         mov     word ptr save24,bx
         mov     word ptr save24+2,es

         push    cs              ; set DS:DX to point to
         pop     ds              ;   new INT 24H handler
         mov     dx,offset _TEXT:int24
         mov     ax,2524h        ; then call MS-DOS to
         int     21h             ;   set the INT 24H vector

         pop     es              ; restore segment registers
         pop     ds
         ret                     ; and return to caller

 get24   endp


         public  res24
 res24   proc    near            ; restore original contents
                                 ; of Int 24H vector

         push    ds              ; save our data segment

         lds     dx,save24       ; put address of old handler
         mov     ax,2524h        ; back into INT 24H vector
         int     21h

         pop     ds              ; restore data segment
         ret                     ; and return to caller

 res24   endp

 ;
 ; This is the replacement Critical Error handler.  It
 ; prompts the user for Abort, Retry, Ignore, or Fail, and
 ; returns the appropriate code to the MS-DOS kernel.
 ;
 int24   proc    far             ; entered from MS-DOS kernel

         push    bx              ; save registers
         push    cx
         push    dx
         push    si
         push    di
         push    bp
         push    ds
         push    es

 int24a: mov     ax,DGROUP       ; display prompt for user
         mov     ds,ax           ; using Function 9 (print string
         mov     es,ax           ; terminated by $ character)
         mov     dx,offset prompt
         mov     ah,9
         int     21h

         mov     ah,1            ; get user's response
         int     21h             ; Function 1 = read one character

         mov     di,offset keys  ; look up code for response key
         mov     cx,keys_len
         cld
         repne scasb
         jnz     int24a          ; prompt again if bad response

                                 ; set AL = action code for MS-DOS
                                 ; according to key that was entered:
                                 ; 0=ignore, 1=retry, 2=abort, 3=fail
         mov     al,[di+keys_len-1]

         pop     es              ; restore registers
         pop     ds
         pop     bp
         pop     di
         pop     si
         pop     dx
         pop     cx
         pop     bx
         iret                    ; exit critical error handler

 int24   endp

 _TEXT   ends

         end

 \SAMPCODE\ADVMSDOS\CHAP08\TRYINT24.ASM

          title     TRYINT24 -- tests critical error handler
          page      55,132
 ;
 ; TRYINT24.ASM - Test program for INT24.ASM
 ;                replacement critical-error handler
 ;
 ; Copyright (C) June 1987 Ray Duncan
 ;
 ; Build:   MASM TRYINT24;
 ;          MASM INT24;
 ;          LINK TRYINT24+INT24;
 ;
 ; Usage:   TRYINT24
 ;

 stdin   equ     0               ; standard input device
 stdout  equ     1               ; standard output device
 stderr  equ     2               ; standard error device

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed


 DGROUP  group   _DATA,STACK     ; 'automatic data group'

         extrn   get24:near
         extrn   res24:near

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP,ss:STACK

 main    proc    far             ; entry point from MS-DOS

         mov     ax,DGROUP       ; set DS = our data segment
         mov     ds,ax

         call    get24           ; capture int 24h for our
                                 ; own handler

                                 ; display sign-on message...
         mov     dx,offset DGROUP:msg    ; DS:DX = address of message
         mov     cx,msg_len      ; CX = length of message
         mov     bx,stdout       ; BX = handle for std output
         mov     ah,40h          ; AH = 40h, write file or device
         int     21h             ; transfer to MS-DOS

 err:    mov     dl,'*'          ; write character to printer
         mov     ah,05           ; to generate a critical error
         int     21h
         jmp     err

         call    res24           ; restore previous int 24h handler

         mov     ax,4c00h        ; no error, terminate program
         int     21h             ; with return code = 0

 main2:  call    res24           ; restore previous int 24h handler

         mov     ax,4c01h        ; error, terminate program
         int     21h             ; with return code = 1

 main    endp                    ; end of main procedure

 _TEXT   ends


 _DATA   segment word public 'DATA'

 msg     db      cr,lf,'Int 24H Handler Demo',cr,lf
 msg_len equ     $-msg

 _DATA   ends


 STACK   segment para stack 'STACK'

         dw      64 dup (?)

 STACK   ends

         end     main            ; defines program entry point


 \SAMPCODE\ADVMSDOS\CHAP11
 \SAMPCODE\ADVMSDOS\CHAP11\EXTMEM.ASM

          title     EXTMEM --- get/put extended memory
          page      55,132

 ;
 ; EXTMEM.ASM --- Demonstration of access to
 ;                extended memory using ROM BIOS
 ;                Int 15H Function 87H
 ;
 ; Copyright (C) June 1988 Ray Duncan
 ;
 ; Build:   MASM EXTMEM;
 ;          LINK EXTMEM;
 ;
 ; Usage:   EXTMEM
 ;

 stdin   equ     0               ; standard input device
 stdout  equ     1               ; standard output device
 stderr  equ     2               ; standard error device

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed


 DGROUP  group   _DATA,STACK     ; 'automatic data group'


 _DATA   segment word public 'DATA'

 bmdt    db      30h dup (0)     ; block move descriptor table

 buff1   db      80h dup ('?')   ; source buffer
 buff2   db      80h dup (0)     ; destination buffer

 _DATA   ends


 STACK   segment para stack 'STACK'

         dw      64 dup (?)

 STACK   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP,ss:STACK

 main    proc    far             ; entry point from MS-DOS

         mov     ax,DGROUP       ; set DS = our data segment
         mov     ds,ax
         mov     es,ax

                                 ; copy 'buff1' to extended
                                 ; memory address 100000H
         mov     dx,10h          ; DX:AX = destination
         mov     ax,0            ; extended mem. address
         mov     bx,seg buff1    ; DS:BX = source conv.
         mov     ds,bx           ; memory address
         mov     bx,offset buff1
         mov     cx,80h          ; CX = bytes to move
         mov     si,seg bmdt     ; ES:SI = block move
         mov     es,si           ; descriptor table
         mov     si,offset bmdt
         call    putblk          ; request transfer


                                 ; fill buff2 from extended
                                 ; memory address 100000H
         mov     dx,10h          ; DX:AX = source extended
         mov     ax,0            ; memory address
         mov     bx,seg buff2    ; DS:BX = destination
         mov     ds,bx           ; conventional mem. address
         mov     bx,offset buff2
         mov     cx,80h          ; CX = bytes to move
         mov     si,seg bmdt     ; ES:SI = block move
         mov     es,si           ; descriptor table
         mov     si,offset bmdt
         call    getblk          ; request transfer

         mov     ax,4c00h        ; no error, terminate program
         int     21h             ; with return code = 0

 main2:  mov     ax,4c01h        ; error, terminate program
         int     21h             ; with return code = 1

 main    endp                    ; end of main procedure




 getblk  proc    near            ; Transfer block from extended
                                 ;   memory to real memory
                                 ; Call with
                                 ; DX:AX = linear 32-bit
                                 ;         extended memory address
                                 ; DS:BX = segment and offset
                                 ;         destination address
                                 ; CX    = length in bytes
                                 ; ES:SI = block move descriptor table
                                 ; Returns
                                 ; AH    = 0 if transfer OK

         mov     es:[si+10h],cx  ; store length into descriptors
         mov     es:[si+18h],cx

                                 ; store access rights bytes
         mov     byte ptr es:[si+15h],93h
         mov     byte ptr es:[si+1dh],93h

         mov     es:[si+12h],ax  ; source extended memory address
         mov     es:[si+14h],dl

                                 ; convert destination segment
                                 ; and offset to linear address
         mov     ax,ds           ; segment * 16
         mov     dx,16
         mul     dx
         add     ax,bx           ; + offset -> linear address
         adc     dx,0

         mov     es:[si+1ah],ax  ; store destination address
         mov     es:[si+1ch],dl

         shr     cx,1            ; convert length to words
         mov     ah,87h          ; Int 15H Fxn 87h = block move
         int     15h             ; transfer to ROM BIOS

         ret                     ; back to caller

 getblk  endp


 putblk  proc    near            ; Transfer block from real
                                 ;   memory to extended memory
                                 ; Call with
                                 ; DX:AX = linear 32-bit
                                 ;         extended memory address
                                 ; DS:BX = segment and offset
                                 ;         source address
                                 ; CX    = length in bytes
                                 ; ES:SI = block move descriptor table
                                 ; Returns
                                 ; AH    = 0 if transfer OK

         mov     es:[si+10h],cx  ; store length into descriptors
         mov     es:[si+18h],cx

                                 ; store access rights bytes
         mov     byte ptr es:[si+15h],93h
         mov     byte ptr es:[si+1dh],93h

         mov     es:[si+1ah],ax  ; store destination extended
         mov     es:[si+1ch],dl  ; memory address

                                 ; convert source segment and
                                 ; offset to linear address
         mov     ax,ds           ; segment * 16
         mov     dx,16
         mul     dx
         add     ax,bx           ; + offset -> linear address
         adc     dx,0

         mov     es:[si+12h],ax  ; store source address
         mov     es:[si+14h],dl

         shr     cx,1            ; convert length to words
         mov     ah,87h          ; Int 15H Fxn 87h = block move
         int     15h             ; transfer to ROM BIOS

         ret                     ; back to caller

 putblk  endp

 _TEXT   ends

         end     main            ; defines program entry point


 \SAMPCODE\ADVMSDOS\CHAP12
 \SAMPCODE\ADVMSDOS\CHAP12\SHELL.C

 /*
         SHELL.C         Simple extendable command interpreter
                                 for MS-DOS version 2.0 and later

         Copyright 1988 Ray Duncan

         Compile:        C>CL SHELL.C

         Usage:          C>SHELL
 */

 #include <stdio.h>
 #include <process.h>
 #include <stdlib.h>
 #include <signal.h>

                                                                         /* ma
                                                                            el
 #define dim(x) (sizeof(x) / sizeof(x[0]))

 unsigned intrinsic(char *);                     /* function prototypes */
 void extrinsic(char *);
 void get_cmd(char *);
 void get_comspec(char *);
 void break_handler(void);
 void cls_cmd(void);
 void dos_cmd(void);
 void exit_cmd(void);

 struct cmd_table {                                      /* intrinsic commands
                            char *cmd_name;
                            int  (*cmd_fxn)();
                                  }   commands[] =

                  { "CLS",   cls_cmd,
                    "DOS",   dos_cmd,
                    "EXIT",  exit_cmd, };

 static char com_spec[64];                       /* COMMAND.COM filespec */


 main(int argc, char *argv[])
 {
         char inp_buf[80];               /* keyboard input buffer */

     get_comspec(com_spec);                      /* get COMMAND.COM filespec *

                                                                         /* re
                                                                    for Ctrl-C
         if(signal(SIGINT, break_handler) == (int(*)()) -1)
     {
                 fputs("Can't capture Control-C Interrupt", stderr);
             exit(1);
     }

     while(1)                            /* main interpreter loop */
     {
                 get_cmd(inp_buf);           /* get a command */
         if (! intrinsic(inp_buf) )      /* if it's intrinsic,
                                        run its subroutine */
            extrinsic(inp_buf);          /* else pass to COMMAND.COM */
         }
 }


 /*
         Try and match user's command with intrinsic command
         table.  If a match is found, run the associated routine
         and return true, else return false.
 */

 unsigned intrinsic(char *input_string)
 {
         int i, j;                                               /* some scrat

                                                                         /* sc
     while(*input_string == '\x20') input_string++ ;

                                                                         /* se
     for(i=0; i < dim(commands); i++)
     {
                 j = strcmp(commands[i].cmd_name, input_string);

         if(j == 0)                                      /* if match, run rout
         {
                         (*commands[i].cmd_fxn)();
             return(1);                          /* and return true */
         }
         }
     return(0);                                          /* no match, return f
 }


 /*
         Process an extrinsic command by passing it
     to an EXEC'd copy of COMMAND.COM.
 */

 void extrinsic(char *input_string)
 {
         int status;

     status = system(input_string);      /* call EXEC function */

     if(status)                                          /* if failed, display
                                                                            er
             fputs("\nEXEC of COMMAND.COM failed\n", stderr);
 }


 /*
         Issue prompt, get user's command from standard input,
     fold it to upper case.
 */

 void get_cmd(char *buffer)
 {
         printf("\nsh: ");                   /* display prompt */
     gets(buffer);                       /* get keyboard entry */
     strupr(buffer);                     /* fold to upper case */
 }


 /*
         Get the full path and file specification for COMMAND.COM
         from the "COMSPEC=" variable in the environment.
 */

 void get_comspec(char *buffer)
 {
         strcpy(buffer, getenv("COMSPEC"));

     if(buffer[0] == NULL)
     {
                 fputs("\nNo COMSPEC in environment\n", stderr);
             exit(1);
     }
 }


 /*
         This Ctrl-C handler keeps SHELL from losing control.
         It just re-issues the prompt and returns.
 */

 void break_handler(void)
 {
         signal(SIGINT, break_handler);          /* reset handler */
     printf("\nsh: ");                   /* display prompt */
 }


 /*
         These are the subroutines for the intrinsic commands.
 */

 void cls_cmd(void)                                      /* CLS command */
 {
         printf("\033[2J");                              /* ANSI escape sequen
 }

 void dos_cmd(void)                                      /* DOS command */
 {
         int status;

     status = spawnlp(P_WAIT, com_spec, com_spec, NULL);

     if (status)
                 fputs("\nEXEC of COMMAND.COM failed\n",stderr);
 }

 void exit_cmd(void)                                     /* EXIT command */
 {
         exit(0);                                                        /* te
 }

 \SAMPCODE\ADVMSDOS\CHAP12\SHELL.ASM

         name    shell
         page    55,132
         title   SHELL.ASM -- simple MS-DOS shell
 ;
 ; SHELL.ASM     Simple extendable command interpreter
 ;               for MS-DOS version 2.0 and later
 ;
 ; Copyright 1988 by Ray Duncan
 ;
 ; Build:        C>MASM SHELL;
 ;               C>LINK SHELL;
 ;
 ; Usage:        C>SHELL;
 ;

 stdin   equ     0                       ; standard input handle
 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed
 blank   equ     20h                     ; ASCII blank code
 escape  equ     01bh                    ; ASCII escape code

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:_DATA,ss:STACK

 shell   proc    far                     ; at entry DS = ES = PSP

         mov     ax,_DATA                ; make our data segment
         mov     ds,ax                   ; addressable

         mov     ax,es:[002ch]           ; get environment segment
         mov     env_seg,ax              ; from PSP and save it

                                         ; release unneeded memory...
                                         ; ES already = PSP segment
         mov     bx,100h                 ; BX = paragraphs needed
         mov     ah,4ah                  ; Function 4AH = resize block
         int     21h                     ; transfer to MS-DOS
         jnc     shell1                  ; jump if resize OK

         mov     dx,offset msg1          ; resize failed, display
         mov     cx,msg1_length          ; error message and exit
         jmp     shell4

 shell1: call    get_comspec             ; get COMMAND.COM filespec
         jnc     shell2                  ; jump if it was found

         mov     dx,offset msg3          ; COMSPEC not found in
         mov     cx,msg3_length          ; environment, display error
         jmp     shell4                  ; message and exit

 shell2: mov     dx,offset shell3        ; set Ctrl-C vector (Int 23H)
         mov     ax,cs                   ; for this program's handler
         mov     ds,ax                   ; DS:DX = handler address
         mov     ax,2523H                ; Fxn 25H = set vector
         int     21h                     ; transfer to MS-DOS

         mov     ax,_DATA                ; make our data segment
         mov     ds,ax                   ; addressable again
         mov     es,ax

 shell3:                                 ; main interpreter loop

         call    get_cmd                 ; get a command from user

         call    intrinsic               ; check if intrinsic function
         jnc     shell3                  ; yes, it was processed

         call    extrinsic               ; no, pass it to COMMAND.COM
         jmp     shell3                  ; then get another command

 shell4:                                 ; come here if error detected
                                         ; DS:DX = message address
                                         ; CX = message length
         mov     bx,stderr               ; BX = standard error handle
         mov     ah,40h                  ; Fxn 40H = write
         int     21h                     ; transfer to MS-DOS

         mov     ax,4c01h                ; Fxn 4CH = terminate with
                                         ; return code = 1
         int     21h                     ; transfer to MS-DOS

 shell   endp


 intrinsic proc  near                    ; Decode user entry against
                                         ; the table "COMMANDS"
                                         ; If match, run the routine,
                                         ; and return Carry = False
                                         ; If no match, Carry = True
                                         ; return Carry = True.

         mov     si,offset commands      ; DS:SI = command table

 intr1:  cmp     byte ptr [si],0         ; end of table?
         je      intr7                   ; jump, end of table found

         mov     di,offset inp_buf       ; no, let DI = addr of user input

 intr2:  cmp     byte ptr [di],blank     ; scan off any leading blanks
         jne     intr3

         inc     di                      ; found blank, go past it
         jmp     intr2

 intr3:  mov     al,[si]                 ; next character from table

         or      al,al                   ; end of string?
         jz      intr4                   ; jump, entire string matched

         cmp     al,[di]                 ; compare to input character
         jnz     intr6                   ; jump, found mismatch

         inc     si                      ; advance string pointers
         inc     di
         jmp     intr3

 intr4:  cmp     byte ptr [di],cr        ; make sure user's entry
         je      intr5                   ; is the same length...
         cmp     byte ptr [di],blank     ; next character in entry
         jne     intr6                   ; must be blank or Return

 intr5:  call    word ptr [si+1]         ; run the command routine

         clc                             ; return Carry Flag = False
         ret                             ; as success flag

 intr6:  lodsb                           ; look for end of this
         or      al,al                   ; command string (null byte)
         jnz     intr6                   ; not end yet, loop

         add     si,2                    ; skip over routine address
         jmp     intr1                   ; try to match next command

 intr7:  stc                             ; command not matched, exit
         ret                             ; with Carry = True

 intrinsic endp


 extrinsic proc  near                    ; process extrinsic command
                                         ; by passing it to
                                         ; COMMAND.COM with a
                                         ; " /C " command tail.

         mov     al,cr                   ; find length of command
         mov     cx,cmd_tail_length      ; by scanning for carriage
         mov     di,offset cmd_tail+1    ; return
         cld
         repnz scasb

         mov     ax,di                   ; calculate command tail
         sub     ax,offset cmd_tail+2    ; length without carriage
         mov     cmd_tail,al             ; return, and store it

                                         ; set command tail address
         mov     word ptr par_cmd,offset cmd_tail
         call    exec                    ; and run COMMAND.COM
         ret

 extrinsic endp


 get_cmd proc    near                    ; prompt user, get command

                                         ; display the shell prompt
         mov     dx,offset prompt        ; DS:DX = message address
         mov     cx,prompt_length        ; CX = message length
         mov     bx,stdout               ; BX = standard output handle
         mov     ah,40h                  ; Fxn 40H = write
         int     21h                     ; transfer to MS-DOS

                                         ; get entry from user
         mov     dx,offset inp_buf       ; DS:DX = input buffer
         mov     cx,inp_buf_length       ; CX = max length to read
         mov     bx,stdin                ; BX = standard input handle
         mov     ah,3fh                  ; Fxn 3FH = read
         int     21h                     ; transfer to MS-DOS

         mov     si,offset inp_buf       ; fold lower case characters
         mov     cx,inp_buf_length       ; in entry to upper case

 gcmd1:  cmp     byte ptr [si],'a'       ; check if 'a-z'
         jb      gcmd2                   ; jump, not in range
         cmp     byte ptr [si],'z'       ; check if 'a-z'
         ja      gcmd2                   ; jump, not in range
         sub     byte ptr [si],'a'-'A'   ; convert to upper case

 gcmd2:  inc     si                      ; advance through entry
         loop    gcmd1
         ret                             ; back to caller

 get_cmd endp


 get_comspec proc near                   ; get location of COMMAND.COM
                                         ; from environment "COMSPEC="
                                         ; Returns Carry = False
                                         ; if COMSPEC found.
                                         ; Returns Carry=True
                                         ; if no COMSPEC.

         mov     si,offset com_var       ; DS:SI = string to match...
         call    get_env                 ; search Environment Block
         jc      gcsp2                   ; jump if COMSPEC not found

                                         ; ES:DI points past "="
         mov     si,offset com_spec      ; DS:SI = local buffer

 gcsp1:  mov     al,es:[di]              ; copy COMSPEC variable
         mov     [si],al                 ; to local buffer
         inc     si
         inc     di
         or      al,al                   ; null char? (turns off Carry)
         jnz     gcsp1                   ; no, get next character

 gcsp2:  ret                             ; back to caller

 get_comspec endp


 get_env proc    near                    ; search environment
                                         ; Call DS:SI = "NAME="
                                         ; uses contents of "ENV_SEG"
                                         ; Returns Carry=False and ES:DI
                                         ; pointing to parameter if found,
                                         ; Returns Carry=True if no match

         mov     es,env_seg              ; get environment segment
         xor     di,di                   ; initialize env. offset

 genv1:  mov     bx,si                   ; initialize pointer to name
         cmp     byte ptr es:[di],0      ; end of environment?
         jne     genv2                   ; jump, end not found

         stc                             ; no match, return Carry set
         ret

 genv2:  mov     al,[bx]                 ; get character from name
         or      al,al                   ; end of name? (turns off Carry)
         jz      genv3                   ; yes, name matched

         cmp     al,es:[di]              ; compare to environment
         jne     genv4                   ; jump if match failed

         inc     bx                      ; advance environment
         inc     di                      ; and name pointers
         jmp     genv2

 genv3:                                  ; match found, Carry = clear,
         ret                             ; ES:DI = variable

 genv4:  xor     al,al                   ; scan forward in environment
         mov     cx,-1                   ; for zero byte
         cld
         repnz   scasb
         jmp     genv1                   ; go compare next string

 get_env endp


 exec    proc    near                    ; call MS-DOS EXEC function
                                         ; to run COMMAND.COM.

         mov     stkseg,ss               ; save stack pointer
         mov     stkptr,sp

                                         ; now run COMMAND.COM
         mov     dx,offset com_spec      ; DS:DX = filename
         mov     bx,offset par_blk       ; ES:BX = parameter block
         mov     ax,4b00h                ; Fxn 4BH = EXEC
                                         ; Subf. 0 = load and execute
         int     21h                     ; transfer to MS-DOS

         mov     ax,_DATA                ; make data segment
         mov     ds,ax                   ; addressable again
         mov     es,ax

         cli                             ; (for bug in some 8088s)
         mov     ss,stkseg               ; restore stack pointer
         mov     sp,stkptr
         sti                             ; (for bug in some 8088s)

         jnc     exec1                   ; jump if no errors

                                         ; display error message
         mov     dx,offset msg2          ; DS:DX = message address
         mov     cx,msg2_length          ; CX = message length
         mov     bx,stderr               ; BX = standard error handle
         mov     ah,40h                  ; Fxn 40H = write
         int     21h                     ; transfer to MS-DOS

 exec1:  ret                             ; back to caller

 exec    endp


 cls_cmd proc    near                    ; intrinsic CLS command

         mov     dx,offset cls_str       ; send the ANSI escape
         mov     cx,cls_str_length       ; sequence to clear
         mov     bx,stdout               ; the screen
         mov     ah,40h
         int     21h
         ret

 cls_cmd endp


 dos_cmd proc    near                    ; intrinsic DOS command

                                         ; set null command tail
         mov     word ptr par_cmd,offset nultail
         call    exec                    ; and run COMMAND.COM
         ret

 dos_cmd endp


 exit_cmd proc   near                    ; intrinsic EXIT Command

         mov     ax,4c00h                ; call MS-DOS terminate
         int     21h                     ; function with
                                         ; return code of zero
 exit_cmd endp

 _TEXT   ends


 STACK   segment para stack 'STACK'      ; declare stack segment

         dw      64 dup (?)

 STACK   ends


 _DATA   segment word public 'DATA'

 commands equ $                          ; "intrinsic" commands table
                                         ; each entry is ASCIIZ string
                                         ; followed by the offset
                                         ; of the procedure to be
                                         ; executed for that command.
         db      'CLS',0
         dw      cls_cmd

         db      'DOS',0
         dw      dos_cmd

         db      'EXIT',0
         dw      exit_cmd

         db      0                       ; end of table

 com_var db      'COMSPEC=',0            ; environment variable

                                         ; COMMAND.COM filespec
 com_spec db     80 dup (0)              ; from environment COMSPEC=

 nultail db      0,cr                    ; null command tail for
                                         ; invoking COMMAND.COM
                                         ; as another shell

 cmd_tail db     0,' /C '                ; command tail for invoking
                                         ; COMMAND.COM  as a transient

 inp_buf db      80 dup (0)              ; command line from Standard Input

 inp_buf_length equ $-inp_buf
 cmd_tail_length equ $-cmd_tail-1

 prompt  db      cr,lf,'sh: '            ; SHELL's user prompt
 prompt_length equ $-prompt

 env_seg dw      0                       ; segment of Environment Block

 msg1    db      cr,lf
         db      'Unable to release memory.'
         db      cr,lf
 msg1_length equ $-msg1

 msg2    db      cr,lf
         db      'EXEC of COMMAND.COM failed.'
         db      cr,lf
 msg2_length equ $-msg2

 msg3    db      cr,lf
         db      'No COMSPEC variable in environment.'
         db      cr,lf
 msg3_length equ $-msg3

 cls_str db      escape,'[2J'            ; ANSI escape sequence
 cls_str_length equ $-cls_str            ; to clear the screen

                                         ; EXEC parameter block
 par_blk dw      0                       ; environment segment
 par_cmd dd      cmd_tail                ; command line
         dd      fcb1                    ; file control block #1
         dd      fcb2                    ; file control block #2

 fcb1    db      0                       ; file control block #1
         db      11 dup (' ')
         db      25 dup (0)

 fcb2    db      0                       ; file control block #2
         db      11 dup (' ')
         db      25 dup (0)

 stkseg  dw      0                       ; original SS contents
 stkptr  dw      0                       ; original SP contents

 _DATA   ends

         end     shell

 \SAMPCODE\ADVMSDOS\CHAP13
 \SAMPCODE\ADVMSDOS\CHAP13\TEST0DIV.ASM

          title     TEST0DIV - test for ZERODIV.COM
          page      55,132

 ;
 ; TEST0DIV.ASM:  Test program for the ZERODIV.ASM
 ;                divide-by-zero interrupt handler
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;
 ; Build:   MASM TEST0DIV;
 ;          LINK TEST0DIV;
 ;
 ; Usage:   ZERODIV      (load divide by zero handler)
 ;          TEST0DIV     (exercise the handler)
 ;

 cr      equ     0dh                     ;ASCII carriage return
 lf      equ     0ah                     ;ASCII line feed

 _TEXT   segment word public 'CODE'

         org     100H

         assume  cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT

 main    proc    near

         mov     dx,offset signon        ; display sign-on message
         mov     ah,9
         int     21h

         mov     dx,0                    ; try register divide
         mov     ax,1
         mov     bx,0
         div     bx

         mov     dx,offset msg1          ; display success message
         mov     ah,9
         int     21h

         mov     dx,0                    ; try direct divide
         mov     ax,1
         div     word ptr zerow

         mov     dx,offset msg2          ; display success message
         mov     ah,9
         int     21h

         mov     dx,0                    ; try mod = 1 divide
         mov     ax,1
         mov     bx,offset zerow
         dec     bx
         div     word ptr [bx+1]

         mov     dx,offset msg3          ; display success message
         mov     ah,9
         int     21h

         mov     dx,0                    ; try mod = 2 divide
         mov     ax,1
         mov     bx,0
         div     word ptr [bx+offset zerow2]

         mov     dx,offset msg4          ; display success message
         mov     ah,9
         int     21h

         mov     ax,4c00h                ; exit to MS-DOS
         int     21h

 main    endp

 signon  db      cr,lf,'Zero Divide Test program',cr,lf,'$'

 msg1    db      cr,lf,'Back from register divide',cr,lf,'$'

 msg2    db      cr,lf,'Back from direct mem divide',cr,lf,'$'

 msg3    db      cr,lf,'Back from mod = 1 divide',cr,lf,'$'

 msg4    db      cr,lf,'Back from mod = 2 divide',cr,lf,'$'

 zerob   db      0                       ; byte-sized operand
 zerow   dw      0                       ; word-sized operand

         db      256 dup (?)             ; some empty space

 zerow2  dw      0

 _TEXT   ends

         end     main


 \SAMPCODE\ADVMSDOS\CHAP13\ZERODIV.ASM

          title     ZERODIV --- Divide by zero handler
          page      55,132

 ;
 ; ZERODIV.ASM --- Terminate-and-stay-resident handler
 ;                 for divide-by-zero interrupts
 ;
 ; Copyright 1988 Ray Duncan
 ;
 ; Build:        C>MASM ZERODIV;
 ;               C>LINK ZERODIV;
 ;               C>EXE2BIN ZERODIV.EXE ZERODIV.COM
 ;               C>DEL ZERODIV.EXE
 ;
 ; Usage:        C>ZERODIV
 ;

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed
 beep    equ     07h             ; ASCII bell code
 backsp  equ     08h             ; ASCII backspace code

 _TEXT   segment word public 'CODE'

         org     100H

         assume  cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT


 init    proc    near            ; entry point at load time

                                 ; capture vector for
                                 ; interrupt zero ...
         mov     dx,offset zdiv  ; DS:DX = handler address
         mov     ax,2500h        ; Fxn 25H = set vector
                                 ; interrupt type = 0
         int     21h             ; transfer to MS-DOS

                                 ; print sign-on message
         mov     dx,offset msg1  ; DS:DX = message address
         mov     ah,9            ; Fxn 09H = display string
         int     21h             ; transfer to MS-DOS

                                 ; DX = paragraphs to reserve
         mov     dx,((offset pgm_len+15)/16)+10h
         mov     ax,3100h        ; Fxn 31H = terminate and
                                 ; stay resident
         int     21h             ; transfer to MS-DOS

 init    endp


 zdiv    proc    far             ; this is the divide by
                                 ; zero interrupt handler

         sti                     ; enable interrupts

         push    ax              ; save registers
         push    bx
         push    cx
         push    dx
         push    si
         push    di
         push    bp
         push    ds
         push    es

         mov     ax,cs           ; make data addressable
         mov     ds,ax

                                 ; display message
                                 ; "Continue or Quit?"
         mov     dx,offset msg2  ; DS:DX = message address
         mov     ah,9            ; Fxn 09H = display string
         int     21h             ; transfer to MS-DOS

 zdiv1:  mov     ah,1            ; Fxn 01H = read keyboard
         int     21h             ; transfer to MS-DOS

         or      al,20h          ; fold char. to lower case

         cmp     al,'c'          ; is it C or Q?
         je      zdiv3           ; jump, it's a C

         cmp     al,'q'
         je      zdiv2           ; jump, it's a Q

                                 ; illegal entry, send beep
                                 ; and erase the character
         mov     dx,offset msg3  ; DS:DX = message address
         mov     ah,9            ; Fxn 09H = display string
         int     21h             ; transfer to MS-DOS

         jmp     zdiv1           ; try again

 zdiv2:                          ; user picked "Quit"
         mov     ax,4cffh        ; terminate current program
         int     21h             ; with return code = 255

 zdiv3:                          ; user picked "Continue"
                                 ; send CR-LF pair
         mov     dx,offset msg4  ; DS:DX = message address
         mov     ah,9            ; Fxn 09H = print string
         int     21h             ; transfer to MS-DOS

                                 ; what CPU type is this?
         xor     ax,ax           ; to find out, we'll put
         push    ax              ; zero in the CPU flags
         popf                    ; and see what happens
         pushf
         pop     ax
         and     ax,0f000h       ; 8086/88 forces
         cmp     ax,0f000h       ; bits 12-15 true
         je      zdiv5           ; jump if 8086/88

                                 ; otherwise we must adjust
                                 ; return address to bypass
                                 ; the divide instruction...
         mov     bp,sp           ; make stack addressable

         lds     bx,[bp+18]      ; get address of the
                                 ; faulting instruction

         mov     bl,[bx+1]       ; get addressing byte
         and     bx,0c7h         ; isolate mod & r/m fields

         cmp     bl,6            ; mod 0, r/m 6 = direct
         jne     zdiv4           ; not direct, jump

         add     word ptr [bp+18],4
         jmp     zdiv5

 zdiv4:  mov     cl,6            ; otherwise isolate mod
         shr     bx,cl           ; field and get instruction
         mov     bl,cs:[bx+itab] ; size from table
         add     [bp+18],bx

 zdiv5:  pop     es              ; restore registers
         pop     ds
         pop     bp
         pop     di
         pop     si
         pop     dx
         pop     cx
         pop     bx
         pop     ax
         iret                    ; return from interrupt

 zdiv    endp


 msg1    db      cr,lf           ; load-time signon message
         db      'Divide by Zero Interrupt '
         db      'Handler installed.'
         db      cr,lf,'$'

 msg2    db      cr,lf,lf        ; interrupt-time message
         db      'Divide by Zero detected: '
         db      cr,lf,'Continue or Quit (C/Q) ? '
         db      '$'

 msg3    db      beep            ; used if bad entry
         db      backsp,' ',backsp,'$'

 msg4    db      cr,lf,'$'       ; carriage return-line feed

                                 ; instruction size table
 itab    db      2               ; mod = 0
         db      3               ; mod = 1
         db      4               ; mod = 2
         db      2               ; mod = 3

 pgm_len equ     $-init          ; program length

 _TEXT   ends

         end     init


 \SAMPCODE\ADVMSDOS\CHAP14
 \SAMPCODE\ADVMSDOS\CHAP14\DRIVER.ASM

         title   DRIVER.ASM Device Driver Skeleton
         page    55,132

 ;
 ; DRIVER.ASM   MS-DOS device driver skeleton
 ;
 ; The driver command code routines are stubs only and have
 ; no effect but to return a non-error "Done" status.
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;
 ; Build:   MASM DRIVER;
 ;          LINK DRIVER;
 ;          EXE2BIN DRIVER.EXE DRIVER.SYS
 ;          DEL DRIVER.EXE
 ;
 ; Usage:   DEVICE=DRIVER.SYS  (in file CONFIG.SYS)
 ;

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:_TEXT,es:NOTHING

         org     0

 MaxCmd  equ     24              ; maximum allowed command code
                                 ; 12 for MS-DOS 2.x
                                 ; 16 for MS-DOS 3.0-3.1
                                 ; 24 for MS-DOS 3.2-3.3

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed
 eom     equ     '$'             ; end of message signal


 Header:                         ; device driver header
         dd      -1              ; link to next device driver
         dw      0c840h          ; device attribute word
         dw      Strat           ; "Strategy" routine entry point
         dw      Intr            ; "Interrupt" routine entry point
         db      'SKELETON'      ; logical device name


 RHPtr   dd      ?               ; pointer to request header, passed
                                 ; by MS-DOS kernel to Strategy routine


 Dispatch:                       ; Interrupt routine command code
                                 ; dispatch table.
         dw      Init            ; 0  = initialize driver
         dw      MediaChk        ; 1  = media check
         dw      BuildBPB        ; 2  = build BIOS parameter block
         dw      IoctlRd         ; 3  = I/O control read
         dw      Read            ; 4  = read
         dw      NdRead          ; 5  = non-destructive read
         dw      InpStat         ; 6  = input status
         dw      InpFlush        ; 7  = flush input buffers
         dw      Write           ; 8  = write
         dw      WriteVfy        ; 9  = write with verify
         dw      OutStat         ; 10 = output status
         dw      OutFlush        ; 11 = flush output buffers
         dw      IoctlWt         ; 12 = I/O control write
         dw      DevOpen         ; 13 = device open       (MS-DOS 3.0+)
         dw      DevClose        ; 14 = device close      (MS-DOS 3.0+)
         dw      RemMedia        ; 15 = removeable media  (MS-DOS 3.0+)
         dw      OutBusy         ; 16 = output until busy (MS-DOS 3.0+)
         dw      Error           ; 17 = not used
         dw      Error           ; 18 = not used
         dw      GenIOCTL        ; 19 = generic IOCTL     (MS-DOS 3.2+)
         dw      Error           ; 20 = not used
         dw      Error           ; 21 = not used
         dw      Error           ; 22 = not used
         dw      GetLogDev       ; 23 = get logical device (MS-DOS 3.2+)
         dw      SetLogDev       ; 24 = set logical device (MS-DOS 3.2+)


 Strat   proc    far             ; device driver Strategy routine,
                                 ; called by MS-DOS kernel with
                                 ; ES:BX = address of request header

                                 ; save pointer to request header
         mov     word ptr cs:[RHPtr],bx
         mov     word ptr cs:[RHPtr+2],es

         ret                     ; back to MS-DOS kernel

 Strat   endp


 Intr    proc  far               ; device driver Interrupt routine,
                                 ; called by MS-DOS kernel immediately
                                 ; after call to Strategy routine

         push    ax              ; save general registers
         push    bx
         push    cx
         push    dx
         push    ds
         push    es
         push    di
         push    si
         push    bp

         push    cs              ; make local data addressable
         pop     ds              ; by setting DS = CS

         les     di,[RHPtr]      ; let ES:DI = request header

                                 ; get BX = command code
         mov     bl,es:[di+2]
         xor     bh,bh
         cmp     bx,MaxCmd       ; make sure it's legal
         jle     Intr1           ; jump, function code is ok
         call    Error           ; set error bit, "Unknown Command" code
         jmp     Intr2

 Intr1:  shl     bx,1            ; form index to dispatch table
                                 ; and branch to command code routine
         call    word ptr [bx+Dispatch]

         les     di,[RHPtr]      ; ES:DI = addr. of request header

 Intr2:  or      ax,0100h        ; merge 'Done' bit into status, and
         mov     es:[di+3],ax    ; store status into request header

         pop     bp              ; restore general registers
         pop     si
         pop     di
         pop     es
         pop     ds
         pop     dx
         pop     cx
         pop     bx
         pop     ax
         ret                     ; back to MS-DOS kernel


 ; Command code routines are called by the Interrupt routine
 ; via the Dispatch table with ES:DI pointing to the request
 ; header.  Each routine should return AX=0 if function was
 ; completed successfully, or AX = 8000h + error code if
 ; function failed.


 MediaChk proc   near            ; function 1 = Media Check

         xor     ax,ax
         ret

 MediaChk endp


 BuildBPB proc   near            ; function 2 = Build BPB

         xor     ax,ax
         ret

 BuildBPB endp


 IoctlRd proc    near            ; function 3 = I/O Control Read

         xor     ax,ax
         ret

 IoctlRd endp


 Read    proc    near            ; function 4 = Read (Input)

         xor     ax,ax
         ret

 Read    endp


 NdRead  proc    near            ; function 5 = Non-Destructive Read

         xor     ax,ax
         ret

 NdRead  endp


 InpStat proc    near            ; function 6 = Input Status

         xor     ax,ax
         ret

 InpStat endp


 InpFlush proc   near            ; function 7 = Flush Input Buffers

         xor     ax,ax
         ret

 InpFlush endp


 Write   proc    near            ; function 8 = Write (Output)

         xor     ax,ax
         ret

 Write   endp


 WriteVfy proc   near            ; function 9 = Write with Verify

         xor     ax,ax
         ret

 WriteVfy endp


 OutStat proc    near            ; function 10 = Output Status

         xor     ax,ax
         ret

 OutStat endp


 OutFlush proc   near            ; function 11 = Flush Output Buffers

         xor     ax,ax
         ret

 OutFlush endp


 IoctlWt proc    near            ; function 12 = I/O Control Write

         xor     ax,ax
         ret

 IoctlWt endp


 DevOpen proc    near            ; function 13 = Device Open

         xor     ax,ax
         ret

 DevOpen endp


 DevClose proc   near            ; function 14 = Device Close

         xor     ax,ax
         ret

 DevClose endp


 RemMedia proc   near            ; function 15 = Removable Media

         xor     ax,ax
         ret

 RemMedia endp


 OutBusy proc    near            ; function 16 = Output Until Busy

         xor     ax,ax
         ret

 OutBusy endp


 GenIOCTL proc   near            ; function 19 = Generic IOCTL

         xor     ax,ax
         ret

 GenIOCTL endp


 GetLogDev proc  near            ; function 23 = Get Logical Device

         xor     ax,ax
         ret

 GetLogDev endp


 SetLogDev proc  near            ; function 24 = Set Logical Device

         xor     ax,ax
         ret

 SetLogDev endp


 Error   proc    near            ; bad command code in request header

         mov     ax,8003h        ; error bit + "Unknown Command" code
         ret

 Error   endp


 Init    proc    near            ; function 0 = initialize driver

         push    es              ; save address of request header
         push    di

         mov     ax,cs           ; convert load address to ASCII
         mov     bx,offset Ident1
         call    hexasc

         mov     ah,9            ; display driver sign-on message
         mov     dx,offset Ident
         int     21h

         pop     di              ; restore request header address
         pop     es

                                 ; set addr. of free memory
                                 ; above driver (break address)
         mov     word ptr es:[di+14],offset Init
         mov     word ptr es:[di+16],cs

         xor     ax,ax           ; return status
         ret

 Init    endp


 hexasc  proc    near            ; converts word to hex ASCII
                                 ; call with AX = value,
                                 ; DS:BX = address for string
                                 ; returns AX, BX destroyed

         push    cx              ; save registers
         push    dx

         mov     dx,4            ; initialize character counter

 hexasc1:
         mov     cx,4            ; isolate next four bits
         rol     ax,cl
         mov     cx,ax
         and     cx,0fh
         add     cx,'0'          ; convert to ASCII
         cmp     cx,'9'          ; is it 0-9?
         jbe     hexasc2         ; yes, jump
         add     cx,'A'-'9'-1    ; add fudge factor for A-F

 hexasc2:                        ; store this character
         mov     [bx],cl
         inc     bx              ; bump string pointer

         dec     dx              ; count characters converted
         jnz     hexasc1         ; loop, not four yet

         pop     dx              ; restore registers
         pop     cx
         ret                     ; back to caller

 hexasc  endp


 Ident   db      cr,lf,lf
         db      'Advanced MS-DOS Example Device Driver'
         db      cr,lf
         db      'Device driver header at: '
 Ident1  db      'XXXX:0000'
         db      cr,lf,lf,eom

 Intr    endp

 _TEXT   ends

         end


 \SAMPCODE\ADVMSDOS\CHAP15
 \SAMPCODE\ADVMSDOS\CHAP15\CLEAN.C

 /*
     CLEAN.C     Filter to turn document files into
                 normal text files.

     Copyright 1988 Ray Duncan

     Compile:    C>CL CLEAN.C

     Usage:      C>CLEAN  <infile >outfile

     All text characters are passed through with high bit stripped
     off.  Form feeds, carriage returns, and line feeds are passed
     through.  Tabs are expanded to spaces.  All other control codes
     are discarded.
 */

 #include <stdio.h>

 #define TAB_WIDTH   8               /* width of a tab stop */

 #define TAB     '\x09'              /* ASCII tab character */
 #define LF      '\x0A'              /* ASCII line feed */
 #define FF      '\x0C'              /* ASCII form feed */
 #define CR      '\x0D'              /* ASCII carriage return */
 #define BLANK   '\x20'              /* ASCII space code */
 #define EOFMK   '\x1A'              /* Ctrl-Z end of file */


 main(int argc, char *argv[])
 {
     char c;                         /* char. from stdin */
     int col = 0;                    /* column counter */

     while((c = getchar()) != EOF)   /* read input character */
     {
         c &= 0x07F;                 /* strip high bit */

         switch(c)                   /* decode character */
         {
             case LF:                /* if line feed or */
             case CR:                /* carriage return, */
                 col=0;              /* reset column count */

             case FF:                /* if form feed, carriage */
                 wchar(c);           /* return, or line feed */
                 break;              /* pass character through */

             case TAB:               /* if tab expand to spaces */
                 do wchar(BLANK);
                 while((++col % TAB_WIDTH) != 0);
                 break;

             default:                /* discard other control */
                 if(c >= BLANK)      /* characters, pass text */
                 {                   /* characters through */
                     wchar(c);
                     col++;          /* bump column counter */
                 }
                 break;
         }
     }
     wchar(EOFMK);                   /* write end-of-file mark */
     exit(0);
 }


 /*
     Write a character to the standard output.  If
     write fails, display error message and terminate.
 */

 wchar(char c)
 {
     if((putchar(c) == EOF) && (c != EOFMK))
     {
         fputs("clean: disk full",stderr);
         exit(1);
     }
 }

 \SAMPCODE\ADVMSDOS\CHAP15\PROTO.C

 /*
     PROTO.C: prototype character-oriented filter

     Copyright 1988 Ray Duncan

     Build:  CL PROTO.C

 */

 #include <stdio.h>

 main(int argc, char *argv[])
 {
     char ch;

     while((ch=getchar()) != EOF)    /* read a character */
     {
         ch = translate(ch);         /* translate it if necessary */

         putchar(ch);                /* write the character */
     }
     exit(0);                        /* terminate at end of file */
 }


 /*
     Perform any necessary translation on character
     from input file.  This example just returns
     the same character.
 */

 int translate(char ch)
 {
     return (ch);
 }

 \SAMPCODE\ADVMSDOS\CHAP15\CLEAN.ASM

         title   CLEAN - Text file filter
         page    55,132
 ;
 ; CLEAN.ASM     Filter to turn document files into
 ;               normal text files.
 ;
 ; Copyright 1988 Ray Duncan
 ;
 ; Build:        C>MASM CLEAN;
 ;               C>LINK CLEAN;
 ;
 ; Usage:        C>CLEAN  <infile  >outfile
 ;
 ; All text characters are passed through with high
 ; bit stripped off.  Form feeds, carriage returns,
 ; and line feeds are passed through.  Tabs are expanded
 ; to spaces.  All other control codes are discarded.
 ;

 tab     equ     09h             ; ASCII tab code
 lf      equ     0ah             ; ASCII line feed
 ff      equ     0ch             ; ASCII form feed
 cr      equ     0dh             ; ASCII carriage return
 blank   equ     020h            ; ASCII space code
 eof     equ     01ah            ; Ctrl-Z End-of-file

 tabsiz  equ     8               ; width of tab stop

 bufsiz  equ     128             ; size of input and
                                 ; output buffers

 stdin   equ     0000            ; standard input handle
 stdout  equ     0001            ; standard output handle
 stderr  equ     0002            ; standard error handle


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:_DATA,es:_DATA,ss:STACK

 clean   proc    far             ; entry point from MS-DOS.

         push    ds              ; save DS:0000 for final
         xor     ax,ax           ; return to MS-DOS, in case
         push    ax              ; Function 4CH can't be used.

         mov     ax,_DATA        ; make data segment addressable
         mov     ds,ax
         mov     es,ax

         mov     ah,30h          ; check version of MS-DOS
         int     21h
         cmp     al,2            ; MS-DOS 2.0 or later?
         jae     clean1          ; jump if version OK

                                 ; MS-DOS 1.x, display error
                                 ; message and exit...
         mov     dx,offset msg1  ; DS:DX = message address
         mov     ah,9            ; Fxn 9 = display string
         int     21h             ; transfer to MS-DOS
         ret                     ; then exit the old way

 clean1: call    init            ; initialize input buffer

 clean2: call    getc            ; get character from input
         jc      clean9          ; exit if end of stream

         and     al,07fh         ; strip off high bit

         cmp     al,blank        ; is it a control char?
         jae     clean4          ; no, write it

         cmp     al,eof          ; is it end of file?
         je      clean8          ; yes, write EOF and exit

         cmp     al,tab          ; is it a tab?
         je      clean6          ; yes, expand it to spaces

         cmp     al,cr           ; is it a carriage return?
         je      clean3          ; yes, go process it.

         cmp     al,lf           ; is it a line feed?
         je      clean3          ; yes, go process it.

         cmp     al,ff           ; is it a form feed?
         jne     clean2          ; no, discard it

 clean3: mov     column,0        ; if CR, LF, or FF
         jmp     clean5          ; reset column to zero

 clean4: inc     column          ; if non-control char.
                                 ; increment column counter

 clean5: call    putc            ; write char. to stdout
         jnc     clean2          ; if disk not full,
                                 ; get another character

                                 ; write failed...
         mov     dx,offset msg2  ; DS:DX = error message
         mov     cx,msg2_len     ; CX = message length
         mov     bx,stderr       ; BX = standard error handle
         mov     ah,40h          ; Fxn 40H = write
         int     21h             ; transfer to MS-DOS

         mov     ax,4c01h        ; Fxn 4CH = terminate
                                 ; return code = 1
         int     21h             ; transfer to MS-DOS

 clean6: mov     ax,column       ; tab code detected
         cwd                     ; tabsiz - (column MOD tabsiz)
         mov     cx,tabsiz       ; is number of spaces needed
         idiv    cx              ; to move to next tab stop
         sub     cx,dx

         add     column,cx       ; also update column counter

 clean7: push    cx              ; save spaces counter

         mov     al,blank        ; write an ASCII space
         call    putc

         pop     cx              ; restore spaces counter
         loop    clean7          ; loop until tab stop

         jmp     clean2          ; get another character

 clean8: call    putc            ; write EOF mark

 clean9: call    flush           ; write last output buffer
         mov     ax,4c00h        ; Fxn 4CH = terminate
                                 ; return code =0
         int     21h             ; transfer to MS-DOS

 clean   endp


 getc    proc    near            ; get char. from stdin
                                 ; returns Carry = 1 if
                                 ; end of input, else
                                 ; AL = char, Carry = 0

         mov     bx,iptr         ; get input buffer pointer
         cmp     bx,ilen         ; end of buffer reached?
         jne     getc1           ; not yet, jump

                                 ; more data is needed...
         mov     bx,stdin        ; BX = standard input handle
         mov     cx,bufsiz       ; CX = length to read
         mov     dx,offset ibuff ; DS:DX = buffer address
         mov     ah,3fh          ; Fxn 3FH = read
         int     21h             ; transfer to MS-DOS
         jc      getc2           ; jump if read failed

         or      ax,ax           ; was anything read?
         jz      getc2           ; jump if end of input

         mov     ilen,ax         ; save length of data
         xor     bx,bx           ; reset buffer pointer

 getc1:  mov     al,[ibuff+bx]   ; get char. from buffer
         inc     bx              ; bump buffer pointer

         mov     iptr,bx         ; save updated pointer
         clc                     ; return char in AL
         ret                     ; and Carry = 0 (clear)

 getc2:  stc                     ; end of input stream
         ret                     ; return Carry = 1 (set)

 getc    endp


 putc    proc    near            ; send char. to stdout
                                 ; returns Carry = 1 if
                                 ; error, else Carry = 0

         mov     bx,optr         ; store character into
         mov     [obuff+bx],al   ; output buffer

         inc     bx              ; bump buffer pointer
         cmp     bx,bufsiz       ; buffer full?
         jne     putc1           ; no, jump

         mov     bx,stdout       ; BX = standard output handle
         mov     cx,bufsiz       ; CX = length to write
         mov     dx,offset obuff ; DS:DX = buffer address
         mov     ah,40h          ; Fxn 40H = write
         int     21h             ; transfer to MS-DOS
         jc      putc2           ; jump if write failed

         cmp     ax,cx           ; was write complete?
         jne     putc2           ; jump if disk full

         xor     bx,bx           ; reset buffer pointer

 putc1:  mov     optr,bx         ; save buffer pointer
         clc                     ; write successful,
         ret                     ; return Carry = 0 (clear)

 putc2:  stc                     ; write failed or disk full,
         ret                     ; return Carry = 1 (set)

 putc    endp


 init    proc    near            ; initialize input buffer

         mov     bx,stdin        ; BX = standard input handle
         mov     cx,bufsiz       ; CX = length to read
         mov     dx,offset ibuff ; DS:DX = buffer address
         mov     ah,3fh          ; Fxn 3FH = read
         int     21h             ; transfer to MS-DOS
         jc      init1           ; jump if read failed
         mov     ilen,ax         ; save actual bytes read
 init1:  ret

 init    endp


 flush   proc    near            ; flush output buffer

         mov     cx,optr         ; CX = bytes to write
         jcxz    flush1          ; exit if buffer empty
         mov     dx,offset obuff ; DS:DX = buffer address
         mov     bx,stdout       ; BX = standard output handle
         mov     ah,40h          ; Fxn 40H = write
         int     21h             ; transfer to MS-DOS
 flush1: ret

 flush   endp

 _TEXT   ends


 _DATA   segment word public 'DATA'

 ibuff   db      bufsiz dup (0)  ; input buffer
 obuff   db      bufsiz dup (0)  ; output buffer

 iptr    dw      0               ; ibuff pointer
 ilen    dw      0               ; bytes in ibuff
 optr    dw      0               ; obuff pointer

 column  dw      0               ; current column counter

 msg1    db      cr,lf
         db      'clean: need MS-DOS version 2 or greater.'
         db      cr,lf,'$'

 msg2    db      cr,lf
         db      'clean: disk is full.'
         db      cr,lf
 msg2_len equ    $-msg2

 _DATA   ends


 STACK   segment para stack 'STACK'

         dw      64 dup (?)

 STACK   ends

         end     clean

 \SAMPCODE\ADVMSDOS\CHAP15\PROTO.ASM

          name      proto
          page      55,132
          title     PROTO.ASM --- prototype filter
 ;
 ; PROTO.ASM:  prototype character-oriented filter
 ;
 ; Copyright 1988 Ray Duncan
 ;
 ; Build:  MASM PROTO;
 ;         LINK PROTO;
 ;
 ; Usage:  PROTO
 ;

 stdin   equ     0               ; standard input handle
 stdout  equ     1               ; standard output handle
 stderr  equ     2               ; standard error handle

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII linefeed


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:_DATA,ss:STACK

 main    proc    far             ; entry point from MS-DOS

         mov     ax,_DATA        ; set DS = our data segment
         mov     ds,ax

 main1:                          ; read char. from stdin...
         mov     dx,offset char  ; DS:DX = buffer address
         mov     cx,1            ; CX = length to read
         mov     bx,stdin        ; BX = standard input handle
         mov     ah,3fh          ; Fxn 3FH = read
         int     21h             ; transfer to MS-DOS
         jc      main3           ; if error, terminate

         cmp     ax,1            ; any character read?
         jne     main2           ; if end of file, terminate

         call    translate       ; translate character

                                 ; write char. to stdout...
         mov     dx,offset char  ; DS:DX = buffer address
         mov     cx,1            ; CX = length to write
         mov     bx,stdout       ; BX = standard output handle
         mov     ah,40h          ; Fxn 40H = write
         int     21h             ; transfer to MS-DOS
         jc      main3           ; if error, terminate

         cmp     ax,1            ; was character written?
         jne     main3           ; if disk full, terminate

         jmp     main1           ; get another character

 main2:                          ; end of file reached
         mov     ax,4c00h        ; Fxn 4CH = terminate
                                 ; return code = 0
         int     21h             ; transfer to MS-DOS

 main3:                          ; error or disk full
         mov     ax,4c01h        ; Fxn 4CH = terminate
                                 ; return code = 1
         int     21h             ; transfer to MS-DOS

 main    endp

 ;
 ; Perform any necessary translation on character
 ; from standard input stored in variable 'char'.
 ; This example just leaves character unchanged.
 ;
 translate proc  near

         ret                     ; does nothing

 translate endp

 _TEXT   ends


 _DATA   segment word public 'DATA'

 char    db      0               ; storage for input character

 _DATA   ends


 STACK   segment para stack 'STACK'

         dw      64 dup (?)

 STACK   ends

         end     main            ; defines program entry point

 \SAMPCODE\DOS_ENCY
 \SAMPCODE\DOS_ENCY\10
 \SAMPCODE\DOS_ENCY\10\CHILD.ASM

          name      child
          title     'CHILD process'
 ;
 ; CHILD.EXE --- a simple process loaded by PARENT.EXE
 ; to demonstrate the MS-DOS EXEC call, Subfunction 00H.
 ;
 ; Ray Duncan, June 1987
 ;

 stdin   equ     0                       ; standard input
 stdout  equ     1                       ; standard output
 stderr  equ     2                       ; standard error

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII linefeed


 DGROUP  group   _DATA,STACK


 _TEXT   segment byte public 'CODE'      ; executable code segment

         assume  cs:_TEXT,ds:_DATA,ss:STACK

 main    proc    far                     ; entry point from MS-DOS

         mov     ax,_DATA                ; set DS = our data segment
         mov     ds,ax

                                         ; display child message ...
         mov     dx,offset msg           ; DS:DX = address of message
         mov     cx,msg_len              ; CX = length of message
         mov     bx,stdout               ; BX = standard output handle
         mov     ah,40h                  ; AH = fxn 40H, write file/device
         int     21h                     ; transfer to MS-DOS
         jc      main2                   ; jump if any error

         mov     ax,4c00h                ; no error, terminate child
         int     21h                     ; with return code = 0

 main2:  mov     ax,4c01h                ; error, terminate child
         int     21h                     ; with return code = 1

 main    endp                            ; end of main procedure

 _TEXT   ends


 _DATA   segment para public 'DATA'      ; static & variable data segment

 msg     db      cr,lf,'Child executing!',cr,lf
 msg_len equ     $-msg

 _DATA   ends


 STACK   segment para stack 'STACK'

         dw      64 dup (?)

 STACK   ends


         end     main                    ; defines program entry point

 \SAMPCODE\DOS_ENCY\10\OVERLAY.ASM

          name      overlay
          title     'OVERLAY segment'
 ;
 ; OVERLAY.EXE --- a simple overlay segment
 ; loaded by ROOT.EXE to demonstrate use of
 ; the MS-DOS EXEC call Subfunction 03H.
 ;
 ; The overlay does not contain a STACK segment
 ; because it uses the ROOT segment's stack.
 ;
 ; Ray Duncan, June 1987
 ;

 stdin   equ     0                       ; standard input
 stdout  equ     1                       ; standard output
 stderr  equ     2                       ; standard error

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII linefeed


 _TEXT   segment byte public 'CODE'      ; executable code segment

         assume  cs:_TEXT,ds:_DATA
 ovlay   proc    far                     ; entry point from root segment

         mov     ax,_DATA                ; set DS = local data segment
         mov     ds,ax

                                         ; display overlay message ...
         mov     dx,offset msg           ; DS:DX = address of message
         mov     cx,msg_len              ; CX = length of message
         mov     bx,stdout               ; BX = standard output handle
         mov     ah,40h                  ; AH = fxn 40H, write file/device
         int     21h                     ; transfer to MS-DOS

         ret                             ; return to root segment

 ovlay   endp                            ; end of ovlay procedure

 _TEXT   ends


 _DATA   segment para public 'DATA'      ; static & variable data segment

 msg     db      cr,lf,'Overlay executing!',cr,lf
 msg_len equ     $-msg

 _DATA   ends

         end

 \SAMPCODE\DOS_ENCY\10\PARENT.ASM

          name      parent
          title     'PARENT --- demonstrate EXEC call'
 ;
 ; PARENT.EXE --- demonstration of EXEC to run process
 ;
 ; Uses MS-DOS EXEC (Int 21H Function 4BH Subfunction 00H)
 ; to load and execute a child process named CHILD.EXE,
 ; then displays CHILD's return code.
 ;
 ; Ray Duncan, June 1987
 ;

 stdin   equ     0                       ; standard input
 stdout  equ     1                       ; standard output
 stderr  equ     2                       ; standard error

 stksize equ     128                     ; size of stack

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII linefeed


 DGROUP  group   _DATA,_ENVIR,_STACK


 _TEXT   segment byte public 'CODE'      ; executable code segment

         assume  cs:_TEXT,ds:_DATA,ss:_STACK


 stk_seg dw      ?                       ; original SS contents
 stk_ptr dw      ?                       ; original SP contents


 main    proc    far                     ; entry point from MS-DOS

         mov     ax,_DATA                ; set DS = our data segment
         mov     ds,ax

                                         ; now give back extra memory
                                         ; so child has somewhere to run...
         mov     ax,es                   ; let AX = segment of PSP base
         mov     bx,ss                   ; and BX = segment of stack base
         sub     bx,ax                   ; reserve seg stack - seg psp
         add     bx,stksize/16           ; plus paragraphs of stack
         mov     ah,4ah                  ; fxn 4AH = modify memory block
         int     21h
         jc      main1
                                         ; display parent message ...
         mov     dx,offset DGROUP:msg1   ; DS:DX = address of message
         mov     cx,msg1_len             ; CX = length of message
         call    pmsg

         push    ds                      ; save parent's data segment
         mov     stk_seg,ss              ; save parent's stack pointer
         mov     stk_ptr,sp

                                         ; now EXEC the child process...
         mov     ax,ds                   ; set ES = DS
         mov     es,ax
         mov     dx,offset DGROUP:cname  ; DS:DX = child pathname
         mov     bx,offset DGROUP:pars   ; EX:BX = parameter block
         mov     ax,4b00h                ; function 4BH subfunction 00H
         int     21h                     ; transfer to MS-DOS

         cli                             ; (for bug in some early 8088s)
         mov     ss,stk_seg              ; restore parent's stack pointer
         mov     sp,stk_ptr
         sti                             ; (for bug in some early 8088s)
         pop     ds                      ; restore DS = our data segment

         jc      main2                   ; jump if EXEC failed

                                         ; otherwise EXEC succeeded,
                                         ; convert and display child's
                                         ; termination and return codes...
         mov     ah,4dh                  ; fxn 4DH = get return code
         int     21h                     ; transfer to MS-DOS
         xchg    al,ah                   ; convert termination code
         mov     bx,offset DGROUP:msg4a
         call    b2hex
         mov     al,ah                   ; get back return code
         mov     bx,offset DGROUP:msg4b  ; and convert it
         call    b2hex
         mov     dx,offset DGROUP:msg4   ; DS:DX = address of message
         mov     cx,msg4_len             ; CX = length of message
         call    pmsg                    ; display it

         mov     ax,4c00h                ; no error, terminate program
         int     21h                     ; with return code = 0

 main1:  mov     bx,offset DGROUP:msg2a  ; convert error code
         call    b2hex
         mov     dx,offset DGROUP:msg2   ; display message 'Memory
         mov     cx,msg2_len             ; resize failed...'
         call    pmsg
         jmp     main3

 main2:  mov     bx,offset DGROUP:msg3a  ; convert error code
         call    b2hex
         mov     dx,offset DGROUP:msg3   ; display message 'EXEC
         mov     cx,msg3_len             ; call failed...'
         call    pmsg

 main3:  mov     ax,4c01h                ; error, terminate program
         int     21h                     ; with return code = 1

 main    endp                            ; end of main procedure


 b2hex   proc    near                    ; convert byte to hex ASCII
                                         ; call with AL = binary value
                                         ;           BX = addr to store string
         push    ax
         shr     al,1
         shr     al,1
         shr     al,1
         shr     al,1
         call    ascii                   ; become first ASCII character
         mov     [bx],al                 ; store it
         pop     ax
         and     al,0fh                  ; isolate lower 4 bits, which
         call    ascii                   ; become the second ASCII character
         mov     [bx+1],al               ; store it
         ret
 b2hex   endp


 ascii   proc    near                    ; convert value 00-0FH in AL
         add     al,'0'                  ; into a "hex ASCII" character
         cmp     al,'9'
         jle     ascii2                  ; jump if in range 00-09H,
         add     al,'A'-'9'-1            ; offset it to range 0A-0FH,

 ascii2: ret                             ; return ASCII char. in AL
 ascii   endp


 pmsg    proc    near                    ; displays message on standard output
                                         ; call with DS:DX = address,
                                         ;              CX = length

         mov     bx,stdout               ; BX = standard output handle
         mov     ah,40h                  ; function 40h = write file/device
         int     21h                     ; transfer to MS-DOS
         ret                             ; back to caller

 pmsg    endp

 _TEXT   ends


 _DATA   segment para public 'DATA'      ; static & variable data segment

 cname   db      'CHILD.EXE',0           ; pathname of child process

 pars    dw      _ENVIR                  ; segment of environment block
         dd      tail                    ; long address, command tail
         dd      fcb1                    ; long address, default FCB #1
         dd      fcb2                    ; long address, default FCB #2

 tail    db      fcb1-tail-2             ; command tail for child
         db      'dummy command tail',cr

 fcb1    db      0                       ; copied into default FCB #1 in
         db      11 dup (' ')            ; child's program segment prefix
         db      25 dup (0)

 fcb2    db      0                       ; copied into default FCB #2 in
         db      11 dup (' ')            ; child's program segment prefix
         db      25 dup (0)

 msg1    db      cr,lf,'Parent executing!',cr,lf
 msg1_len equ    $-msg1

 msg2    db      cr,lf,'Memory resize failed, error code='
 msg2a   db      'xxh.',cr,lf
 msg2_len equ    $-msg2

 msg3    db      cr,lf,'EXEC call failed, error code='
 msg3a   db      'xxh.',cr,lf
 msg3_len equ    $-msg3

 msg4    db      cr,lf,'Parent regained control!'
         db      cr,lf,'Child termination type='
 msg4a   db      'xxh, return code='
 msg4b   db      'xxh.',cr,lf
 msg4_len equ    $-msg4

 _DATA   ends


 _ENVIR  segment para public 'DATA'      ; example environment block
                                         ; to be passed to child

         db      'PATH=',0               ; basic PATH, PROMPT,
         db      'PROMPT=$p$_$n$g',0     ; and COMSPEC strings
         db      'COMSPEC=C:\COMMAND.COM',0
         db      0                       ; extra null terminates block

 _ENVIR  ends


 _STACK  segment para stack 'STACK'

         db      stksize dup (?)

 _STACK  ends


         end     main                    ; defines program entry point

 \SAMPCODE\DOS_ENCY\10\ROOT.ASM

          name      root
          title     'ROOT --- demonstrate EXEC overlay'
 ;
 ; ROOT.EXE --- demonstration of EXEC for overlays
 ;
 ; Uses MS-DOS EXEC (Int 21H Function 4BH Subfunction 03H)
 ; to load an overlay named OVERLAY.EXE, calls a routine
 ; within the OVERLAY, then recovers control and terminates.
 ;
 ; Ray Duncan, June 1987
 ;

 stdin   equ     0                       ; standard input
 stdout  equ     1                       ; standard output
 stderr  equ     2                       ; standard error

 stksize equ     128                     ; size of stack

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII linefeed


 DGROUP  group   _DATA,_STACK


 _TEXT   segment byte public 'CODE'      ; executable code segment

         assume  cs:_TEXT,ds:_DATA,ss:_STACK


 stk_seg dw      ?                       ; original SS contents
 stk_ptr dw      ?                       ; original SP contents


 main    proc    far                     ; entry point from MS-DOS

         mov     ax,_DATA                ; set DS = our data segment
         mov     ds,ax

                                         ; now give back extra memory
         mov     ax,es                   ; AX = segment of PSP base
         mov     bx,ss                   ; BX = segment of stack base
         sub     bx,ax                   ; reserve seg stack - seg psp
         add     bx,stksize/16           ; plus paragraphs of stack
         mov     ah,4ah                  ; fxn 4AH = modify memory block
         int     21h                     ; transfer to MS-DOS
         jc      main1                   ; jump if resize failed

                                         ; display message 'Root
                                         ; 'segment executing...'
         mov     dx,offset DGROUP:msg1   ; DS:DX = address of message
         mov     cx,msg1_len             ; CX = length of message
         call    pmsg

                                         ; allocate memory for overlay
         mov     bx,1000h                ; get 64 KB (4096 paragraphs)
         mov     ah,48h                  ; fxn 48h, allocate mem block
         int     21h                     ; transfer to MS-DOS
         jc      main2                   ; jump if allocation failed

         mov     pars,ax                 ; set load address for overlay
         mov     pars+2,ax               ; set relocation segment for overlay
         mov     word ptr entry+2,ax     ; set segment of entry point

         push    ds                      ; save root's data segment
         mov     stk_seg,ss              ; save root's stack pointer
         mov     stk_ptr,sp

                                         ; now use EXEC to load overlay
         mov     ax,ds                   ; set ES = DS
         mov     es,ax
         mov     dx,offset DGROUP:oname  ; DS:DX = overlay pathname
         mov     bx,offset DGROUP:pars   ; EX:BX = parameter block
         mov     ax,4b03h                ; function 4BH, subfunction 03H
         int     21h                     ; transfer to MS-DOS

         cli                             ; (for bug in some early 8088s)
         mov     ss,stk_seg              ; restore root's stack pointer
         mov     sp,stk_ptr
         sti                             ; (for bug in some early 8088s)
         pop     ds                      ; restore DS = our data segment

         jc      main3                   ; jump if EXEC failed

                                         ; otherwise EXEC succeeded...
         push    ds                      ; save our data segment
         call    dword ptr entry         ; now call the overlay
         pop     ds                      ; restore our data segment

                                         ; display message that root
                                         ; segment regained control...
         mov     dx,offset DGROUP:msg5   ; DS:DX = address of message
         mov     cx,msg5_len             ; CX = length of message
         call    pmsg                    ; display it

         mov     ax,4c00h                ; no error, terminate program
         int     21h                     ; with return code = 0

 main1:  mov     bx,offset DGROUP:msg2a  ; convert error code
         call    b2hex
         mov     dx,offset DGROUP:msg2   ; display message 'Memory
         mov     cx,msg2_len             ; resize failed...'
         call    pmsg
         jmp     main4

 main2:  mov     bx,offset DGROUP:msg3a  ; convert error code
         call    b2hex
         mov     dx,offset DGROUP:msg3   ; display message 'Memory
         mov     cx,msg3_len             ; allocation failed...'
         call    pmsg
         jmp     main4

 main3:  mov     bx,offset DGROUP:msg4a  ; convert error code
         call    b2hex
         mov     dx,offset DGROUP:msg4   ; display message 'EXEC
         mov     cx,msg4_len             ; call failed...'
         call    pmsg

 main4:  mov     ax,4c01h                ; error, terminate program
         int     21h                     ; with return code = 1

 main    endp                            ; end of main procedure


 b2hex   proc    near                    ; convert byte to hex ASCII
                                         ; call with AL = binary value
                                         ; BX = addr to store string
         push    ax
         shr     al,1
         shr     al,1
         shr     al,1
         shr     al,1
         call    ascii                   ; become first ASCII character
         mov     [bx],al                 ; store it
         pop     ax
         and     al,0fh                  ; isolate lower 4 bits, which
         call    ascii                   ; become the second ASCII character
         mov     [bx+1],al               ; store it
         ret
 b2hex   endp


 ascii   proc    near                    ; convert value 00-0FH in AL
         add     al,'0'                  ; into a "hex ASCII" character
         cmp     al,'9'
         jle     ascii2                  ; jump if in range 00-09H,
         add     al,'A'-'9'-1            ; offset it to range 0A-0FH,
 ascii2: ret                             ; return ASCII char. in AL.
 ascii   endp


 pmsg    proc    near                    ; displays message on standard output
                                         ; call with DS:DX = address,
                                         ;              CX = length

         mov     bx,stdout               ; BX = standard output handle
         mov     ah,40h                  ; function 40H = write file/device
         int     21h                     ; transfer to MS-DOS
         ret                             ; back to caller

 pmsg    endp

 _TEXT   ends


 _DATA   segment para public 'DATA'      ; static & variable data segment

 oname   db      'OVERLAY.OVL',0         ; pathname of overlay file

 pars    dw      0                       ; load address (segment) for file
         dw      0                       ; relocation (segment) for file

 entry   dd      0                       ; entry point for overlay

 msg1    db      cr,lf,'Root segment executing!',cr,lf
 msg1_len equ    $-msg1

 msg2    db      cr,lf,'Memory resize failed, error code='
 msg2a   db      'xxh.',cr,lf
 msg2_len equ    $-msg2

 msg3    db      cr,lf,'Memory allocation failed, error code='
 msg3a   db      'xxh.',cr,lf
 msg3_len equ    $-msg3

 msg4    db      cr,lf,'EXEC call failed, error code='
 msg4a   db      'xxh.',cr,lf
 msg4_len equ    $-msg4

 msg5    db      cr,lf,'Root segment regained control!',cr,lf
 msg5_len equ    $-msg5

 _DATA   ends


 _STACK  segment para stack 'STACK'

         db      stksize dup (?)

 _STACK  ends


         end     main                    ; defines program entry point

 \SAMPCODE\DOS_ENCY\11
 \SAMPCODE\DOS_ENCY\11\HELLO.ASM

 ;
 ; Name:          hello
 ;
 ; Description:   This RAM-resident (terminate-and-stay-resident) utility
 ;                displays the message "Hello, World" in response to a
 ;                software interrupt.
 ;
 ; Comments:      Assemble and link to create HELLO.EXE.
 ;
 ;                Execute HELLO.EXE to make resident.
 ;
 ;                Execute  INT 64h  to display the message.
 ;


 TSRInt           EQU     64h
 STDOUT           EQU     1

 RESIDENT_TEXT    SEGMENT byte public 'CODE'
                  ASSUME  cs:RESIDENT_TEXT,ds:RESIDENT_DATA

 TSRAction        PROC    far

                  sti                    ; enable interrupts

                  push    ds             ; preserve registers
                  push    ax
                  push    bx
                  push    cx
                  push    dx

                  mov     dx,seg RESIDENT_DATA
                  mov     ds,dx
                  mov     dx,offset Message      ; DS:DX -> message
                  mov     cx,16                  ; CX = length
                  mov     bx,STDOUT              ; BX = file handle
                  mov     ah,40h                 ; AH = INT 21h function 40h
                                                 ; (Write File)
                  int     21h                    ; display the message

                  pop     dx                     ; restore registers and exit
                  pop     cx
                  pop     bx
                  pop     ax
                  pop     ds
                  iret

 TSRAction        ENDP

 RESIDENT_TEXT    ENDS


 RESIDENT_DATA    SEGMENT word public 'DATA'

 Message          DB      0Dh,0Ah,'Hello, World',0Dh,0Ah

 RESIDENT_DATA    ENDS


 TRANSIENT_TEXT   SEGMENT para public 'TCODE'
                  ASSUME  cs:TRANSIENT_TEXT,ss:TRANSIENT_STACK

 HelloTSR PROC    far                    ; At entry:     CS:IP -> SnapTSR
                                         ;               SS:SP -> stack
                                         ;               DS,ES -> PSP
 ; Install this TSR's interrupt handler

                  mov     ax,seg RESIDENT_TEXT
                  mov     ds,ax
                  mov     dx,offset RESIDENT_TEXT:TSRAction
                  mov     al,TSRInt
                  mov     ah,25h
                  int     21h

 ; Terminate and stay resident

                  mov     dx,cs          ; DX = paragraph address of start of
                                         ; transient portion (end of resident
                                         ; portion)
                  mov     ax,es          ; ES = PSP segment
                  sub     dx,ax          ; DX = size of resident portion

                  mov     ax,3100h       ; AH = INT 21h function number (TSR)
                                         ; AL = 00H (return code)
                  int     21h

 HelloTSR         ENDP

 TRANSIENT_TEXT   ENDS


 TRANSIENT_STACK  SEGMENT word stack 'TSTACK'

                  DB      80h dup(?)

 TRANSIENT_STACK  ENDS


                  END     HelloTSR

 \SAMPCODE\DOS_ENCY\11\INDOS_1.ASM

         test    ss:[CriticalErrorFlag],0FFH        ;(versions 3.1 and later)
         jne     NearLabel
         push    ss:[NearWord]
         int     28H

 \SAMPCODE\DOS_ENCY\11\INDOS_2.ASM

         cmp     ss:[CriticalErrorFlag],00          ;(versions earlier than 3.
         jne     NearLabel
         int     28H

 \SAMPCODE\DOS_ENCY\11\SNAP.ASM

 ;
 ; Name:         snap
 ;
 ; Description:  This RAM-resident (terminate-and-stay-resident) utility
 ;               produces a video "snapshot" by copying the contents of the
 ;               video regeneration buffer to a disk file.  It may be used
 ;               in 80-column alphanumeric video modes on IBM PCs and PS/2s.
 ;
 ; Comments:     Assemble and link to create SNAP.EXE.
 ;
 ;               Execute SNAP.EXE to make resident.
 ;
 ;               Press Alt-Enter to dump current contents of video buffer
 ;               to a disk file.
 ;

 MultiplexID     EQU     0CAh            ; unique INT 2FH ID value

 TSRStackSize    EQU     100h            ; resident stack size in bytes

 KB_FLAG         EQU     17h             ; offset of shift-key status flag in
                                         ; ROM BIOS keyboard data area

 KBIns           EQU     80h             ; bit masks for KB_FLAG
 KBCaps          EQU     40h
 KBNum           EQU     20h
 KBScroll        EQU     10h
 KBAlt           EQU     8
 KBCtl           EQU     4
 KBLeft          EQU     2
 KBRight         EQU     1

 SCEnter         EQU     1Ch

 CR              EQU     0Dh
 LF              EQU     0Ah
 TRUE            EQU     -1
 FALSE           EQU     0

                 PAGE
 ;----------------------------------------------------------------------------
 ;
 ; RAM-resident routines
 ;
 ;----------------------------------------------------------------------------

 RESIDENT_GROUP  GROUP   RESIDENT_TEXT,RESIDENT_DATA,RESIDENT_STACK

 RESIDENT_TEXT   SEGMENT byte public 'CODE'
                 ASSUME  cs:RESIDENT_GROUP,ds:RESIDENT_GROUP

 ;----------------------------------------------------------------------------
 ; System verification routines
 ;----------------------------------------------------------------------------

 VerifyDOSState  PROC    near            ; Returns:    carry flag set if MS-DO
                                         ;             is busy

                 push    ds              ; preserve these registers
                 push    bx
                 push    ax

                 lds     bx,cs:ErrorModeAddr
                 mov     ah,[bx]         ; AH = ErrorMode flag

                 lds     bx,cs:InDOSAddr
                 mov     al,[bx]         ; AL = InDOS flag

                 xor     bx,bx           ; BH = 00H, BL = 00H
                 cmp     bl,cs:InISR28   ; carry flag set if INT 28H handler
                                         ; is running
                 rcl     bl,01h          ; BL = 01H if INT 28H handler is runn

                 cmp     bx,ax           ; carry flag zero if AH = 00H
                                         ; and AL <= BL

                 pop     ax              ; restore registers
                 pop     bx
                 pop     ds
                 ret

 VerifyDOSState  ENDP


 VerifyIntState  PROC    near            ; Returns:    carry flag set if hardw
                                         ;             or ROM BIOS unstable

                 push    ax              ; preserve AX

 ; Verify hardware interrupt status by interrogating Intel 8259A Programmable
 ;  Interrupt Controller

                 mov     ax,00001011b    ; AH = 0
                                         ; AL = 0CW3 for Intel 8259A (RR = 1,
                                         ; RIS = 1)
                 out     20h,al          ; request 8259A's in-service register
                 jmp     short L10       ; wait a few cycles
 L10:            in      al,20h          ; AL = hardware interrupts currently
                                         ; being serviced (bit = 1 if in-servi
                 cmp     ah,al
                 jc      L11             ; exit if any hardware interrupts sti
                                         ; being serviced

 ; Verify status of ROM BIOS interrupt handlers

                 xor     al,al           ; AL = 00H

                 cmp     al,cs:InISR5
                 jc      L11             ; exit if currently in INT 05H handle

                 cmp     al,cs:InISR9
                 jc      L11             ; exit if currently in INT 09H handle

                 cmp     al,cs:InISR10
                 jc      L11             ; exit if currently in INT 10H handle

                 cmp     al,cs:InISR13   ; set carry flag if currently in
                                         ; INT 13H handler

 L11:            pop     ax              ; restore AX and return
                 ret

 VerifyIntState  ENDP


 VerifyTSRState  PROC    near            ; Returns: carry flag set if TSR
                                         ;          inactive

                 rol     cs:HotFlag,1    ; carry flag set if (HotFlag = TRUE)
                 cmc                     ; carry flag set if (HotFlag = FALSE)
                 jc      L20             ; exit if no hot key

                 ror     cs:ActiveTSR,1  ; carry flag set if (ActiveTSR = TRUE
                 jc      L20             ; exit if already active

                 call    VerifyDOSState
                 jc      L20             ; exit if MS-DOS unstable

                 call    VerifyIntState  ; set carry flag if hardware or BIOS
                                         ; unstable

 L20:            ret

 VerifyTSRState  ENDP

                 PAGE
 ;----------------------------------------------------------------------------
 ; System monitor routines
 ;----------------------------------------------------------------------------

 ISR5            PROC    far             ; INT 05H handler
                                         ; (ROM BIOS print screen)

                 inc     cs:InISR5       ; increment status flag

                 pushf
                 cli
                 call    cs:PrevISR5     ; chain to previous INT 05H handler

                 dec     cs:InISR5       ; decrement status flag
                 iret

 ISR5            ENDP


 ISR8            PROC    far             ; INT 08H handler (timer tick, IRQ0)

                 pushf
                 cli
                 call    cs:PrevISR8     ; chain to previous handler

                 cmp     cs:InISR8,0
                 jne     L31             ; exit if already in this handler

                 inc     cs:InISR8       ; increment status flag

                 sti                     ; interrupts are ok
                 call    VerifyTSRState
                 jc      L30             ; jump if TSR is inactive

                 mov     byte ptr cs:ActiveTSR,TRUE
                 call    TSRapp
                 mov     byte ptr cs:ActiveTSR,FALSE

 L30:            dec     cs:InISR8

 L31:            iret

 ISR8            ENDP


 ISR9            PROC    far             ; INT 09H handler
                                         ; (keyboard interrupt IRQ1)

                 push    ds              ; preserve these registers
                 push    ax
                 push    bx

                 push    cs
                 pop     ds              ; DS -> RESIDENT_GROUP

                 in      al,60h          ; AL = current scan code

                 pushf                   ; simulate an INT
                 cli
                 call    ds:PrevISR9     ; let previous handler execute

                 mov     ah,ds:InISR9    ; if already in this handler ..
                 or      ah,ds:HotFlag   ; .. or currently processing hot key
                 jnz     L43             ; .. jump to exit

                 inc     ds:InISR9       ; increment status flag
                 sti                     ; now interrupts are ok

 ; Check scan code sequence

                 cmp     ds:HotSeqLen,0
                 je      L40             ; jump if no hot sequence to match

                 mov     bx,ds:HotIndex
                 cmp     al,[bx+HotSequence]     ; test scan code sequence
                 jne     L41             ; jump if no match

                 inc     bx
                 cmp     bx,ds:HotSeqLen
                 jb      L42             ; jump if not last scan code to match

 ; Check shift-key state

 L40:            push    ds
                 mov     ax,40h
                 mov     ds,ax           ; DS -> ROM BIOS data area
                 mov     al,ds:[KB_FLAG] ; AH = ROM BIOS shift-key flags
                 pop     ds

                 and     al,ds:HotKBMask ; AL = flags AND "don't care" mask
                 cmp     al,ds:HotKBFlag
                 jne     L42             ; jump if shift state does not match

 ; Set flag when hot key is found

                 mov     byte ptr ds:HotFlag,TRUE

 L41:            xor     bx,bx           ; reinitialize index

 L42:            mov     ds:HotIndex,bx  ; update index into sequence
                 dec     ds:InISR9       ; decrement status flag

 L43:            pop     bx              ; restore registers and exit
                 pop     ax
                 pop     ds
                 iret

 ISR9            ENDP


 ISR10           PROC    far             ; INT 10H handler (ROM BIOS video I/O

                 inc     cs:InISR10      ; increment status flag

                 pushf
                 cli
                 call    cs:PrevISR10    ; chain to previous INT 10H handler

                 dec     cs:InISR10      ; decrement status flag
                 iret

 ISR10           ENDP


 ISR13           PROC    far             ; INT 13H handler
                                         ; (ROM BIOS fixed disk I/O)

                 inc     cs:InISR13      ; increment status flag

                 pushf
                 cli
                 call    cs:PrevISR13    ; chain to previous INT 13H handler

                 pushf                   ; preserve returned flags
                 dec     cs:InISR13      ; decrement status flag
                 popf                    ; restore flags register

                 sti                     ; enable interrupts
                 ret     2               ; simulate IRET without popping flags

 ISR13           ENDP


 ISR1B           PROC    far             ; INT 1BH trap (ROM BIOS Ctrl-Break)

                 mov     byte ptr cs:Trap1B,TRUE
                 iret

 ISR1B           ENDP


 ISR23           PROC    far             ; INT 23H trap (MS-DOS Ctrl-C)

                 mov     byte ptr cs:Trap23,TRUE
                 iret

 ISR23           ENDP


 ISR24           PROC    far             ; INT 24H trap (MS-DOS critical error

                 mov     byte ptr cs:Trap24,TRUE

                 xor     al,al           ; AL = 00H (MS-DOS 2.x):
                                         ; ignore the error
                 cmp     cs:MajorVersion,2
                 je      L50

                 mov     al,3            ; AL = 03H (MS-DOS 3.x):
                                         ; fail the MS-DOS call in which
                                         ; the critical error occurred
 L50:            iret

 ISR24           ENDP


 ISR28           PROC    far             ; INT 28H handler
                                         ; (MS-DOS idle interrupt)

                 pushf
                 cli
                 call    cs:PrevISR28    ; chain to previous INT 28H handler

                 cmp     cs:InISR28,0
                 jne     L61             ; exit if already inside this handler

                 inc     cs:InISR28      ; increment status flag

                 call    VerifyTSRState
                 jc      L60             ; jump if TSR is inactive

                 mov     byte ptr cs:ActiveTSR,TRUE
                 call    TSRapp
                 mov     byte ptr cs:ActiveTSR,FALSE

 L60:            dec     cs:InISR28      ; decrement status flag

 L61:            iret

 ISR28           ENDP


 ISR2F           PROC    far             ; INT 2FH handler
                                         ; (MS-DOS multiplex interrupt)
                                         ; Caller:  AH = handler ID
                                         ;          AL = function number
                                         ; Returns for function 0:  AL = 0FFH
                                         ; for all other functions:  nothing

                 cmp     ah,MultiplexID
                 je      L70             ; jump if this handler is requested

                 jmp     cs:PrevISR2F    ; chain to previous INT 2FH handler

 L70:            test    al,al
                 jnz     MultiplexIRET   ; jump if reserved or undefined funct

 ; Function 0:  get installed state

                 mov     al,0FFh         ; AL = 0FFh (this handler is installe

 MultiplexIRET:  iret                    ; return from interrupt

 ISR2F           ENDP

                 PAGE
 ;
 ;
 ; AuxInt21--sets ErrorMode while executing INT 21h to force use of the
 ;       AuxStack instead of the IOStack.
 ;
 ;

 AuxInt21        PROC    near            ; Caller:     registers for INT 21H
                                         ; Returns:    registers from INT 21H

                 push    ds
                 push    bx
                 lds     bx,ErrorModeAddr
                 inc     byte ptr [bx]   ; ErrorMode is now nonzero
                 pop     bx
                 pop     ds

                 int     21h             ; perform MS-DOS function

                 push    ds
                 push    bx
                 lds     bx,ErrorModeAddr
                 dec     byte ptr [bx]   ; restore ErrorMode
                 pop     bx
                 pop     ds
                 ret

 AuxInt21        ENDP


 Int21v          PROC    near            ; perform INT 21H or AuxInt21,
                                         ; depending on MS-DOS version

                 cmp     DOSVersion,30Ah
                 jb      L80             ; jump if earlier than 3.1

                 int     21h             ; version 3.1 and later
                 ret

 L80:            call    AuxInt21        ; versions earlier than 3.1
                 ret

 Int21v          ENDP

                 PAGE
 ;----------------------------------------------------------------------------
 ; RAM-resident application
 ;----------------------------------------------------------------------------

 TSRapp          PROC    near

 ; Set up a safe stack

                 push    ds              ; save previous DS on previous stack

                 push    cs
                 pop     ds              ; DS -> RESIDENT_GROUP

                 mov     PrevSP,sp       ; save previous SS:SP
                 mov     PrevSS,ss

                 mov     ss,TSRSS        ; SS:SP -> RESIDENT_STACK
                 mov     sp,TSRSP

                 push    es              ; preserve remaining registers
                 push    ax
                 push    bx
                 push    cx
                 push    dx
                 push    si
                 push    di
                 push    bp

                 cld                     ; clear direction flag

 ; Set break and critical error traps

                 mov     cx,NTrap
                 mov     si,offset RESIDENT_GROUP:StartTrapList

 L90:            lodsb                   ; AL = interrupt number
                                         ; DS:SI -> byte past interrupt number

                 mov     byte ptr [si],FALSE     ; zero the trap flag

                 push    ax              ; preserve AX
                 mov     ah,35h          ; INT 21H function 35H
                                         ; (get interrupt vector)
                 int     21h             ; ES:BX = previous interrupt vector
                 mov     [si+1],bx       ; save offset and segment ..
                 mov     [si+3],es       ;  .. of previous handler

                 pop     ax              ; AL = interrupt number
                 mov     dx,[si+5]       ; DS:DX -> this TSR's trap
                 mov     ah,25h          ; INT 21H function 25H
                 int     21h             ; (set interrupt vector)
                 add     si,7            ; DS:SI -> next in list

                 loop    L90

 ; Disable MS-DOS break checking during disk I/O

                 mov     ax,3300h        ; AH = INT 21H function number
                                         ; AL = 00H (request current break sta
                 int     21h             ; DL = current break state
                 mov     PrevBreak,dl    ; preserve current state

                 xor     dl,dl           ; DL = 00H (disable disk I/O break
                                         ; checking)
                 mov     ax,3301h        ; AL = 01H (set break state)
                 int     21h

 ; Preserve previous extended error information

                 cmp     DOSVersion,30Ah
                 jb      L91             ; jump if MS-DOS version earlier
                                         ; than 3.1
                 push    ds              ; preserve DS
                 xor     bx,bx           ; BX = 00H (required for function 59H
                 mov     ah,59h          ; INT 21H function 59H
                 call    Int21v          ; (get extended error info)

                 mov     cs:PrevExtErrDS,ds
                 pop     ds
                 mov     PrevExtErrAX,ax ; preserve error information
                 mov     PrevExtErrBX,bx ; in data structure
                 mov     PrevExtErrCX,cx
                 mov     PrevExtErrDX,dx
                 mov     PrevExtErrSI,si
                 mov     PrevExtErrDI,di
                 mov     PrevExtErrES,es

 ; Inform MS-DOS about current PSP

 L91:            mov     ah,51h          ; INT 21H function 51H (get PSP addre
                 call    Int21v          ; BX = foreground PSP

                 mov     PrevPSP,bx      ; preserve previous PSP

                 mov     bx,TSRPSP       ; BX = resident PSP
                 mov     ah,50h          ; INT 21H function 50H (set PSP addre
                 call    Int21v

 ; Inform MS-DOS about current DTA (not really necessary in this application
 ; because DTA is not used)

                 mov     ah,2Fh          ; INT 21H function 2FH
                 int     21h             ; (get DTA address) into ES:BX
                 mov     PrevDTAoffs,bx
                 mov     PrevDTAseg,es

                 push    ds              ; preserve DS
                 mov     ds,TSRPSP
                 mov     dx,80h          ; DS:DX -> default DTA at PSP:0080
                 mov     ah,1Ah          ; INT 21H function 1AH
                 int     21h             ; (set DTA address)
                 pop     ds              ; restore DS

 ; Open a file, write to it, and close it

                 mov     ax,0E07h        ; AH = INT 10H function number
                                         ; (Write Teletype)
                                         ; AL = 07H (bell character)
                 int     10h             ; emit a beep

                 mov     dx,offset RESIDENT_GROUP:SnapFile
                 mov     ah,3Ch          ; INT 21H function 3CH
                                         ; (create file handle)
                 mov     cx,0            ; file attribute
                 int     21h
                 jc      L94             ; jump if file not opened

                 push    ax              ; push file handle
                 mov     ah,0Fh          ; INT 10H function 0FH
                                         ; (get video status)
                 int     10h             ; AL = video mode number
                                         ; AH = number of character columns
                 pop     bx              ; BX = file handle

                 cmp     ah,80
                 jne     L93             ; jump if not 80-column mode

                 mov     dx,0B800h       ; DX = color video buffer segment
                 cmp     al,3
                 jbe     L92             ; jump if color alphanumeric mode

                 cmp     al,7
                 jne     L93             ; jump if not monochrome mode

                 mov     dx,0B000h       ; DX = monochrome video buffer segmen

 L92:            push    ds
                 mov     ds,dx
                 xor     dx,dx           ; DS:DX -> start of video buffer
                 mov     cx,80*25*2      ; CX = number of bytes to write
                 mov     ah,40h          ; INT 21H function 40H (write file)
                 int     21h
                 pop     ds

 L93:            mov     ah,3Eh          ; INT 21H function 3EH (close file)
                 int     21h

                 mov     ax,0E07h        ; emit another beep
                 int     10h

 ; Restore previous DTA

 L94:            push    ds              ; preserve DS
                 lds     dx,PrevDTA      ; DS:DX -> previous DTA
                 mov     ah,1Ah          ; INT 21H function 1AH (set DTA addre
                 int     21h
                 pop     ds

 ; Restore previous PSP

                 mov     bx,PrevPSP      ; BX = previous PSP
                 mov     ah,50h          ; INT 21H function 50H
                 call    Int21v          ; (set PSP address)

 ; Restore previous extended error information

                 mov     ax,DOSVersion
                 cmp     ax,30AH
                 jb      L95             ; jump if MS-DOS version earlier than
                 cmp     ax,0A00h
                 jae     L95             ; jump if MS OS/2-DOS 3.x box

                 mov     dx,offset RESIDENT_GROUP:PrevExtErrInfo
                 mov     ax,5D0Ah
                 int     21h             ; (restore extended error information

 ; Restore previous MS-DOS break checking

 L95:            mov     dl,PrevBreak    ; DL = previous state
                 mov     ax,3301h
                 int     21h

 ; Restore previous break and critical error traps

                 mov     cx,NTrap
                 mov     si,offset RESIDENT_GROUP:StartTrapList
                 push    ds              ; preserve DS

 L96:            lods    byte ptr cs:[si] ; AL = interrupt number
                                         ; ES:SI -> byte past interrupt number

                 lds     dx,cs:[si+1]    ; DS:DX -> previous handler
                 mov     ah,25h          ; INT 21H function 25H
                 int     21h             ; (set interrupt vector)
                 add     si,7            ; DS:SI -> next in list
                 loop    L96

                 pop     ds              ; restore DS

 ; Restore all registers

                 pop     bp
                 pop     di
                 pop     si
                 pop     dx
                 pop     cx
                 pop     bx
                 pop     ax
                 pop     es

                 mov     ss,PrevSS       ; SS:SP -> previous stack
                 mov     sp,PrevSP
                 pop     ds              ; restore previous DS

 ; Finally, reset status flag and return

                 mov     byte ptr cs:HotFlag,FALSE
                 ret

 TSRapp          ENDP


 RESIDENT_TEXT   ENDS


 RESIDENT_DATA   SEGMENT word public 'DATA'

 ErrorModeAddr   DD      ?               ; address of MS-DOS ErrorMode flag
 InDOSAddr       DD      ?               ; address of MS-DOS InDOS flag

 NISR            DW      (EndISRList-StartISRList)/8 ; number of installed ISR

 StartISRList    DB      05h             ; INT number
 InISR5          DB      FALSE           ; flag
 PrevISR5        DD      ?               ; address of previous handler
                 DW      offset RESIDENT_GROUP:ISR5

                 DB      08h
 InISR8          DB      FALSE
 PrevISR8        DD      ?
                 DW      offset RESIDENT_GROUP:ISR8

                 DB      09h
 InISR9          DB      FALSE
 PrevISR9        DD      ?
                 DW      offset RESIDENT_GROUP:ISR9

                 DB      10h
 InISR10         DB      FALSE
 PrevISR10       DD      ?
                 DW      offset RESIDENT_GROUP:ISR10

                 DB      13h
 InISR13         DB      FALSE
 PrevISR13       DD      ?
                 DW      offset RESIDENT_GROUP:ISR13

                 DB      28h
 InISR28         DB      FALSE
 PrevISR28       DD      ?
                 DW      offset RESIDENT_GROUP:ISR28

                 DB      2Fh
 InISR2F         DB      FALSE
 PrevISR2F       DD      ?
                 DW      offset RESIDENT_GROUP:ISR2F

 EndISRList      LABEL   BYTE

 TSRPSP          DW      ?               ; resident PSP
 TSRSP           DW      TSRStackSize    ; resident SS:SP
 TSRSS           DW      seg RESIDENT_STACK
 PrevPSP         DW      ?               ; previous PSP
 PrevSP          DW      ?               ; previous SS:SP
 PrevSS          DW      ?

 HotIndex        DW      0               ; index of next scan code in sequence
 HotSeqLen       DW      EndHotSeq-HotSequence   ; length of hot-key sequence

 HotSequence     DB      SCEnter         ; hot sequence of scan codes
 EndHotSeq       LABEL   BYTE

 HotKBFlag       DB      KBAlt           ; hot value of ROM BIOS KB_FLAG
 HotKBMask       DB      (KBIns OR KBCaps OR KBNum OR KBScroll) XOR 0FFh
 HotFlag         DB      FALSE

 ActiveTSR       DB      FALSE

 DOSVersion      LABEL   WORD
                 DB      ?               ; minor version number
 MajorVersion    DB      ?               ; major version number

 ; The following data is used by the TSR application:

 NTrap           DW      (EndTrapList-StartTrapList)/8   ; number of traps

 StartTrapList   DB      1Bh
 Trap1B          DB      FALSE
 PrevISR1B       DD      ?
                 DW      offset RESIDENT_GROUP:ISR1B

                 DB      23h
 Trap23          DB      FALSE
 PrevISR23       DD      ?
                 DW      offset RESIDENT_GROUP:ISR23

                 DB      24h
 Trap24          DB      FALSE
 PrevISR24       DD      ?
                 DW      offset RESIDENT_GROUP:ISR24

 EndTrapList     LABEL   BYTE

 PrevBreak       DB      ?               ; previous break-checking flag

 PrevDTA         LABEL   DWORD           ; previous DTA address
 PrevDTAoffs     DW      ?
 PrevDTAseg      DW      ?

 PrevExtErrInfo  LABEL   BYTE            ; previous extended error information
 PrevExtErrAX    DW      ?
 PrevExtErrBX    DW      ?
 PrevExtErrCX    DW      ?
 PrevExtErrDX    DW      ?
 PrevExtErrSI    DW      ?
 PrevExtErrDI    DW      ?
 PrevExtErrDS    DW      ?
 PrevExtErrES    DW      ?
                 DW      3 dup(0)

 SnapFile        DB      '\snap.img'     ; output filename in root directory

 RESIDENT_DATA   ENDS

 RESIDENT_STACK  SEGMENT word stack 'STACK'

                 DB      TSRStackSize dup(?)

 RESIDENT_STACK  ENDS

                 PAGE
 ;----------------------------------------------------------------------------
 ;
 ; Transient installation routines
 ;
 ;----------------------------------------------------------------------------

 TRANSIENT_TEXT  SEGMENT para public 'TCODE'
                 ASSUME  cs:TRANSIENT_TEXT,ds:RESIDENT_DATA,ss:RESIDENT_STACK

 InstallSnapTSR  PROC    far             ; At entry:  CS:IP -> InstallSnapTSR
                                         ;            SS:SP -> stack
                                         ;            DS,ES -> PSP
 ; Save PSP segment

                 mov     ax,seg RESIDENT_DATA
                 mov     ds,ax           ; DS  -> RESIDENT_DATA

                 mov     TSRPSP,es       ; save PSP segment

 ; Check the MS-DOS version

                 call    GetDOSVersion   ; AH = major version number
                                         ; AL = minor version number

 ; Verify that this TSR is not already installed
 ;
 ;       Before executing INT 2Fh in MS-DOS versions 2.x, test whether INT 2FH
 ;       vector is in use.  If so, abort if PRINT.COM is using it.
 ;
 ;       (Thus, in MS-DOS 2.x, if both this program and PRINT.COM are used,
 ;       this program should be made resident before PRINT.COM.)

                 cmp     ah,2
                 ja      L101            ; jump if version 3.0 or later

                 mov     ax,352Fh        ; AH = INT 21H function number
                                         ; AL = interrupt number
                 int     21h             ; ES:BX = INT 2FH vector

                 mov     ax,es
                 or      ax,bx           ; jump if current INT 2FH vector ..
                 jnz     L100            ; .. is nonzero

                 push    ds
                 mov     ax,252Fh        ; AH = INT 21H function number
                                         ; AL = interrupt number
                 mov     dx,seg RESIDENT_GROUP
                 mov     ds,dx
                 mov     dx,offset RESIDENT_GROUP:MultiplexIRET

                 int     21h             ; point INT 2FH vector to IRET
                 pop     ds
                 jmp     short L103      ; jump to install this TSR

 L100:           mov     ax,0FF00h       ; look for PRINT.COM:
                 int     2Fh             ; if resident, AH = print queue lengt
                                         ; otherwise, AH is unchanged

                 cmp     ah,0FFh         ; if PRINT.COM is not resident ..
                 je      L101            ; .. use multiplex interrupt

                 mov     al,1
                 call    FatalError      ; abort if PRINT.COM already installe

 L101:           mov     ah,MultiplexID  ; AH = multiplex interrupt ID value
                 xor     al,al           ; AL = 00H
                 int     2Fh             ; multiplex interrupt

                 test    al,al
                 jz      L103            ; jump if ok to install

                 cmp     al,0FFh
                 jne     L102            ; jump if not already installed

                 mov     al,2
                 call    FatalError      ; already installed

 L102:           mov     al,3
                 call    FatalError      ; can't install

 ; Get addresses of InDOS and ErrorMode flags

 L103:           call    GetDOSFlags

 ; Install this TSR's interrupt handlers

                 push    es              ; preserve PSP segment

                 mov     cx,NISR
                 mov     si,offset StartISRList

 L104:           lodsb                   ; AL = interrupt number
                                         ; DS:SI -> byte past interrupt number
                 push    ax              ; preserve AX
                 mov     ah,35h          ; INT 21H function 35H
                 int     21h             ; ES:BX = previous interrupt vector
                 mov     [si+1],bx       ; save offset and segment ..
                 mov     [si+3],es       ; .. of previous handler

                 pop     ax              ; AL = interrupt number
                 push    ds              ; preserve DS
                 mov     dx,[si+5]
                 mov     bx,seg RESIDENT_GROUP
                 mov     ds,bx           ; DS:DX -> this TSR's handler
                 mov     ah,25h          ; INT 21H function 25H
                 int     21h             ; (set interrupt vector)
                 pop     ds              ; restore DS
                 add     si,7            ; DS:SI -> next in list
                 loop    L104

 ; Free the environment

                 pop     es              ; ES = PSP segment
                 push    es              ; preserve PSP segment
                 mov     es,es:[2Ch]     ; ES = segment of environment
                 mov     ah,49h          ; INT 21H function 49H
                 int     21h             ; (free memory block)

 ; Terminate and stay resident

                 pop     ax              ; AX = PSP segment
                 mov     dx,cs           ; DX = paragraph address of start of
                                         ; transient portion (end of resident
                                         ; portion)
                 sub     dx,ax           ; DX = size of resident portion

                 mov     ax,3100h        ; AH = INT 21H function number
                                         ; AL = 00H (return code)
                 int     21h

 InstallSnapTSR  ENDP


 GetDOSVersion   PROC    near            ; Caller:   DS = seg RESIDENT_DATA
                                         ;           ES = PSP
                                         ; Returns:  AH = major version
                                         ;           AL = minor version
                 ASSUME  ds:RESIDENT_DATA

                 mov     ax,30h          ; INT 21h function 30H:
                                         ; (get MS-DOS version)
                 int     21h
                 cmp     al,2
                 jb      L110            ; jump if version 1.x

                 xchg    ah,al           ; AH = major version
                                         ; AL = minor version
                 mov     DOSVersion,ax   ; save with major version in
                                         ; high-order byte
                 ret

 L110:           mov     al,00h
                 call    FatalError      ; abort if version 1.x

 GetDOSVersion   ENDP
 GetDOSFlags     PROC    near            ; Caller:     DS = seg RESIDENT_DATA
                                         ; Returns:    InDOSAddr -> InDOS
                                         ;             ErrorModeAddr -> ErrorM
                                         ; Destroys:   AX,BX,CX,DI
                 ASSUME  ds:RESIDENT_DATA

 ; Get InDOS address from MS-DOS

                 push    es

                 mov     ah,34h          ; INT 21H function number
                 int     21h             ; ES:BX -> InDOS
                 mov     word ptr InDOSAddr,bx
                 mov     word ptr InDOSAddr+2,es

 ; Determine ErrorMode address

                 mov     word ptr ErrorModeAddr+2,es     ; assume ErrorMode is
                                                         ; in the same segment
                                                         ; as InDOS

                 mov     ax,DOSVersion
                 cmp     ax,30Ah
                 jb      L120            ; jump if MS-DOS version earlier
                                         ; than 3.1 ..
                 cmp     ax,0A00h
                 jae     L120            ; .. or MS OS/2-DOS 3.x box

                 dec     bx              ; in MS-DOS 3.1 and later, ErrorMode
                 mov     word ptr ErrorModeAddr,bx       ; is just before InDO
                 jmp     short L125

 L120:                                   ; scan MS-DOS segment for ErrorMode

                 mov     cx,0FFFFh       ; CX = maximum number of bytes to sca
                 xor     di,di           ; ES:DI -> start of MS-DOS segment

 L121:           mov     ax,word ptr cs:LF2  ; AX = opcode for INT 28H

 L122:           repne   scasb           ; scan for first byte of fragment
                 jne     L126            ; jump if not found

                 cmp     ah,es:[di]              ; inspect second byte of opco
                 jne     L122                    ; jump if not INT 28H

                 mov     ax,word ptr cs:LF1 + 1  ; AX = opcode for CMP
                 cmp     ax,es:[di][LF1-LF2]
                 jne     L123                    ; jump if opcode not CMP

                 mov     ax,es:[di][(LF1-LF2)+2] ; AX = offset of ErrorMode
                 jmp     short L124              ; in DOS segment

 L123:           mov     ax,word ptr cs:LF3 + 1  ; AX = opcode for TEST
                 cmp     ax,es:[di][LF3-LF4]
                 jne     L121                    ; jump if opcode not TEST

                 mov     ax,es:[di][(LF3-LF4)+2] ; AX = offset of ErrorMode

 L124:           mov     word ptr ErrorModeAddr,ax

 L125:           pop     es
                 ret

 ; Come here if address of ErrorMode not found

 L126:           mov     al,04H
                 call    FatalError


 ; Code fragments for scanning for ErrorMode flag

 LFnear          LABEL   near            ; dummy labels for addressing
 LFbyte          LABEL   byte
 LFword          LABEL   word
                                         ; MS-DOS versions earlier than 3.1
 LF1:            cmp     ss:LFbyte,0     ; CMP ErrorMode,0
                 jne     LFnear
 LF2:            int     28h
                                         ; MS-DOS versions 3.1 and later

 LF3:            test    ss:LFbyte,0FFh  ; TEST ErrorMode,0FFH
                 jne     LFnear
                 push    ss:LFword
 LF4:            int     28h

 GetDOSFlags     ENDP

 FatalError      PROC    near            ; Caller:   AL = message number
                                         ;           ES = PSP
                 ASSUME  ds:TRANSIENT_DATA

                 push    ax              ; save message number on stack

                 mov     bx,seg TRANSIENT_DATA
                 mov     ds,bx

 ; Display the requested message

                 mov     bx,offset MessageTable
                 xor     ah,ah           ; AX = message number
                 shl     ax,1            ; AX = offset into MessageTable
                 add     bx,ax           ; DS:BX -> address of message
                 mov     dx,[bx]         ; DS:BX -> message
                 mov     ah,09h          ; INT 21H function 09H (display strin
                 int     21h             ; display error message

                 pop     ax              ; AL = message number
                 or      al,al
                 jz      L130            ; jump if message number is zero
                                         ; (MS-DOS versions 1.x)

 ; Terminate (MS-DOS 2.x and later)

                 mov     ah,4Ch          ; INT 21H function 4CH
                 int     21h             ; (terminate process with return code

 ; Terminate (MS-DOS 1.x)

 L130            PROC    far

                 push    es              ; push PSP:0000H
                 xor     ax,ax
                 push    ax
                 ret                     ; far return (jump to PSP:0000H)

 L130            ENDP

 FatalError      ENDP


 TRANSIENT_TEXT  ENDS

                 PAGE
 ;
 ;
 ; Transient data segment
 ;
 ;

 TRANSIENT_DATA  SEGMENT word public 'DATA'

 MessageTable    DW   Message0           ; MS-DOS version error
                 DW   Message1           ; PRINT.COM found in MS-DOS 2.x
                 DW   Message2           ; already installed
                 DW   Message3           ; can't install
                 DW   Message4           ; can't find flag

 Message0        DB   CR,LF,'TSR requires MS-DOS 2.0 or later version',CR,LF,'
 Message1        DB   CR,LF,'Can''t install TSR:  PRINT.COM active',CR,LF,'$'
 Message2        DB   CR,LF,'This TSR is already installed',CR,LF,'$'
 Message3        DB   CR,LF,'Can''t install this TSR',CR,LF,'$'
 Message4        DB   CR,LF,'Unable to locate MS-DOS ErrorMode flag',CR,LF,'$'

 TRANSIENT_DATA  ENDS

                 END     InstallSnapTSR

 \SAMPCODE\DOS_ENCY\12
 \SAMPCODE\DOS_ENCY\12\FXN59H_1.ASM

 myfcb   db      0               ; drive = default
         db      'MYFILE  '      ; filename, 8 chars
         db      'DAT'           ; extension, 3 chars
         db      25 dup (0)      ; remainder of FCB
         .
         .
         .
         mov     dx,seg myfcb    ; DS:DX = FCB
         mov     ds,dx
         mov     dx,offset myfcb
         mov     ah,0fh          ; function 0FH = Open FCB

         int     21h             ; transfer to MS-DOS
         or      al,al           ; test status
         jz      success         ; jump, open succeeded
                                 ; open failed, get
                                 ; extended error info
         mov     bx,0            ; BX = 00H for ver. 2.x-3.x
         mov     ah,59h          ; function 59H = Get Info
         int     21h             ; transfer to MS-DOS
         or      ax,ax           ; really an error?
         jz      success         ; no error, jump
                                 ; test recommended actions
         cmp     bl,01h
         jz      retry           ; if BL = 01H retry operation
         cmp     bl,04h
         jz      cleanup         ; if BL = 04H clean up and exit
         cmp     bl,05h
         jz      panic           ; if BL = 05H exit immediately
         .
         .
         .

 \SAMPCODE\DOS_ENCY\12\FXN59H_2.ASM

 myfile  db      'MYFILE.DAT',0  ; ASCIIZ filename
         .
         .
         .
         mov     dx,seg myfile   ; DS:DX = ASCIIZ filename
         mov     ds,dx
         mov     dx,offset myfile
         mov     ax,3d02h        ; open, read/write
         int     21h             ; transfer to MS-DOS
         jnc     success         ; jump, open succeeded
                                 ; open failed, get
                                 ; extended error info
         mov     bx,0            ; BX = 00H for ver. 2.x-3.x
         mov     ah,59h          ; function 59H = Get Info
         int     21h             ; transfer to MS-DOS
         or      ax,ax           ; really an error?
         jz      success         ; no error, jump
                                 ; test recommended actions
         cmp     bl,01h
         jz      retry           ; if BL = 01H retry operation
         .
         .
         .

 \SAMPCODE\DOS_ENCY\12\INT24.ASM

         name    int24
         title   INT24 Critical Error Handler

 ;
 ; INT24.ASM -- Replacement critical error handler
 ; by Ray Duncan, September 1987
 ;

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII linefeed

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 save24  dd      0               ; previous contents of Int 24H
                                 ; critical error handler vector

                                 ; prompt message used by
                                 ; critical error handler
 prompt  db      cr,lf,'Critical Error Occurred: '
         db      'Abort, Retry, Ignore, Fail? $'

 keys    db      'aArRiIfF'      ; possible user response keys
 keys_len equ    $-keys          ; (both cases of each allowed)

 codes   db      2,2,1,1,0,0,3,3 ; codes returned to MS-DOS kernel
                                 ; for corresponding response keys

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

         public  get24
 get24   proc    near            ; set Int 24H vector to point
                                 ; to new critical error handler

         push    ds              ; save segment registers
         push    es

         mov     ax,3524h        ; get address of previous
         int     21h             ; INT 24H handler and save it

         mov     word ptr save24,bx
         mov     word ptr save24+2,es

         push    cs              ; set DS:DX to point to
         pop     ds              ; new INT 24H handler
         mov     dx,offset _TEXT:int24
         mov     ax,2524h        ; then call MS-DOS to
         int     21h             ; set the INT 24H vector

         pop     es              ; restore segment registers
         pop     ds
         ret                     ; and return to caller

 get24   endp


         public  res24
 res24   proc    near            ; restore original contents
                                 ; of Int 24H vector

         push    ds              ; save our data segment

         lds     dx,save24       ; put address of old handler
         mov     ax,2524h        ; back into INT 24H vector
         int     21h

         pop     ds              ; restore data segment
         ret                     ; and return to caller

 res24   endp

 ;
 ; This is the replacement critical error handler.  It
 ; prompts the user for Abort, Retry, Ignore, or Fail and
 ; returns the appropriate code to the MS-DOS kernel.
 ;
 int24   proc    far             ; entered from MS-DOS kernel

         push    bx              ; save registers
         push    cx
         push    dx
         push    si
         push    di
         push    bp
         push    ds
         push    es

 int24a: mov     ax,DGROUP       ; display prompt for user
         mov     ds,ax           ; using function 09H (print string
         mov     es,ax           ; terminated by $ character)
         mov     dx,offset prompt
         mov     ah,09h
         int     21h

         mov     ah,01h          ; get user's response
         int     21h             ; Function 01H = read one character

         mov     di,offset keys  ; look up code for response key
         mov     cx,keys_len
         cld
         repne scasb
         jnz     int24a          ; prompt again if bad response

                                 ; set AL = action code for MS-DOS
                                 ; according to key that was entered:
                                 ; 0 = ignore, 1 = retry, 2 = abort, 3 = fail
         mov     al,[di+keys_len-1]

         pop     es              ; restore registers
         pop     ds
         pop     bp
         pop     di
         pop     si
         pop     dx
         pop     cx
         pop     bx
         iret                    ; exit critical error handler

 int24   endp

 _TEXT   ends

         end

 \SAMPCODE\DOS_ENCY\12\REST_INT.ASM

 rest_int proc   near
         push    ds              ; save our data segment
         mov     dx,word ptr oldint23
         mov     ds,word ptr oldint23+2
         mov     ax,2523h        ; restore original contents
         int     21h             ; of Int 23H vector
         pop     ds              ; restore our data segment
         push    ds              ; then save it again
         mov     dx,word ptr oldint1B
         mov     ds,word ptr oldint1B+2
         mov     ax,251Bh        ; restore original contents
         int     21h             ; of Int 1BH vector
         pop     ds              ; get back our data segment
         ret                     ; return to caller
 rest_int endp

 \SAMPCODE\DOS_ENCY\12\SET_INT.ASM

 _DATA   segment para public 'DATA'
 oldint1b dd     0               ; original INT 1BH vector
 oldint23 dd     0               ; original INT 23H vector
 _DATA   ends
 _TEXT   segment byte public 'CODE'
         assume cs:_TEXT,ds:_DATA,es:NOTHING
 myint1b:                        ; handler for Ctrl-Break
 myint23:                        ; handler for Ctrl-C
         iret

 set_int proc    near
         mov     ax,351bh        ; get current contents of
         int     21h             ; Int 1BH vector and save it
         mov     word ptr oldint1b,bx
         mov     word ptr oldint1b+2,es
         mov     ax,3523h        ; get current contents of
         int     21h             ; Int 23H vector and save it
         mov     word ptr oldint23,bx
         mov     word ptr oldint23+2,es
         push    ds              ; save our data segment
         push    cs              ; let DS point to our
         pop     ds              ; code segment
         mov     dx,offset myint1b
         mov     ax,251bh        ; set interrupt vector 1BH
         int     21h             ; to point to new handler
         mov     dx,offset myint23
         mov     ax,2523h        ; set interrupt vector 23H
         int     21h             ; to point to new handler
         pop     ds              ; restore our data segment
         ret                     ; back to caller
 set_int endp
 _TEXT   ends

 \SAMPCODE\DOS_ENCY\13
 \SAMPCODE\DOS_ENCY\13\DIVZERO.ASM

         name    divzero
         title   'DIVZERO - Interrupt 00H Handler'
 ;
 ; DIVZERO.ASM: Demonstration Interrupt 00H Handler
 ;
 ; To assemble, link, and convert to COM file:
 ;
 ;       C>MASM DIVZERO;  <Enter>
 ;       C>LINK DIVZERO;  <Enter>
 ;       C>EXE2BIN DIVZERO.EXE DIVZERO.COM  <Enter>
 ;       C>DEL DIVZERO.EXE  <Enter>
 ;

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII linefeed
 eos     equ     '$'             ; end of string marker

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT

         org     100h

 entry:  jmp     start           ; skip over data area

 intmsg  db      'Divide by Zero Occurred!',cr,lf,eos

 divmsg  db      'Dividing '     ; message used by demo
 par1    db      '0000h'         ; dividend goes here
         db      ' by '
 par2    db      '00h'           ; divisor goes here
         db      ' equals '
 par3    db      '00h'           ; quotient here
         db      ' remainder '
 par4    db      '00h'           ; and remainder here
         db      cr,lf,eos

 oldint0 dd      ?               ; save old Int 00H vector

 intflag db      0               ; nonzero if divide by
                                 ; zero interrupt occurred

 oldip   dw      0               ; save old IP value


 ;
 ; The routine 'int0' is the actual divide by zero
 ; interrupt handler.  It gains control whenever a
 ; divide by zero or overflow occurs.  Its action
 ; is to set a flag and then increment the instruction
 ; pointer saved on the stack so that the failing
 ; divide will not be reexecuted after the IRET.
 ;
 ; In this particular case we can call MS-DOS to
 ; display a message during interrupt handling
 ; because the application triggers the interrupt
 ; intentionally. Thus, it is known that MS-DOS or
 ; other interrupt handlers are not in control
 ; at the point of interrupt.
 ;

 int0:   pop     cs:oldip        ; capture instruction pointer

         push    ax
         push    bx
         push    cx
         push    dx
         push    di
         push    si
         push    ds
         push    es

         push    cs              ; set DS = CS
         pop     ds

         mov     ah,09h          ; print error message
         mov     dx,offset _TEXT:intmsg
         int     21h

         add     oldip,2         ; bypass instruction causing
                                 ; divide by zero error

         mov     intflag,1       ; set divide by 0 flag

         pop     es              ; restore all registers
         pop     ds
         pop     si
         pop     di
         pop     dx
         pop     cx
         pop     bx
         pop     ax

         push    cs:oldip        ; restore instruction pointer

         iret                    ; return from interrupt


 ;
 ; The code beginning at 'start' is the application
 ; program.  It alters the vector for Interrupt 00H to
 ; point to the new handler, carries out some divide
 ; operations (including one that will trigger an
 ; interrupt) for demonstration purposes, restores
 ; the original contents of the Interrupt 00H vector,
 ; and then terminates.
 ;

 start:  mov     ax,3500h        ; get current contents
         int     21h             ; of Int 00H vector

                                 ; save segment:offset
                                 ; of previous Int 00H handler
         mov     word ptr oldint0,bx
         mov     word ptr oldint0+2,es

                                 ; install new handler...
         mov     dx,offset int0  ; DS:DX = handler address
         mov     ax,2500h        ; call MS-DOS to set
         int     21h             ; Int 00H vector

                                 ; now our handler is active,
                                 ; carry out some test divides.

         mov     ax,20h          ; test divide
         mov     bx,1            ; divide by 1
         call    divide

         mov     ax,1234h        ; test divide
         mov     bx,5eh          ; divide by 5EH
         call    divide

         mov     ax,5678h        ; test divide
         mov     bx,7fh          ; divide by 127
         call    divide

         mov     ax,20h          ; test divide
         mov     bx,0            ; divide by 0
         call    divide          ; (triggers interrupt)

                                 ; demonstration complete,
                                 ; restore old handler

         lds     dx,oldint0      ; DS:DX = handler address
         mov     ax,2500h        ; call MS-DOS to set
         int     21h             ; Int 00H vector

         mov     ax,4c00h        ; final exit to MS-DOS
         int     21h             ; with return code = 0

 ;
 ; The routine 'divide' carries out a trial division,
 ; displaying the arguments and the results.  It is
 ; called with AX = dividend and BL = divisor.
 ;

 divide  proc    near

         push    ax              ; save arguments
         push    bx

         mov     di,offset par1  ; convert dividend to
         call    wtoa            ; ASCII for display

         mov     ax,bx           ; convert divisor to
         mov     di,offset par2  ; ASCII for display
         call    btoa

         pop     bx              ; restore arguments
         pop     ax

         div     bl              ; perform the division
         cmp     intflag,0       ; divide by zero detected?
         jne     nodiv           ; yes, skip display

         push    ax              ; no, convert quotient to
         mov     di,offset par3  ; ASCII for display
         call    btoa

         pop     ax              ; convert remainder to
         xchg    ah,al           ; ASCII for display
         mov     di,offset par4
         call    btoa

         mov     ah,09h          ; show arguments, results
         mov     dx,offset divmsg
         int     21h

 nodiv:  mov     intflag,0       ; clear divide by 0 flag
         ret                     ; and return to caller

 divide  endp



 wtoa    proc    near            ; convert word to hex ASCII
                                 ; call with AX = binary value
                                 ;           DI = addr for string
                                 ; returns AX, CX, DI destroyed

         push    ax              ; save original value
         mov     al,ah
         call    btoa            ; convert upper byte
         add     di,2            ; increment output address
         pop     ax
         call    btoa            ; convert lower byte
         ret                     ; return to caller

 wtoa    endp



 btoa    proc    near            ; convert byte to hex ASCII
                                 ; call with AL = binary value
                                 ;           DI = addr to store string
                                 ; returns AX, CX destroyed

         mov     ah,al           ; save lower nibble
         mov     cx,4            ; shift right 4 positions
         shr     al,cl           ; to get upper nibble
         call    ascii           ; convert 4 bits to ASCII
         mov     [di],al         ; store in output string
         mov     al,ah           ; get back lower nibble

         and     al,0fh          ; blank out upper one
         call    ascii           ; convert 4 bits to ASCII
         mov     [di+1],al       ; store in output string
         ret                     ; back to caller

 btoa    endp



 ascii   proc    near            ; convert AL bits 0-3 to
                                 ; ASCII {0...9,A...F}
         add     al,'0'          ; and return digit in AL
         cmp     al,'9'
         jle     ascii2
         add     al,'A'-'9'-1    ; "fudge factor" for A-F
 ascii2: ret                     ; return to caller

 ascii   endp

 _TEXT   ends

         end     entry

 \SAMPCODE\DOS_ENCY\13\INT08H.ASM

         .
         .
         .
 myflag  dw      ?                       ; variable to be incremented
                                         ; on each timer-tick interrupt

 oldint8 dd      ?                       ; contains address of previous
                                         ; timer-tick interrupt handler
         .
         .                               ; get the previous contents
         .                               ; of the Interrupt 0BH vector...
         mov     ax,3508h                ; AH = 35H (Get Interrupt Vector)
         int     21h                     ; AL = Interrupt number (08H)
         mov     word ptr oldint8,bx     ; save the address of
         mov     word ptr oldint8+2,es   ; the previous Int 08H Handler
         mov     dx,seg myint8           ; put address of the new
         mov     ds,dx                   ; interrupt handler into DS:DX
         mov     dx,offset myint8        ; and call MS-DOS to set vector
         mov     ax,2508h                ; AH = 25H (Set Interrupt Vector)
         int     21h                     ; AL = Interrupt number (08H)
         .
         .
         .
 myint8:                                 ; this is the new handler
                                         ; for Interrupt 08H

         inc     cs:myflag               ; increment variable on each
                                         ; timer-tick interrupt

         jmp     dword ptr cs:[oldint8]  ; then chain to the
                                         ; previous interrupt handler

 \SAMPCODE\DOS_ENCY\13\MYINT8.ASM

 myint8:                                 ; this is the new handler
                                         ; for Interrupt 08H

         mov     ax,1                    ; test and set interrupt-
         xchg    cs:myflag,ax            ; handling-in-progress semaphore

         push    ax                      ; save the semaphore

         pushf                           ; simulate interrupt, allowing
         call    dword ptr cs:oldint8    ; the previous handler for the
                                         ; Interrupt 08H vector to run

         pop     ax                      ; get the semaphore back
         or      ax,ax                   ; is our interrupt handler
                                         ; already running?

         jnz     myint8x                 ; yes, skip this one

         .                               ; now perform our interrupt
         .                               ; processing here...
         .

         mov     cs:myflag,0             ; clear the interrupt-handling-
                                         ; in-progress flag

 myint8x:
         iret                            ; return from interrupt

 \SAMPCODE\DOS_ENCY\14
 \SAMPCODE\DOS_ENCY\14\FIND.C

 /*
         FIND.C          Searches text stream for a string.

         Usage:          FIND "pattern" [< source] [> destination]

         by Ray Duncan, June 1987

 */

 #include <stdio.h>

 #define TAB     '\x09'                  /* ASCII tab character (^I) */
 #define BLANK   '\x20'                  /* ASCII space character */

 #define TAB_WIDTH 8                     /* columns per tab stop */

 static char input[256];                 /* buffer for line from input */
 static char output[256];                /* buffer for line to output */
 static char pattern[256];               /* buffer for search pattern */

 main(argc,argv)
 int argc;
 char *argv[];
 {       int line=0;                     /* initialize line variable */

         if ( argc < 2 )                 /* was search pattern supplied? */
         {       puts("find: missing pattern.");
                 exit(1);                /* abort if not */
         }
         strcpy(pattern,argv[1]);        /* save copy of string to find */
         strupr(pattern);                /* fold it to uppercase */
         while( gets(input) != NULL )    /* read a line from input */
         {       line++;                 /* count lines */
                 strcpy(output,input);   /* save copy of input string */
                 strupr(input);          /* fold input to uppercase */
                                         /* if line contains pattern */
                 if( strstr(input,pattern) )
                                         /* write it to standard output */
                         writeline(line,output);
         }
         exit(0);                        /* terminate at end of file */
 }

 /*
         WRITELINE: Write line number and text to standard output,
         expanding any tab characters to stops defined by TAB_WIDTH.
 */

 writeline(line,p)
 int line;
 char *p;
 {       int i=0;                        /* index to original line text */
         int col=0;                      /* actual output column counter */
         printf("\n%4d: ",line);         /* write line number */
         while( p[i]!=NULL )             /* while end of line not reached */
         {       if(p[i]==TAB)           /* if current char = tab, expand it *
                 {       do putchar(BLANK);
                         while((++col % TAB_WIDTH) != 0);
                 }
                 else                    /* otherwise just send character */
                 {       putchar(p[i]);
                         col++;          /* count columns */
                 }
                 i++;                    /* advance through output line */
         }
 }

 \SAMPCODE\DOS_ENCY\14\LC.C

 /*
         LC:     a simple character-oriented filter to translate
                 all uppercase {A-Z} to lowercase {a-z} characters.

         Usage:  LC [< source] [> destination]

         Ray Duncan, June 1987

 */

 #include <stdio.h>

 main(argc,argv)
 int argc;
 char *argv[];
 {       char ch;
                                         /* read a character */
         while ( (ch=getchar() ) != EOF )
         {       ch=translate(ch);       /* perform any necessary
                                            character translation */
                 putchar(ch);            /* then write character */
         }
         exit(0);                        /* terminate at end of file */
 }

 /*
         Translate characters A-Z to lowercase equivalents
 */

 int translate(ch)
 char ch;
 {       if (ch >= 'A' && ch <= 'Z') ch += 'a'-'A';
         return (ch);
 }

 \SAMPCODE\DOS_ENCY\14\PROTOC.C

 /*
         PROTOC.C: a template for a character-oriented filter.

         Ray Duncan, June 1987
 */

 #include <stdio.h>

 main(argc,argv)
 int argc;
 char *argv[];
 {       char ch;

         while ( (ch=getchar())!=EOF )   /* read a character */
         {       ch=translate(ch);       /* translate it if necessary */
                 putchar(ch);            /* write the character */
         }
         exit(0);                        /* terminate at end of file */
 }

 /*
         Perform any necessary translation on character from
         input file.  Template action just returns same character.
 */

 int translate(ch)
 char ch;
 {       return (ch);
 }

 \SAMPCODE\DOS_ENCY\14\PROTOL.C

 /*
         PROTOL.C: a template for a line-oriented filter.

         Ray Duncan, June 1987.
 */

 #include <stdio.h>

 static char input[256];                 /* buffer for input line */
 static char output[256];                /* buffer for output line */

 main(argc,argv)
 int argc;
 char *argv[];
 {       while( gets(input) != NULL )    /* get a line from input stream */
                                         /* perform any necessary translation
                                            and possibly write result */
         {       if (translate()) puts(output);
         }
         exit(0);                        /* terminate at end of file */
 }

 /*
         Perform any necessary translation on input line, leaving
         the resulting text in output buffer.  Value of function
         is 'true' if output buffer should be written to standard output
         by main routine, 'false' if nothing should be written.
 */

 translate()
 {       strcpy(output,input);           /* template action is copy input */
         return(1);                      /* line and return true flag */
 }

 \SAMPCODE\DOS_ENCY\14\EXECSORT.ASM

         name    execsort
         title   'EXECSORT --- demonstrate EXEC of filter'
         .sall
 ;
 ; EXECSORT.ASM --- demonstration of use of EXEC to run the SORT
 ; filter as a child process, redirecting its input and output.
 ; This program requires the files SORT.EXE and MYFILE.DAT in
 ; the current drive and directory.
 ;
 ; Ray Duncan, June 1987
 ;

 stdin   equ     0                       ; standard input
 stdout  equ     1                       ; standard output
 stderr  equ     2                       ; standard error

 stksize equ     128                     ; size of stack

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII linefeed

 jerr    macro   target                  ;; Macro to test carry flag
         local   notset                  ;; and jump if flag set.
         jnc     notset                  ;; Uses JMP DISP16 to avoid
         jmp     target                  ;; branch out of range errors
 notset:
         endm


 DGROUP  group   _DATA,_STACK            ; 'automatic data group'


 _TEXT   segment byte public 'CODE'      ; executable code segment

         assume  cs:_TEXT,ds:DGROUP,ss:_STACK


 stk_seg dw      ?                       ; original SS contents
 stk_ptr dw      ?                       ; original SP contents


 main    proc    far                     ; entry point from MS-DOS

         mov     ax,DGROUP               ; set DS = our data segment
         mov     ds,ax

                                         ; now give back extra memory so
                                         ; child SORT has somewhere to run...
         mov     ax,es                   ; let AX = segment of PSP base
         mov     bx,ss                   ; and BX = segment of stack base
         sub     bx,ax                   ; reserve seg stack - seg psp
         add     bx,stksize/16           ; plus paragraphs of stack
         mov     ah,4ah                  ; fxn 4AH = modify memory block
         int     21h                     ; transfer to MS-DOS
         jerr    main1                   ; jump if resize block failed

                                         ; prepare stdin and stdout
                                         ; handles for child SORT process

         mov     bx,stdin                ; dup the handle for stdin
         mov     ah,45h
         int     21h                     ; transfer to MS-DOS
         jerr    main1                   ; jump if dup failed
         mov     oldin,ax                ; save dup'd handle

         mov     dx,offset DGROUP:infile ; now open the input file
         mov     ax,3d00h                ; mode = read-only
         int     21h                     ; transfer to MS-DOS
         jerr    main1                   ; jump if open failed

         mov     bx,ax                   ; force stdin handle to
         mov     cx,stdin                ; track the input file handle
         mov     ah,46h
         int     21h                     ; transfer to MS-DOS
         jerr    main1                   ; jump if force dup failed

         mov     bx,stdout               ; dup the handle for stdout
         mov     ah,45h
         int     21h                     ; transfer to MS-DOS
         jerr    main1                   ; jump if dup failed
         mov     oldout,ax               ; save dup'd handle

         mov     dx,offset dGROUP:outfile ; now create the output file
         mov     cx,0                    ; normal attribute
         mov     ah,3ch
         int     21h                     ; transfer to MS-DOS
         jerr    main1                   ; jump if create failed

         mov     bx,ax                   ; force stdout handle to
         mov     cx,stdout               ; track the output file handle
         mov     ah,46h
         int     21h                     ; transfer to MS-DOS
         jerr    main1                   ; jump if force dup failed

                                         ; now EXEC the child SORT,
                                         ; which will inherit redirected
                                         ; stdin and stdout handles

         push    ds                      ; save EXECSORT's data segment
         mov     stk_seg,ss              ; save EXECSORT's stack pointer
         mov     stk_ptr,sp

         mov     ax,ds                   ; set ES = DS
         mov     es,ax
         mov     dx,offset DGROUP:cname  ; DS:DX = child pathname
         mov     bx,offset DGROUP:pars   ; EX:BX = parameter block
         mov     ax,4b00h                ; function 4BH, subfunction 00H
         int     21h                     ; transfer to MS-DOS

         cli                             ; (for bug in some early 8088s)
         mov     ss,stk_seg              ; restore execsort's stack pointer
         mov     sp,stk_ptr
         sti                             ; (for bug in some early 8088s)
         pop     ds                      ; restore DS = our data segment

         jerr    main1                   ; jump if EXEC failed

         mov     bx,oldin                ; restore original meaning of
         mov     cx,stdin                ; standard input handle for
         mov     ah,46h                  ; this process
         int     21h
         jerr    main1                   ; jump if force dup failed

         mov     bx,oldout               ; restore original meaning
         mov     cx,stdout               ; of standard output handle
         mov     ah,46h                  ; for this process
         int     21h
         jerr    main1                   ; jump if force dup failed

         mov     bx,oldin                ; close dup'd handle of
         mov     ah,3eh                  ; original stdin
         int     21h                     ; transfer to MS-DOS
         jerr    main1                   ; jump if close failed

         mov     bx,oldout               ; close dup'd handle of
         mov     ah,3eh                  ; original stdout
         int     21h                     ; transfer to MS-DOS
         jerr    main1                   ; jump if close failed

                                         ; display success message
         mov     dx,offset DGROUP:msg1   ; address of message
         mov     cx,msg1_len             ; message length
         mov     bx,stdout               ; handle for standard output
         mov     ah,40h                  ; fxn 40H = write file or device
         int     21h                     ; transfer to MS-DOS
         jerr    main1

         mov     ax,4c00h                ; no error, terminate program
         int     21h                     ; with return code = 0

 main1:  mov     ax,4c01h                ; error, terminate program
         int     21h                     ; with return code = 1

 main    endp                            ; end of main procedure

 _TEXT   ends


 _DATA   segment para public 'DATA'      ; static & variable data segment

 infile  db      'MYFILE.DAT',0          ; input file for SORT filter
 outfile db      'MYFILE.SRT',0          ; output file for SORT filter

 oldin   dw      ?                       ; dup of old stdin handle
 oldout  dw      ?                       ; dup of old stdout handle

 cname   db      'SORT.EXE',0            ; pathname of child SORT process

 pars    dw      0                       ; segment of environment block
                                         ; (0 = inherit parent's)
         dd      tail                    ; long address, command tail
         dd      -1                      ; long address, default FCB #1
                                         ; (-1 = none supplied)
         dd      -1                      ; long address, default FCB #2
                                         ; (-1 = none supplied)

 tail    db      0,cr                    ; empty command tail for child

 msg1    db      cr,lf,'SORT was executed as child.',cr,lf
 msg1_len equ    $-msg1

 _DATA   ends


 _STACK  segment para stack 'STACK'

         db      stksize dup (?)

 _STACK  ends


         end     main                    ; defines program entry point

 \SAMPCODE\DOS_ENCY\14\LC.ASM

         name    lc
         title   'LC.ASM --- lowercase filter'
 ;
 ; LC.ASM:       a simple character-oriented filter to translate
 ;               all uppercase {A-Z} to lowercase {a-z}.
 ;
 ; Ray Duncan, June 1987
 ;

 stdin   equ     0               ; standard input
 stdout  equ     1               ; standard output
 stderr  equ     2               ; standard error

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII linefeed


 DGROUP  group   _DATA,STACK     ; 'automatic data group'


 _TEXT   segment byte public 'CODE'

         assume  cs:_TEXT,ds:DGROUP,ss:STACK

 main    proc    far             ; entry point from MS-DOS

         mov     ax,DGROUP       ; set DS = our data segment
         mov     ds,ax

 main1:                          ; read a character from standard input
         mov     dx,offset DGROUP:char   ; address to place character
         mov     cx,1            ; length to read = 1
         mov     bx,stdin        ; handle for standard input
         mov     ah,3fh          ; function 3FH = read from file or device
         int     21h             ; transfer to MS-DOS
         jc      main3           ; error, terminate
         cmp     ax,1            ; any character read?
         jne     main2           ; end of file, terminate program

         call    translt         ; translate character if necessary

                                 ; now write character to standard output
         mov     dx,offset DGROUP:char   ; address of character
         mov     cx,1            ; length to write = 1
         mov     bx,stdout       ; handle for standard output
         mov     ah,40h          ; function 40H = write to file or device
         int     21h             ; transfer to MS-DOS
         jc      main3           ; error, terminate
         cmp     ax,1            ; was character written?
         jne     main3           ; disk full, terminate program
         jmp     main1           ; go process another character

 main2:  mov     ax,4c00h        ; end of file reached, terminate
         int     21h             ; program with return code = 0

 main3:  mov     ax,4c01h        ; error or disk full, terminate
         int     21h             ; program with return code = 1

 main    endp                    ; end of main procedure

 ;
 ; Translate uppercase {A-Z} characters to corresponding
 ; lowercase characters {a-z}.  Leave other characters unchanged.
 ;
 translt proc    near

         cmp     byte ptr char,'A'
         jb      transx
         cmp     byte ptr char,'Z'
         ja      transx
         add     byte ptr char,'a'-'A'
 transx: ret

 translt endp


 _TEXT   ends


 _DATA   segment word public 'DATA'

 char    db      0               ; temporary storage for input character

 _DATA   ends


 STACK   segment para stack 'STACK'

         dw      64 dup (?)

 STACK   ends


         end     main            ; defines program entry point

 \SAMPCODE\DOS_ENCY\14\PROTOC.ASM

         name    protoc
         title   'PROTOC.ASM --- template character filter'
 ;
 ; PROTOC.ASM: a template for a character-oriented filter.
 ;
 ; Ray Duncan, June 1987
 ;

 stdin   equ     0               ; standard input
 stdout  equ     1               ; standard output
 stderr  equ     2               ; standard error

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII linefeed


 DGROUP  group   _DATA,STACK     ; 'automatic data group'


 _TEXT   segment byte public 'CODE'

         assume  cs:_TEXT,ds:DGROUP,ss:STACK

 main    proc    far             ; entry point from MS-DOS

         mov     ax,DGROUP       ; set DS = our data segment
         mov     ds,ax

 main1:                          ; read a character from standard input
         mov     dx,offset DGROUP:char   ; address to place character
         mov     cx,1            ; length to read = 1
         mov     bx,stdin        ; handle for standard input
         mov     ah,3fh          ; function 3FH = read from file or device
         int     21h             ; transfer to MS-DOS
         jc      main3           ; error, terminate
         cmp     ax,1            ; any character read?
         jne     main2           ; end of file, terminate program

         call    translt         ; translate character if necessary

                                 ; now write character to standard output
         mov     dx,offset DGROUP:char   ; address of character
         mov     cx,1            ; length to write = 1
         mov     bx,stdout       ; handle for standard output
         mov     ah,40h          ; function 40H = write to file or device
         int     21h             ; transfer to MS-DOS
         jc      main3           ; error, terminate
         cmp     ax,1            ; was character written?
         jne     main3           ; disk full, terminate program
         jmp     main1           ; go process another character

 main2:  mov     ax,4c00h        ; end of file reached, terminate
         int     21h             ; program with return code = 0

 main3:  mov     ax,4c01h        ; error or disk full, terminate
         int     21h             ; program with return code = 1

 main    endp                    ; end of main procedure

 ;
 ; Perform any necessary translation on character from input,
 ; stored in 'char'.  Template action: leave character unchanged.
 ;
 translt proc    near

         ret                     ; template action: do nothing

 translt endp


 _TEXT   ends


 _DATA   segment word public 'DATA'

 char    db      0               ; temporary storage for input character

 _DATA   ends


 STACK   segment para stack 'STACK'

         dw      64 dup (?)

 STACK   ends


         end     main            ; defines program entry point

 \SAMPCODE\DOS_ENCY\14\PROTOL.ASM

         name    protol
         title   'PROTOL.ASM --- template line filter'
 ;
 ; PROTOL.ASM:  a template for a line-oriented filter.
 ;
 ; Ray Duncan, June 1987
 ;

 stdin   equ     0               ; standard input
 stdout  equ     1               ; standard output
 stderr  equ     2               ; standard error

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII linefeed


 DGROUP  group   _DATA,STACK     ; 'automatic data group'


 _TEXT   segment byte public 'CODE'

         assume  cs:_TEXT,ds:DGROUP,es:DGROUP,ss:STACK

 main    proc    far             ; entry point from MS-DOS

         mov     ax,DGROUP       ; set DS = ES = our data segment
         mov     ds,ax
         mov     es,ax

 main1:                          ; read a line from standard input
         mov     dx,offset DGROUP:input  ; address to place data
         mov     cx,256          ; max length to read = 256
         mov     bx,stdin        ; handle for standard input
         mov     ah,3fh          ; function 3FH = read from file or device
         int     21h             ; transfer to MS-DOS
         jc      main3           ; if error, terminate
         or      ax,ax           ; any characters read?
         jz      main2           ; end of file, terminate program

         call    translt         ; translate line if necessary
         or      ax,ax           ; anything to output after translation?
         jz      main1           ; no, get next line

                                 ; now write line to standard output
         mov     dx,offset DGROUP:output ; address of data
         mov     cx,ax           ; length to write
         mov     bx,stdout       ; handle for standard output
         mov     ah,40h          ; function 40H = write to file or device
         int     21h             ; transfer to MS-DOS
         jc      main3           ; if error, terminate
         cmp     ax,cx           ; was entire line written?
         jne     main3           ; disk full, terminate program
         jmp     main1           ; go process another line

 main2:  mov     ax,4c00h        ; end of file reached, terminate
         int     21h             ; program with return code = 0

 main3:  mov     ax,4c01h        ; error or disk full, terminate
         int     21h             ; program with return code = 1

 main    endp                    ; end of main procedure

 ;
 ; Perform any necessary translation on line stored in
 ; 'input' buffer, leaving result in 'output' buffer.
 ;
 ; Call with:    AX = length of data in 'input' buffer.
 ;
 ; Return:       AX = length to write to standard output.
 ;
 ; Action of template routine is just to copy the line.
 ;
 translt proc    near

                                 ; just copy line from input to output
         mov     si,offset DGROUP:input
         mov     di,offset DGROUP:output
         mov     cx,ax
         rep movsb
         ret                     ; return length in AX unchanged

 translt endp


 _TEXT   ends


 _DATA   segment word public 'DATA'

 input   db      256 dup (?)     ; storage for input line
 output  db      256 dup (?)     ; storage for output line

 _DATA   ends


 STACK   segment para stack 'STACK'

         dw      64 dup (?)

 STACK   ends


         end     main            ; defines program entry point

 \SAMPCODE\DOS_ENCY\15
 \SAMPCODE\DOS_ENCY\15\TEMPLATE.ASM

         name    template
         title   'TEMPLATE --- installable driver template'

 ;
 ; TEMPLATE.ASM:  A program skeleton for an installable
 ;                device driver (MS-DOS 2.0 or later)
 ;
 ; The driver command-code routines are stubs only and have
 ; no effect but to return a nonerror "Done" status.
 ;
 ; Ray Duncan, July 1987
 ;

 _TEXT   segment byte public 'CODE'

         assume  cs:_TEXT,ds:_TEXT,es:NOTHING

         org     0

 MaxCmd  equ     24              ; maximum allowed command code
                                 ; 12 for MS-DOS 2.x
                                 ; 16 for MS-DOS 3.0-3.1
                                 ; 24 for MS-DOS 3.2-3.3

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII linefeed
 eom     equ     '$'             ; end-of-message signal


 Header:                         ; device driver header
         dd      -1              ; link to next device driver
         dw      c840h           ; device attribute word
         dw      Strat           ; "Strategy" routine entry point
         dw      Intr            ; "Interrupt" routine entry point
         db      'TEMPLATE'      ; logical device name


 RHPtr   dd      ?               ; pointer to request header, passed
                                 ; by MS-DOS kernel to Strategy routine


 Dispatch:                       ; Interrupt routine command code
                                 ; dispatch table
         dw      Init            ; 0  = initialize driver
         dw      MediaChk        ; 1  = media check on block device
         dw      BuildBPB        ; 2  = build BIOS parameter block
         dw      IoctlRd         ; 3  = I/O control read
         dw      Read            ; 4  = read (input) from device
         dw      NdRead          ; 5  = nondestructive read
         dw      InpStat         ; 6  = return current input status
         dw      InpFlush        ; 7  = flush device input buffers
         dw      Write           ; 8  = write (output) to device
         dw      WriteVfy        ; 9  = write with verify
         dw      OutStat         ; 10 = return current output status
         dw      OutFlush        ; 11 = flush output buffers
         dw      IoctlWt         ; 12 = I/O control write
         dw      DevOpen         ; 13 = device open       (MS-DOS 3.0+)
         dw      DevClose        ; 14 = device close      (MS-DOS 3.0+)
         dw      RemMedia        ; 15 = removable media   (MS-DOS 3.0+)
         dw      OutBusy         ; 16 = output until busy (MS-DOS 3.0+)
         dw      Error           ; 17 = not used
         dw      Error           ; 18 = not used
         dw      GenIOCTL        ; 19 = generic IOCTL     (MS-DOS 3.2+)
         dw      Error           ; 20 = not used
         dw      Error           ; 21 = not used
         dw      Error           ; 22 = not used
         dw      GetLogDev       ; 23 = get logical device (MS-DOS 3.2+)
         dw      SetLogDev       ; 24 = set logical device (MS-DOS 3.2+)


 Strat   proc    far             ; device driver Strategy routine,
                                 ; called by MS-DOS kernel with
                                 ; ES:BX = address of request header

                                 ; save pointer to request header
         mov     word ptr cs:[RHPtr],bx
         mov     word ptr cs:[RHPtr+2],es

         ret                     ; back to MS-DOS kernel

 Strat   endp


 Intr    proc    far             ; device driver Interrupt routine,
                                 ; called by MS-DOS kernel immediately
                                 ; after call to Strategy routine

         push    ax              ; save general registers
         push    bx
         push    cx
         push    dx
         push    ds
         push    es
         push    di
         push    si
         push    bp

         push    cs              ; make local data addressable
         pop     ds              ; by setting DS = CS

         les     di,[RHPtr]      ; let ES:DI = request header

                                 ; get BX = command code
         mov     bl,es:[di+2]
         xor     bh,bh
         cmp     bx,MaxCmd       ; make sure it's valid
         jle     Intr1           ; jump, function code is ok
         call    Error           ; set error bit, "Unknown Command" code
         jmp     Intr2

 Intr1:  shl     bx,1            ; form index to dispatch table
                                 ; and branch to command-code routine
         call    word ptr [bx+Dispatch]

         les     di,[RHPtr]      ; ES:DI = address of request header

 Intr2:  or      ax,0100h        ; merge Done bit into status and
         mov     es:[di+3],ax    ; store status into request header

         pop     bp              ; restore general registers
         pop     si
         pop     di
         pop     es
         pop     ds
         pop     dx
         pop     cx
         pop     bx
         pop     ax
         ret                     ; return to MS-DOS kernel


 ; Command-code routines are called by the Interrupt routine
 ; via the dispatch table with ES:DI pointing to the request
 ; header. Each routine should return AX = 00H if function was
 ; completed successfully or AX = 8000H + error code if
 ; function failed.


 MediaChk proc   near            ; function 1 = Media Check

         xor     ax,ax
         ret

 MediaChk endp


 BuildBPB proc   near            ; function 2 = Build BPB

         xor     ax,ax
         ret

 BuildBPB endp


 IoctlRd proc    near            ; function 3 = I/O Control Read

         xor     ax,ax
         ret

 IoctlRd endp


 Read    proc    near            ; function 4 = Read (Input)

         xor     ax,ax
         ret

 Read    endp


 NdRead  proc    near            ; function 5 = Nondestructive Read

         xor     ax,ax
         ret

 NdRead  endp


 InpStat proc    near            ; function 6 = Input Status

         xor     ax,ax
         ret

 InpStat endp


 InpFlush proc   near            ; function 7 = Flush Input Buffers

         xor     ax,ax
         ret

 InpFlush endp


 Write   proc    near            ; function 8 = Write (Output)

         xor     ax,ax
         ret

 Write   endp


 WriteVfy proc   near            ; function 9 = Write with Verify

         xor     ax,ax
         ret

 WriteVfy endp


 OutStat proc    near            ; function 10 = Output Status

         xor     ax,ax
         ret

 OutStat endp


 OutFlush proc   near            ; function 11 = Flush Output Buffers

         xor     ax,ax
         ret

 OutFlush endp


 IoctlWt proc    near            ; function 12 = I/O Control Write

         xor     ax,ax
         ret

 IoctlWt endp


 DevOpen proc    near            ; function 13 = Device Open

         xor     ax,ax
         ret

 DevOpen endp


 DevClose proc   near            ; function 14 = Device Close

         xor     ax,ax
         ret

 DevClose endp


 RemMedia proc   near            ; function 15 = Removable Media

         xor     ax,ax
         ret

 RemMedia endp


 OutBusy proc    near            ; function 16 = Output Until Busy

         xor     ax,ax
         ret

 OutBusy endp


 GenIOCTL proc   near            ; function 19 = Generic IOCTL

         xor     ax,ax
         ret

 GenIOCTL endp


 GetLogDev proc  near            ; function 23 = Get Logical Device

         xor     ax,ax
         ret

 GetLogDev endp


 SetLogDev proc  near            ; function 24 = Set Logical Device

         xor     ax,ax
         ret

 SetLogDev endp


 Error   proc    near            ; bad command code in request header

         mov     ax,8003h        ; error bit + "Unknown Command" code
         ret

 Error   endp


 Init    proc    near            ; function 0 = initialize driver

         push    es              ; save address of request header
         push    di

         mov     ah,9            ; display driver sign-on message
         mov     dx,offset Ident
         int     21h

         pop     di              ; restore request header address
         pop     es

                                 ; set address of free memory
                                 ; above driver (break address)
         mov     word ptr es:[di+14],offset Init
         mov     word ptr es:[di+16],cs

         xor     ax,ax           ; return status
         ret

 Init    endp

 Ident   db      cr,lf,lf
         db      'TEMPLATE Example Device Driver'
         db      cr,lf,eom

 Intr    endp

 _TEXT   ends

         end

 \SAMPCODE\DOS_ENCY\15\TINYDISK.ASM

         name    tinydisk
         title   TINYDISK example block-device driver

 ; TINYDISK.ASM --- 64 KB RAMdisk
 ;
 ; Ray Duncan, July 1987
 ; Example of a simple installable block-device driver.

 _TEXT   segment public 'CODE'

         assume  cs:_TEXT,ds:_TEXT,es:_TEXT

         org     0

 MaxCmd  equ     12              ; max driver command code
                                 ; (no MS-DOS 3.x functions)

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII linefeed
 blank   equ     020h            ; ASCII space code
 eom     equ     '$'             ; end-of-message signal

 Secsize equ     512             ; bytes/sector, IBM-compatible media

                                 ; device-driver header
 Header  dd      -1              ; link to next driver in chain
         dw      0               ; device attribute word
         dw      Strat           ; "Strategy" routine entry point
         dw      Intr            ; "Interrupt" routine entry point
         db      1               ; number of units, this device
         db      7 dup (0)       ; reserved area (block-device drivers)

 RHPtr   dd      ?               ; segment:offset of request header

 Secseg  dw      ?               ; segment base of sector storage

 Xfrsec  dw      0               ; current sector for transfer
 Xfrcnt  dw      0               ; sectors successfully transferred
 Xfrreq  dw      0               ; number of sectors requested
 Xfraddr dd      0               ; working address for transfer

 Array   dw      BPB             ; array of pointers to BPB
                                 ; for each supported unit


 Bootrec equ     $

         jmp     $               ; phony JMP at start of
         nop                     ; boot sector; this field
                                 ; must be 3 bytes

         db      'MS   2.0'      ; OEM identity field

                                 ; BIOS Parameter Block (BPB)
 BPB     dw      Secsize         ; 00H - bytes per sector
         db      1               ; 02H - sectors per cluster
         dw      1               ; 03H - reserved sectors
         db      1               ; 05H - number of FATs
         dw      32              ; 06H - root directory entries
         dw      128             ; 08H - sectors = 64 KB/secsize
         db      0f8h            ; 0AH - media descriptor
         dw      1               ; 0BH - sectors per FAT

 Bootrec_len equ $-Bootrec


 Strat   proc    far             ; RAMdisk strategy routine

                                 ; save address of request header
         mov     word ptr cs:RHPtr,bx
         mov     word ptr cs:[RHPtr+2],es
         ret                     ; back to MS-DOS kernel

 Strat   endp


 Intr    proc    far             ; RAMdisk interrupt routine

         push    ax              ; save general registers
         push    bx
         push    cx
         push    dx
         push    ds
         push    es
         push    di
         push    si
         push    bp

         mov     ax,cs           ; make local data addressable
         mov     ds,ax

         les     di,[RHPtr]      ; ES:DI = request header

         mov     bl,es:[di+2]    ; get command code
         xor     bh,bh
         cmp     bx,MaxCmd       ; make sure it's valid
         jle     Intr1           ; jump, function code is ok
         mov     ax,8003h        ; set Error bit and
         jmp     Intr3           ; "Unknown Command" error code

 Intr1:  shl     bx,1            ; form index to dispatch table and
                                 ; branch to command-code routine
         call    word ptr [bx+Dispatch]
                                 ; should return AX = status

         les     di,[RHPtr]      ; restore ES:DI = request header

 Intr3:  or      ax,0100h        ; merge Done bit into status and store
         mov     es:[di+3],ax    ; status into request header

 Intr4:  pop     bp              ; restore general registers
         pop     si
         pop     di
         pop     es
         pop     ds
         pop     dx
         pop     cx
         pop     bx
         pop     ax
         ret                     ; return to MS-DOS kernel

 Intr    endp


 Dispatch:                       ; command-code dispatch table
                                 ; all command-code routines are
                                 ; entered with ES:DI pointing
                                 ; to request header and return
                                 ; the operation status in AX
         dw      Init            ;  0 = initialize driver
         dw      MediaChk        ;  1 = media check on block device
         dw      BuildBPB        ;  2 = build BIOS parameter block
         dw      Dummy           ;  3 = I/O control read
         dw      Read            ;  4 = read (input) from device
         dw      Dummy           ;  5 = nondestructive read
         dw      Dummy           ;  6 = return current input status
         dw      Dummy           ;  7 = flush device input buffers
         dw      Write           ;  8 = write (output) to device
         dw      Write           ;  9 = write with verify
         dw      Dummy           ; 10 = return current output status
         dw      Dummy           ; 11 = flush output buffers
         dw      Dummy           ; 12 = I/O control write


 MediaChk proc  near             ; command code 1 = Media Check

                                 ; return "not changed" code
         mov     byte ptr es:[di+0eh],1
         xor     ax,ax           ; and success status
         ret

 MediaChk endp


 BuildBPB proc  near             ; command code 2 = Build BPB

                                 ; put BPB address in request header
         mov     word ptr es:[di+12h],offset BPB
         mov     word ptr es:[di+14h],cs
         xor     ax,ax           ; return success status
         ret

 BuildBPB endp


 Read    proc    near            ; command code 4 = Read (Input)

         call    Setup           ; set up transfer variables

 Read1:  mov     ax,Xfrcnt       ; done with all sectors yet?
         cmp     ax,Xfrreq
         je      Read2           ; jump if transfer completed
         mov     ax,Xfrsec       ; get next sector number
         call    Mapsec          ; and map it
         mov     ax,es
         mov     si,di
         les     di,Xfraddr      ; ES:DI = requester's buffer
         mov     ds,ax           ; DS:SI = RAMdisk address
         mov     cx,Secsize      ; transfer logical sector from
         cld                     ; RAMdisk to requestor
         rep movsb
         push    cs              ; restore local addressing
         pop     ds
         inc     Xfrsec          ; advance sector number
                                 ; advance transfer address
         add     word ptr Xfraddr,Secsize
         inc     Xfrcnt          ; count sectors transferred
         jmp     Read1

 Read2:                          ; all sectors transferred
         xor     ax,ax           ; return success status
         les     di,RHPtr        ; put actual transfer count
         mov     bx,Xfrcnt       ; into request header
         mov     es:[di+12h],bx
         ret

 Read    endp


 Write   proc    near            ; command code 8 = Write (Output)
                                 ; command code 9 = Write with Verify

         call    Setup           ; set up transfer variables

 Write1: mov     ax,Xfrcnt       ; done with all sectors yet?
         cmp     ax,Xfrreq
         je      Write2          ; jump if transfer completed

         mov     ax,Xfrsec       ; get next sector number
         call    Mapsec          ; and map it
         lds     si,Xfraddr
         mov     cx,Secsize      ; transfer logical sector from
         cld                     ; requester to RAMdisk
         rep movsb
         push    cs              ; restore local addressing
         pop     ds
         inc     Xfrsec          ; advance sector number
                                 ; advance transfer address
         add     word ptr Xfraddr,Secsize
         inc     Xfrcnt          ; count sectors transferred
         jmp     Write1

 Write2:                         ; all sectors transferred
         xor     ax,ax           ; return success status
         les     di,RHPtr        ; put actual transfer count
         mov     bx,Xfrcnt       ; into request header
         mov     es:[di+12h],bx
         ret

 Write   endp


 Dummy   proc    near            ; called for unsupported functions

         xor     ax,ax           ; return success flag for all
         ret

 Dummy   endp


 Mapsec  proc    near            ; map sector number to memory address
                                 ; call with AX = logical sector no.
                                 ; return ES:DI = memory address

         mov     di,Secsize/16   ; paragraphs per sector
         mul     di              ; * logical sector number
         add     ax,Secseg       ; + segment base of sector storage
         mov     es,ax
         xor     di,di           ; now ES:DI points to sector
         ret

 Mapsec  endp


 Setup   proc    near            ; set up for read or write
                                 ; call ES:DI = request header
                                 ; extracts address, start, count

         push    es              ; save request header address
         push    di
         mov     ax,es:[di+14h]  ; starting sector number
         mov     Xfrsec,ax
         mov     ax,es:[di+12h]  ; sectors requested
         mov     Xfrreq,ax
         les     di,es:[di+0eh]  ; requester's buffer address
         mov     word ptr Xfraddr,di
         mov     word ptr Xfraddr+2,es
         mov     Xfrcnt,0        ; initialize sectors transferred count
         pop     di              ; restore request header address
         pop     es
         ret

 Setup   endp


 Init    proc    near            ; command code 0 = Initialize driver
                                 ; on entry ES:DI = request header

         mov     ax,cs           ; calculate segment base for sector
         add     ax,Driver_len   ; storage and save it
         mov     Secseg,ax
         add     ax,1000h        ; add 1000H paras (64 KB) and
         mov     es:[di+10h],ax  ; set address of free memory
         mov     word ptr es:[di+0eh],0

         call    Format          ; format the RAMdisk

         call    Signon          ; display driver identification

         les     di,cs:RHPtr     ; restore ES:DI = request header
                                 ; set logical units = 1
         mov     byte ptr es:[di+0dh],1
                                 ; set address of BPB array
         mov     word ptr es:[di+12h],offset Array
         mov     word ptr es:[di+14h],cs

         xor     ax,ax           ; return success status
         ret

 Init    endp


 Format  proc    near            ; format the RAMdisk area

         mov     es,Secseg       ; first zero out RAMdisk
         xor     di,di
         mov     cx,8000h        ; 32 K words = 64 KB
         xor     ax,ax
         cld
         rep stosw

         mov     ax,0            ; get address of logical
         call    Mapsec          ; sector zero
         mov     si,offset Bootrec
         mov     cx,Bootrec_len
         rep movsb               ; and copy boot record to it

         mov     ax,word ptr BPB+3
         call    Mapsec          ; get address of 1st FAT sector
         mov     al,byte ptr BPB+0ah
         mov     es:[di],al      ; put media ID byte into it
         mov     word ptr es:[di+1],-1

         mov     ax,word ptr BPB+3
         add     ax,word ptr BPB+0bh
         call    Mapsec          ; get address of 1st directory sector
         mov     si,offset Volname
         mov     cx,Volname_len
         rep movsb               ; copy volume label to it

         ret                     ; done with formatting

 Format  endp


 Signon  proc    near            ; driver identification message

         les     di,RHPtr        ; let ES:DI = request header
         mov     al,es:[di+22]   ; get drive code from header,
         add     al,'A'          ; convert it to ASCII, and
         mov     drive,al        ; store into sign-on message

         mov     ah,30h          ; get MS-DOS version
         int     21h
         cmp     al,2
         ja      Signon1         ; jump if version 3.0 or later
         mov     Ident1,eom      ; version 2.x, don't print drive

 Signon1:                        ; print sign-on message
         mov     ah,09H          ; Function 09H = print string
         mov     dx,offset Ident ; DS:DX = address of message
         int     21h             ; transfer to MS-DOS

         ret                     ; back to caller

 Signon  endp


 Ident   db      cr,lf,lf        ; driver sign-on message
         db      'TINYDISK 64 KB RAMdisk'
         db      cr,lf
 Ident1  db      'RAMdisk will be drive '
 Drive   db      'X:'
         db      cr,lf,eom


 Volname db      'DOSREF_DISK'   ; volume label for RAMdisk
         db      08h             ; attribute byte
         db      10 dup (0)      ; reserved area
         dw      0               ; time = 00:00
         dw      0f01h           ; date = August 1, 1987
         db      6 dup (0)       ; reserved area

 Volname_len equ $-volname

 Driver_len dw (($-header)/16)+1 ; driver size in paragraphs

 _TEXT   ends

         end

 \SAMPCODE\DOS_ENCY\17
 \SAMPCODE\DOS_ENCY\17\SAMPLE.C

 /* SAMPLE.C -- Demonstration Windows Program */

 #include <windows.h>
 #include "sample.h"

 long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;

 int PASCAL WinMain (hInstance, hPrevInstance, lpszCmdLine, nCmdShow)
      HANDLE      hInstance, hPrevInstance ;
      LPSTR       lpszCmdLine ;
      int         nCmdShow ;
      {
      WNDCLASS    wndclass ;
      HWND        hWnd ;
      MSG         msg ;
      static char szAppName [] = "Sample" ;

                /*---------------------------*/
                /* Register the Window Class */
                /*---------------------------*/

      if (!hPrevInstance)
           {
           wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
           wndclass.lpfnWndProc   = WndProc ;
           wndclass.cbClsExtra    = 0 ;
           wndclass.cbWndExtra    = 0 ;
           wndclass.hInstance     = hInstance ;
           wndclass.hIcon         = NULL ;
           wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
           wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
           wndclass.lpszMenuName  = szAppName ;
           wndclass.lpszClassName = szAppName ;

           RegisterClass (&wndclass) ;
           }

                /*----------------------------------*/
                /* Create the window and display it */
                /*----------------------------------*/

      hWnd = CreateWindow (szAppName, "Demonstration Windows Program",
                           WS_OVERLAPPEDWINDOW,
                           (int) CW_USEDEFAULT, 0,
                           (int) CW_USEDEFAULT, 0,
                           NULL, NULL, hInstance, NULL) ;

      ShowWindow (hWnd, nCmdShow) ;
      UpdateWindow (hWnd) ;

                 /*----------------------------------------------*/
                 /* Stay in message loop until a WM_QUIT message */
                 /*----------------------------------------------*/

      while (GetMessage (&msg, NULL, 0, 0))
           {
           TranslateMessage (&msg) ;
           DispatchMessage (&msg) ;
           }
      return msg.wParam ;
      }

 long FAR PASCAL WndProc (hWnd, iMessage, wParam, lParam)
      HWND         hWnd ;
      unsigned     iMessage ;
      WORD         wParam ;
      LONG         lParam ;
      {
      PAINTSTRUCT  ps ;
      HFONT        hFont ;
      HMENU        hMenu ;
      static short xClient, yClient, nCurrentFont = IDM_SCRIPT ;
      static BYTE  cFamily [] = { FF_SCRIPT, FF_MODERN, FF_ROMAN } ;
      static char  *szFace [] = { "Script",  "Modern",  "Roman"  } ;

      switch (iMessage)
           {

                     /*---------------------------------------------*/
                     /* WM_COMMAND message: Change checkmarked font */
                     /*---------------------------------------------*/

           case WM_COMMAND:
                hMenu = GetMenu (hWnd) ;
                CheckMenuItem (hMenu, nCurrentFont, MF_UNCHECKED) ;
                nCurrentFont = wParam ;
                CheckMenuItem (hMenu, nCurrentFont, MF_CHECKED) ;
                InvalidateRect (hWnd, NULL, TRUE) ;
                break ;

                     /*--------------------------------------------*/
                     /* WM_SIZE message: Save dimensions of window */
                     /*--------------------------------------------*/

           case WM_SIZE:
                xClient = LOWORD (lParam) ;
                yClient = HIWORD (lParam) ;
                break ;

                     /*-----------------------------------------------*/
                     /* WM_PAINT message: Display "Windows" in Script */
                     /*-----------------------------------------------*/

           case WM_PAINT:
                BeginPaint (hWnd, &ps) ;

                hFont = CreateFont (yClient, xClient / 8,
                                    0, 0, 400, 0, 0, 0, OEM_CHARSET,
                                    OUT_STROKE_PRECIS, OUT_STROKE_PRECIS,
                                    DRAFT_QUALITY, (BYTE) VARIABLE_PITCH |
                                    cFamily [nCurrentFont - IDM_SCRIPT],
                                    szFace  [nCurrentFont - IDM_SCRIPT]) ;

                hFont = SelectObject (ps.hdc, hFont) ;
                TextOut (ps.hdc, 0, 0, "Windows", 7) ;

                DeleteObject (SelectObject (ps.hdc, hFont)) ;
                EndPaint (hWnd, &ps) ;
                break ;

                     /*---------------------------------------*/
                     /* WM_DESTROY message: Post Quit message */
                     /*---------------------------------------*/

           case WM_DESTROY:
                PostQuitMessage (0) ;
                break ;

                     /*---------------------------------------*/
                     /* Other messages: Do default processing */
                     /*---------------------------------------*/

           default:
                return DefWindowProc (hWnd, iMessage, wParam, lParam) ;
           }
      return 0L ;
      }

 \SAMPCODE\DOS_ENCY\17\SAMPLE.H

 #define IDM_SCRIPT 1
 #define IDM_MODERN 2
 #define IDM_ROMAN  3

 \SAMPCODE\DOS_ENCY\17\SAMPLE.RC

 #include "sample.h"

 Sample MENU
      BEGIN
           POPUP "&Typeface"
                BEGIN
                     MENUITEM "&Script", IDM_SCRIPT, CHECKED
                     MENUITEM "&Modern", IDM_MODERN
                     MENUITEM "&Roman",  IDM_ROMAN
                END
      END

 \SAMPCODE\DOS_ENCY\17\SAMPLE.DEF

 NAME           SAMPLE
 DESCRIPTION    'Demonstration Windows Program'
 STUB           'WINSTUB.EXE'
 CODE           MOVABLE
 DATA           MOVABLE MULTIPLE
 HEAPSIZE       1024
 STACKSIZE      4096
 EXPORTS        WndProc

 \SAMPCODE\DOS_ENCY\17\SAMPLE.MAK

 sample.obj : sample.c sample.h
      cl -c -Gsw -W2 -Zdp sample.c

 sample.res : sample.rc sample.h
      rc -r sample.rc

 sample.exe : sample.obj sample.def sample.res
      link4 sample, /align:16, /map /line, slibw, sample
      rc sample.res
      mapsym sample

 \SAMPCODE\DOS_ENCY\18
 \SAMPCODE\DOS_ENCY\18\ASCTBL1.C

 /*************************************************************************
  *                                                                       *
  *  ASCTBL.C                                                             *
  *   This program generates an ASCII lookup table for all displayable    *
  *   ASCII and extended IBM PC codes, leaving blanks for nondisplayable  *
  *   codes.                                                              *
  *                                                                       *
  ************************************************************************/

 #include <ctype.h>
 #include <stdio.h>

 main()
 {
 int i, j, k;
        /* Print table title. */
        printf("\n\n\n                ASCII LOOKUP TABLE\n\n");

        /* Print column headers. */
        printf("    ");
        for (i = 0; i < 16; i++)
                printf("%X  ", i);
        fputchar("\n");

        /* Print each line of the table. */
        for ( i = 0, k = 0; i < 16; i++)
                {
                /* Print first hex digit of symbols on this line. */
                printf("%X   ", i);
                /* Print each of the 16 symbols for this line. */
                for (j = 0; j < 16; j++)
                        {
                        /* Filter nonprintable characters. */
                        if ((k >= 7 && k <= 13) || (k >= 28 && k <= 31))
                                printf("   ");
                        else
                                printf("%c  ", k);
                        k++;
                        }
        fputchar("\n");
        }
 }

 \SAMPCODE\DOS_ENCY\18\ASCTBL2.C

 /*************************************************************************
  *                                                                       *
  *  ASCTBL.C                                                             *
  *   This program generates an ASCII lookup table for all displayable    *
  *   ASCII and extended IBM PC codes, leaving blanks for nondisplayable  *
  *   codes.                                                              *
  *                                                                       *
  ************************************************************************/

 #define LINT_ARGS
 #include <ctype.h>
 #include <stdio.h>

 main()
 {
 int i, j, k;
         /* Print table title. */
         printf("\n\n\n                ASCII LOOKUP TABLE\n\n");

         /* Print column headers. */
         printf("    ");
         for (i = 0; i < 16; i++)
                 printf("%X  ", i);
         fputchar('\n');

         /* Print each line of the table. */
         for ( i = 0, k = 0; i < 16; i++)
                 {
                 /* Print first hex digit of symbols on this line. */
                 printf("%X   ", i);
                 /* Print each of the 16 symbols for this line. */
                 for (j = 0; j < 16; j++)
                         {
                         /* Filter nonprintable characters. */
                         if ((k >= 7 && k <= 13) || (k >= 28 && k <= 31))
                                 printf("   ");
                         else
                                 printf("%c  ", k);
                         k++;
                         }
                 fputchar('\n');
                 }
 }

 \SAMPCODE\DOS_ENCY\18\COMMSCMD.C

 /***********************************************************************
 *                                                                      *
 *  COMMSCMD                                                            *
 *                                                                      *
 *  This routine controls the COMMSCOP program that has been in-        *
 *  stalled as a resident routine.  The operation performed is de-      *
 *  termined by the command line.  The COMMSCMD program is invoked      *
 *  as follows:                                                         *
 *                                                                      *
 *                COMMSCMD [[cmd][ port]]                               *
 *                                                                      *
 *  where cmd is the command to be executed                             *
 *            STOP   -- stop trace                                      *
 *            START  -- flush trace buffer and start trace              *
 *            RESUME -- resume a stopped trace                          *
 *        port is the COMM port to be traced (1=COM1, 2=COM2, etc.)     *
 *                                                                      *
 *  If cmd is omitted, STOP is assumed.  If port is omitted, 1 is       *
 *  assumed.                                                            *
 *                                                                      *
 ***********************************************************************/

 #include <stdlib.h>
 #include <stdio.h>
 #include <dos.h>
 #define COMMCMD 0x60

 main(argc, argv)
 int argc;
 char *argv[];
 {
     int cmd, port, result;
     static char commands[3] [10] = {"STOPPED", "STARTED", "RESUMED"};
     union REGS inregs, outregs;

     cmd = 0;
     port = 0;

     if (argc > 1)
         {
         if (0 == stricmp(argv[1], "STOP"))
             cmd = 0;
         else if (0 == stricmp(argv[1], "START"))
             cmd = 1;
         else if (0 == stricmp(argv[1], "RESUME"))
             cmd = 2;
         }

     if (argc == 3)
         {
         port = atoi(argv[2]);
         if (port > 0)
             port = port - 1;
         }

     inregs.h.ah = cmd;
     inregs.x.dx = port;
     result = int86(COMMCMD, &inregs, &outregs);


     printf("\nCommunications tracing %s for port COM%1d:\n",
             commands[cmd], port + 1);
 }

 \SAMPCODE\DOS_ENCY\18\UPERCAS1.C

 /************************************************************************
  *                                                                      *
  * UPPERCAS.C                                                           *
  *    This routine converts a fixed string to uppercase and prints it.  *
  *                                                                      *
  ************************************************************************/

 #include <ctype.h>
 #include <string.h>
 #include <stdio.h>

 main(argc,argv)

 int argc;
 char *argv[];

 {
 char    *cp,c;

         cp = "a string\n";

         /*  Convert *cp to uppercase and write to standard output  */

         while (*cp != '\0')
                 {
                 c = toupper(*cp++);
                 putchar(c);
                 }

 }

 \SAMPCODE\DOS_ENCY\18\UPERCAS2.C

 /************************************************************************
  *                                                                      *
  * UPPERCAS.C                                                           *
  *    This routine converts a fixed string to uppercase and prints it.  *
  *                                                                      *
  ***********************************************************************/

 #include <ctype.h>
 #undef toupper
 #undef tolower
 #include <string.h>
 #include <stdio.h>

 main(argc,argv)

 int argc;
 char *argv[];

 {
 char    *cp,c;

         cp = "a string\n";

         /*  Convert *cp to uppercase and write to standard output  */

         while (*cp != '\0')
                 {
                 c = toupper(*cp++);
                 putchar(c);
                 }

 }

 \SAMPCODE\DOS_ENCY\18\BADSCOP.ASM

         TITLE   BADSCOP -- BAD VERSION OF COMMUNICATIONS TRACE UTILITY
 ; ***********************************************************************
 ; *                                                                     *
 ; *  BADSCOP --                                                         *
 ; *     THIS PROGRAM MONITORS THE ACTIVITY ON A SPECIFIED COMM PORT     *
 ; *     AND PLACES A COPY OF ALL COMM ACTIVITY IN A RAM BUFFER.  EACH   *
 ; *     ENTRY IN THE BUFFER IS TAGGED TO INDICATE WHETHER THE BYTE      *
 ; *     WAS SENT BY OR RECEIVED BY THE SYSTEM.                          *
 ; *                                                                     *
 ; *     BADSCOP IS INSTALLED BY ENTERING                                *
 ; *                                                                     *
 ; *                     BADSCOP                                         *
 ; *                                                                     *
 ; *  THIS WILL INSTALL BADSCOP AND SET UP A 64K BUFFER TO BE USED       *
 ; *  FOR DATA LOGGING.  REMEMBER THAT 2 BYTES ARE REQUIRED FOR          *
 ; *  EACH COMM BYTE, SO THE BUFFER IS ONLY 32K EVENTS LONG, OR ABOUT    *
 ; *  30 SECONDS OF CONTINUOUS 9600 BAUD DATA.  IN THE REAL WORLD,       *
 ; *  ASYNC DATA IS RARELY CONTINUOUS, SO THE BUFFER WILL PROBABLY       *
 ; *  HOLD MORE THAN 30 SECONDS WORTH OF DATA.                           *
 ; *                                                                     *
 ; *  WHEN INSTALLED, BADSCOP INTERCEPTS ALL INT 14H CALLS.  IF THE      *
 ; *  PROGRAM HAS BEEN ACTIVATED AND THE INT IS EITHER SEND OR RE-       *
 ; *  CEIVE DATA, A COPY OF THE DATA BYTE, PROPERLY TAGGED, IS PLACED    *
 ; *  IN THE BUFFER.  IN ANY CASE, DATA IS PASSED ON TO THE REAL         *
 ; *  INT 14H HANDLER.                                                   *
 ; *                                                                     *
 ; *  BADSCOP IS INVOKED BY ISSUING AN INT 60H CALL.  THE INT HAS        *
 ; *  THE FOLLOWING CALLING SEQUENCE:                                    *
 ; *                                                                     *
 ; *       AH -- COMMAND                                                 *
 ; *             0 -- STOP TRACING, PLACE STOP MARK IN BUFFER            *
 ; *             1 -- FLUSH BUFFER AND START TRACE                       *
 ; *             2 -- RESUME TRACE                                       *
 ; *             3 -- RETURN COMM BUFFER ADDRESSES                       *
 ; *       DX -- COMM PORT (ONLY USED WITH AH = 1 or 2)                  *
 ; *             0 -- COM1                                               *
 ; *             1 -- COM2                                               *
 ; *                                                                     *
 ; *  THE FOLLOWING DATA IS RETURNED IN REPONSE TO AH = 3:               *
 ; *                                                                     *
 ; *       CX -- BUFFER COUNT IN BYTES                                   *
 ; *       DX -- SEGMENT ADDRESS OF THE START OF THE BUFFER              *
 ; *       BX -- OFFSET ADDRESS OF THE START OF THE BUFFER               *
 ; *                                                                     *
 ; *  THE COMM BUFFER IS FILLED WITH 2-BYTE DATA ENTRIES OF THE          *
 ; *  FOLLOWING FORM:                                                    *
 ; *                                                                     *
 ; *       BYTE 0 -- CONTROL                                             *
 ; *            BIT 0 -- ON FOR RECEIVED DATA, OFF FOR TRANS.            *
 ; *            BIT 7 -- STOP MARK -- INDICATES COLLECTION WAS           *
 ; *                  INTERRUPTED AND RESUMED.                           *
 ; *       BYTE 1 -- 8-BIT DATA                                          *
 ; *                                                                     *
 ; ***********************************************************************

         PUBLIC INITIALIZE,CONTROL,VECTOR_INIT,COMMSCOPE
         PUBLIC OLD_COMM_INT,COUNT,STATUS,PORT,BUFPNTR

 CSEG    SEGMENT
         ASSUME  CS:CSEG,DS:CSEG
         ORG     100H                    ;TO MAKE A COM FILE

 INITIALIZE:
         JMP     VECTOR_INIT             ;JUMP TO THE INITIALIZATION
                                         ; ROUTINE WHICH, TO SAVE SPACE,
                                         ; IS IN THE COMM BUFFER

 ;
 ;  SYSTEM VARIABLES
 ;
 OLD_COMM_INT    DD      ?               ;ADDRESS OF REAL COMM INT
 COUNT           DW      0               ;BUFFER COUNT
 COMMSCOPE_INT   EQU     60H             ;COMMSCOPE CONTROL INT
 STATUS          DB      0               ;PROCESSING STATUS
                                         ; 0 -- OFF
                                         ; 1 -- ON
 PORT            DB      0               ;COMM PORT BEING TRACED
 BUFPNTR         DW      VECTOR_INIT     ;NEXT BUFFER LOCATION

         SUBTTL  DATA INTERRUPT HANDLER
 PAGE
 ; ***********************************************************************
 ; *                                                                     *
 ; *  COMMSCOPE                                                          *
 ; *  THIS PROCEDURE INTERCEPTS ALL INT 14H CALLS AND LOGS THE DATA      *
 ; *  IF APPROPRIATE.                                                    *
 ; *                                                                     *
 ; ***********************************************************************
 COMMSCOPE       PROC    NEAR

         TEST    CS:STATUS,1             ;ARE WE ON?
         JZ      OLD_JUMP                ; NO, SIMPLY JUMP TO OLD HANDLER

         CMP     AH,00H                  ;SKIP SETUP CALLS
         JE      OLD_JUMP                ; .

         CMP     AH,03H                  ;SKIP STATUS REQUESTS
         JAE     OLD_JUMP                ; .

         CMP     AH,02H                  ;IS THIS A READ REQUEST?
         JE      GET_READ                ; YES, GO PROCESS

 ;
 ;  DATA WRITE REQUEST -- SAVE IF APPROPRIATE
 ;
         CMP     DL,CS:PORT              ;IS WRITE FOR PORT BEING TRACED?
         JNE     OLD_JUMP                ; NO, JUST PASS IT THROUGH

         PUSH    DS                      ;SAVE CALLER'S REGISTERS
         PUSH    BX                      ; .
         PUSH    CS                      ;SET UP DS FOR OUR PROGRAM
         POP     DS                      ; .
         MOV     BX,BUFPNTR              ;GET ADDRESS OF NEXT BUFFER LOCATION
         MOV     [BX],BYTE PTR 0         ;MARK AS TRANSMITTED BYTE
         MOV     [BX+1],AL               ;SAVE DATA IN BUFFER
         INC     COUNT                   ;INCREMENT BUFFER BYTE COUNT
         INC     COUNT                   ; .
         INC     BX                      ;POINT TO NEXT LOCATION
         INC     BX                      ; .
         MOV     BUFPNTR,BX              ;SAVE NEW POINTER
         JNZ     WRITE_DONE              ;ZERO INDICATES BUFFER HAS WRAPPED

         MOV     STATUS,0                ;TURN COLLECTION OFF -- BUFFER FULL
 WRITE_DONE:
         POP     BX                      ;RESTORE CALLER'S REGISTERS
         POP     DS                      ; .
         JMP     OLD_JUMP                ;PASS REQUEST ON TO BIOS ROUTINE
 ;
 ;  PROCESS A READ DATA REQUEST AND WRITE TO BUFFER IF APPROPRIATE
 ;
 GET_READ:
         CMP     DL,CS:PORT              ;IS READ FOR PORT BEING TRACED?
         JNE     OLD_JUMP                ; NO, JUST PASS IT THROUGH

         PUSH    DS                      ;SAVE CALLER'S REGISTERS
         PUSH    BX                      ; .
         PUSH    CS                      ;SET UP DS FOR OUR PROGRAM
         POP     DS                      ; .

         PUSHF                           ;FAKE INT 14H CALL
         CLI                             ; .
         CALL    OLD_COMM_INT            ;PASS REQUEST ON TO BIOS
         TEST    AH,80H                  ;VALID READ?
         JNZ     READ_DONE               ; NO, SKIP BUFFER UPDATE

         MOV     BX,BUFPNTR              ;GET ADDRESS OF NEXT BUFFER LOCATION
         MOV     [BX],BYTE PTR 1         ;MARK AS RECEIVED BYTE
         MOV     [BX+1],AL               ;SAVE DATA IN BUFFER
         INC     COUNT                   ;INCREMENT BUFFER BYTE COUNT
         INC     COUNT                   ; .
         INC     BX                      ;POINT TO NEXT LOCATION
         INC     BX                      ; .
         MOV     BUFPNTR,BX              ;SAVE NEW POINTER
         JNZ     READ_DONE               ;ZERO INDICATES BUFFER HAS WRAPPED

         MOV     STATUS,0                ;TURN COLLECTION OFF -- BUFFER FULL
 READ_DONE:
         POP     BX                      ;RESTORE CALLER'S REGISTERS
         POP     DS                      ; .
         IRET

 ;
 ;  JUMP TO COMM BIOS ROUTINE
 ;
 OLD_JUMP:
         JMP     OLD_COMM_INT

 COMMSCOPE ENDP

         SUBTTL  CONTROL INTERRUPT HANDLER
 PAGE
 ; ***********************************************************************
 ; *                                                                     *
 ; *  CONTROL                                                            *
 ; *     THIS ROUTINE PROCESSES CONTROL REQUESTS.                        *
 ; *                                                                     *
 ; ***********************************************************************

 CONTROL PROC    NEAR
         CMP     AH,00H                  ;STOP REQUEST?
         JNE     CNTL_START              ; NO, CHECK START
         PUSH    DS                      ;SAVE REGISTERS
         PUSH    BX                      ; .
         PUSH    CS                      ;SET DS FOR OUR ROUTINE
         POP     DS
         MOV     STATUS,0                ;TURN PROCESSING OFF
         MOV     BX,BUFPNTR              ;PLACE STOP MARK IN BUFFER
         MOV     [BX],BYTE PTR 80H       ; .
         MOV     [BX+1],BYTE PTR 0FFH    ; .
         INC     COUNT                   ;INCREMENT COUNT
         INC     COUNT                   ; .
         POP     BX                      ;RESTORE REGISTERS
         POP     DS                      ; .
         JMP     CONTROL_DONE

 CNTL_START:
         CMP     AH,01H                  ;START REQUEST?
         JNE     CNTL_RESUME             ; NO, CHECK RESUME
         MOV     CS:PORT,DL              ;SAVE PORT TO TRACE
         MOV     CS:BUFPNTR,OFFSET VECTOR_INIT   ;RESET BUFFER TO START
         MOV     CS:COUNT,0              ;ZERO COUNT
         MOV     CS:STATUS,1             ;START LOGGING
         JMP     CONTROL_DONE

 CNTL_RESUME:
         CMP     AH,02H                  ;RESUME REQUEST?
         JNE     CNTL_STATUS             ; NO, CHECK STATUS
         CMP     CS:BUFPNTR,0            ;END OF BUFFER CONDITION?
         JE      CONTROL_DONE            ; YES, DO NOTHING
         MOV     CS:PORT,DL              ;SAVE PORT TO TRACE
         MOV     CS:STATUS,1             ;START LOGGING
         JMP     CONTROL_DONE

 CNTL_STATUS:
         CMP     AH,03H                  ;RETURN STATUS REQUEST?
         JNE     CONTROL_DONE            ; NO, ERROR -- DO NOTHING
         MOV     CX,CS:COUNT             ;RETURN COUNT
         PUSH    CS                      ;RETURN SEGMENT ADDR OF BUFFER
         POP     DX                      ; .
         MOV     BX,OFFSET VECTOR_INIT   ;RETURN OFFSET ADDR OF BUFFER

 CONTROL_DONE:
         IRET

 CONTROL ENDP


         SUBTTL  INITIALIZE INTERRUPT VECTORS
 PAGE
 ; ***********************************************************************
 ; *                                                                     *
 ; *  VECTOR_INIT                                                        *
 ; *  THIS PROCEDURE INITIALIZES THE INTERRUPT VECTORS AND THEN          *
 ; *  EXITS VIA THE MS-DOS TERMINATE-AND-STAY-RESIDENT FUNCTION.         *
 ; *  A BUFFER OF 64K IS RETAINED.  THE FIRST AVAILABLE BYTE             *
 ; *  IN THE BUFFER IS THE OFFSET OF VECTOR_INIT.                        *
 ; *                                                                     *
 ; ***********************************************************************

         EVEN                            ;ASSURE BUFFER ON EVEN BOUNDARY
 VECTOR_INIT     PROC    NEAR
 ;
 ;  GET ADDRESS OF COMM VECTOR (INT 14H)
 ;
         MOV     AH,35H
         MOV     AL,14H
         INT     21H
 ;
 ;  SAVE OLD COMM INT ADDRESS
 ;
         MOV     WORD PTR OLD_COMM_INT,BX
         MOV     AX,ES
         MOV     WORD PTR OLD_COMM_INT[2],AX
 ;
 ;  SET UP COMM INT TO POINT TO OUR ROUTINE
 ;
         MOV     DX,OFFSET COMMSCOPE
         MOV     AH,25H
         MOV     AL,14H
         INT     21H
 ;
 ;  INSTALL CONTROL ROUTINE INT
 ;
         MOV     DX,OFFSET CONTROL
         MOV     AH,25H
         MOV     AL,COMMSCOPE_INT
         INT     21H
 ;
 ;  SET LENGTH TO 64K, EXIT AND STAY RESIDENT
 ;
         MOV     AX,3100H                ;TERM AND STAY RES COMMAND
         MOV     DX,1000H                ;64K RESERVED
         INT     21H                     ;DONE
 VECTOR_INIT ENDP

 CSEG    ENDS
         END     INITIALIZE

 \SAMPCODE\DOS_ENCY\18\COMMSCOP.ASM

         TITLE   COMMSCOP -- COMMUNICATIONS TRACE UTILITY
 ; ***********************************************************************
 ; *                                                                     *
 ; *  COMMSCOPE --                                                       *
 ; *     THIS PROGRAM MONITORS THE ACTIVITY ON A SPECIFIED COMM PORT     *
 ; *     AND PLACES A COPY OF ALL COMM ACTIVITY IN A RAM BUFFER.  EACH   *
 ; *     ENTRY IN THE BUFFER IS TAGGED TO INDICATE WHETHER THE BYTE      *
 ; *     WAS SENT BY OR RECEIVED BY THE SYSTEM.                          *
 ; *                                                                     *
 ; *     COMMSCOPE IS INSTALLED BY ENTERING                              *
 ; *                                                                     *
 ; *                      COMMSCOP                                       *
 ; *                                                                     *
 ; *     THIS WILL INSTALL COMMSCOP AND SET UP A 64K BUFFER TO BE USED   *
 ; *     FOR DATA LOGGING.  REMEMBER THAT 2 BYTES ARE REQUIRED FOR       *
 ; *     EACH COMM BYTE, SO THE BUFFER IS ONLY 32K EVENTS LONG, OR ABOUT *
 ; *     30 SECONDS OF CONTINUOUS 9600 BAUD DATA.  IN THE REAL WORLD,    *
 ; *     ASYNC DATA IS RARELY CONTINUOUS, SO THE BUFFER WILL PROBABLY    *
 ; *     HOLD MORE THAN 30 SECONDS WORTH OF DATA.                        *
 ; *                                                                     *
 ; *     WHEN INSTALLED, COMMSCOP INTERCEPTS ALL INT 14H CALLS.  IF THE  *
 ; *     PROGRAM HAS BEEN ACTIVATED AND THE INT IS EITHER SEND OR RE-    *
 ; *     CEIVE DATA, A COPY OF THE DATA BYTE, PROPERLY TAGGED, IS PLACED *
 ; *     IN THE BUFFER.  IN ANY CASE, DATA IS PASSED ON TO THE REAL      *
 ; *     INT 14H HANDLER.                                                *
 ; *                                                                     *
 ; *     COMMSCOP IS INVOKED BY ISSUING AN INT 60H CALL.  THE INT HAS    *
 ; *     THE FOLLOWING CALLING SEQUENCE:                                 *
 ; *                                                                     *
 ; *             AH -- COMMAND                                           *
 ; *                   0 -- STOP TRACING, PLACE STOP MARK IN BUFFER      *
 ; *                   1 -- FLUSH BUFFER AND START TRACE                 *
 ; *                   2 -- RESUME TRACE                                 *
 ; *                   3 -- RETURN COMM BUFFER ADDRESSES                 *
 ; *             DX -- COMM PORT (ONLY USED WITH AH = 1 or 2)            *
 ; *                   0 -- COM1                                         *
 ; *                   1 -- COM2                                         *
 ; *                                                                     *
 ; *     THE FOLLOWING DATA IS RETURNED IN RESPONSE TO AH = 3:           *
 ; *                                                                     *
 ; *             CX -- BUFFER COUNT IN BYTES                             *
 ; *             DX -- SEGMENT ADDRESS OF THE START OF THE BUFFER        *
 ; *             BX -- OFFSET ADDRESS OF THE START OF THE BUFFER         *
 ; *                                                                     *
 ; *     THE COMM BUFFER IS FILLED WITH 2-BYTE DATA ENTRIES OF THE       *
 ; *     FOLLOWING FORM:                                                 *
 ; *                                                                     *
 ; *             BYTE 0 -- CONTROL                                       *
 ; *                  BIT 0 -- ON FOR RECEIVED DATA, OFF FOR TRANS.      *
 ; *                  BIT 7 -- STOP MARK -- INDICATES COLLECTION WAS     *
 ; *                              INTERRUPTED AND RESUMED.               *
 ; *             BYTE 1 -- 8-BIT DATA                                    *
 ; *                                                                     *
 ; ***********************************************************************

 CSEG    SEGMENT
         ASSUME  CS:CSEG,DS:CSEG
         ORG     100H                    ;TO MAKE A COMM FILE

 INITIALIZE:
         JMP     VECTOR_INIT             ;JUMP TO THE INITIALIZATION
                                         ; ROUTINE WHICH, TO SAVE SPACE,
                                         ; IS IN THE COMM BUFFER

 ;
 ;  SYSTEM VARIABLES
 ;
 OLD_COMM_INT    DD      ?               ;ADDRESS OF REAL COMM INT
 COUNT           DW      0               ;BUFFER COUNT
 COMMSCOPE_INT   EQU     60H             ;COMMSCOPE CONTROL INT
 STATUS          DB      0               ;PROCESSING STATUS
                                         ; 0 -- OFF
                                         ; 1 -- ON
 PORT            DB      0               ;COMM PORT BEING TRACED
 BUFPNTR         DW      VECTOR_INIT     ;NEXT BUFFER LOCATION

         SUBTTL  DATA INTERRUPT HANDLER
 PAGE
 ; ***********************************************************************
 ; *                                                                     *
 ; *  COMMSCOPE                                                          *
 ; *     THIS PROCEDURE INTERCEPTS ALL INT 14H CALLS AND LOGS THE DATA   *
 ; *     IF APPROPRIATE.                                                 *
 ; *                                                                     *
 ; ***********************************************************************
 COMMSCOPE       PROC    NEAR

         TEST    CS:STATUS,1             ;ARE WE ON?
         JZ      OLD_JUMP                ; NO, SIMPLY JUMP TO OLD HANDLER

         CMP     AH,00H                  ;SKIP SETUP CALLS
         JE      OLD_JUMP                ; .

         CMP     AH,03H                  ;SKIP STATUS REQUESTS
         JAE     OLD_JUMP                ; .

         CMP     AH,02H                  ;IS THIS A READ REQUEST?
         JE      GET_READ                ; YES, GO PROCESS

 ;
 ;  DATA WRITE REQUEST -- SAVE IF APPROPRIATE
 ;
         CMP     DL,CS:PORT              ;IS WRITE FOR PORT BEING TRACED?
         JNE     OLD_JUMP                ; NO, JUST PASS IT THROUGH

         PUSH    DS                      ;SAVE CALLER'S REGISTERS
         PUSH    BX                      ; .
         PUSH    CS                      ;SET UP DS FOR OUR PROGRAM
         POP     DS                      ; .
         MOV     BX,BUFPNTR              ;GET ADDR OF NEXT BUFFER LOC
         MOV     [BX],BYTE PTR 0         ;MARK AS TRANSMITTED BYTE
         MOV     [BX+1],AL               ;SAVE DATA IN BUFFER
         INC     COUNT                   ;INCREMENT BUFFER BYTE COUNT
         INC     COUNT                   ; .
         INC     BX                      ;POINT TO NEXT LOCATION
         INC     BX                      ; .
         MOV     BUFPNTR,BX              ;SAVE NEW POINTER
         JNZ     WRITE_DONE              ;ZERO MEANS BUFFER HAS WRAPPED

         MOV     STATUS,0                ;TURN COLLECTION OFF
 WRITE_DONE:
         POP     BX                      ;RESTORE CALLER'S REGISTERS
         POP     DS                      ; .
         JMP     OLD_JUMP                ;PASS REQUEST ON TO BIOS ROUTINE
 ;
 ;  PROCESS A READ DATA REQUEST AND WRITE TO BUFFER IF APPROPRIATE
 ;
 GET_READ:
         CMP     DL,CS:PORT              ;IS READ FOR PORT BEING TRACED?
         JNE     OLD_JUMP                ; NO, JUST PASS IT THROUGH

         PUSH    DS                      ;SAVE CALLER'S REGISTERS
         PUSH    BX                      ; .
         PUSH    CS                      ;SET UP DS FOR OUR PROGRAM
         POP     DS                      ; .

         PUSHF                           ;FAKE INT 14H CALL
         CLI                             ; .
         CALL    OLD_COMM_INT            ;PASS REQUEST ON TO BIOS
         TEST    AH,80H                  ;VALID READ?
         JNZ     READ_DONE               ; NO, SKIP BUFFER UPDATE

         MOV     BX,BUFPNTR              ;GET ADDR OF NEXT BUFFER LOC
         MOV     [BX],BYTE PTR 1         ;MARK AS RECEIVED BYTE
         MOV     [BX+1],AL               ;SAVE DATA IN BUFFER
         INC     COUNT                   ;INCREMENT BUFFER BYTE COUNT
         INC     COUNT                   ; .
         INC     BX                      ;POINT TO NEXT LOCATION
         INC     BX                      ; .
         MOV     BUFPNTR,BX              ;SAVE NEW POINTER
         JNZ     READ_DONE               ;ZERO MEANS BUFFER HAS WRAPPED

         MOV     STATUS,0                ;TURN COLLECTION OFF
 READ_DONE:
         POP     BX                      ;RESTORE CALLER'S REGISTERS
         POP     DS                      ; .
         IRET

 ;
 ;  JUMP TO COMM BIOS ROUTINE
 ;
 OLD_JUMP:
         JMP     CS:OLD_COMM_INT

 COMMSCOPE ENDP

         SUBTTL  CONTROL INTERRUPT HANDLER
 PAGE
 ; ***********************************************************************
 ; *                                                                     *
 ; *  CONTROL                                                            *
 ; *     THIS ROUTINE PROCESSES CONTROL REQUESTS.                        *
 ; *                                                                     *
 ; ***********************************************************************

 CONTROL PROC    NEAR
         CMP     AH,00H                  ;STOP REQUEST?
         JNE     CNTL_START              ; NO, CHECK START
         PUSH    DS                      ;SAVE REGISTERS
         PUSH    BX                      ; .
         PUSH    CS                      ;SET DS FOR OUR ROUTINE
         POP     DS
         MOV     STATUS,0                ;TURN PROCESSING OFF
         MOV     BX,BUFPNTR              ;PLACE STOP MARK IN BUFFER
         MOV     [BX],BYTE PTR 80H       ; .
         MOV     [BX+1],BYTE PTR 0FFH    ; .
         INC     BX                      ;INCREMENT BUFFER POINTER
         INC     BX                      ; .
         MOV     BUFPNTR,BX              ; .
         INC     COUNT                   ;INCREMENT COUNT
         INC     COUNT                   ; .
         POP     BX                      ;RESTORE REGISTERS
         POP     DS                      ; .
         JMP     CONTROL_DONE

 CNTL_START:
         CMP     AH,01H                  ;START REQUEST?
         JNE     CNTL_RESUME             ; NO, CHECK RESUME
         MOV     CS:PORT,DL              ;SAVE PORT TO TRACE
         MOV     CS:BUFPNTR,OFFSET VECTOR_INIT ;RESET BUFFER TO START
         MOV     CS:COUNT,0              ;ZERO COUNT
         MOV     CS:STATUS,1             ;START LOGGING
         JMP     CONTROL_DONE

 CNTL_RESUME:
         CMP     AH,02H                  ;RESUME REQUEST?
         JNE     CNTL_STATUS             ; NO, CHECK STATUS
         CMP     CS:BUFPNTR,0            ;END OF BUFFER CONDITION?
         JE      CONTROL_DONE            ; YES, DO NOTHING
         MOV     CS:PORT,DL              ;SAVE PORT TO TRACE
         MOV     CS:STATUS,1             ;START LOGGING
         JMP     CONTROL_DONE

 CNTL_STATUS:
         CMP     AH,03H                  ;RETURN STATUS REQUEST?
         JNE     CONTROL_DONE            ; NO, ERROR -- DO NOTHING
         MOV     CX,CS:COUNT             ;RETURN COUNT
         PUSH    CS                      ;RETURN SEGMENT ADDR OF BUFFER
         POP     DX                      ; .
         MOV     BX,OFFSET VECTOR_INIT   ;RETURN OFFSET ADDR OF BUFFER

 CONTROL_DONE:
         IRET

 CONTROL ENDP


         SUBTTL     INITIALIZE INTERRUPT VECTORS
 PAGE
 ; ***********************************************************************
 ; *                                                                     *
 ; *  VECTOR_INIT                                                        *
 ; *     THIS PROCEDURE INITIALIZES THE INTERRUPT VECTORS AND THEN       *
 ; *     EXITS VIA THE MS-DOS TERMINATE-AND-STAY-RESIDENT FUNCTION.      *
 ; *     A BUFFER OF 64K IS RETAINED.  THE FIRST AVAILABLE BYTE          *
 ; *     IN THE BUFFER IS THE OFFSET OF VECTOR_INIT.                     *
 ; *                                                                     *
 ; ***********************************************************************

         EVEN                    ;ASSURE BUFFER ON EVEN BOUNDARY
 VECTOR_INIT     PROC     NEAR
 ;
 ;  GET ADDRESS OF COMM VECTOR (INT 14H)
 ;
         MOV     AH,35H
         MOV     AL,14H
         INT     21H
 ;
 ;  SAVE OLD COMM INT ADDRESS
 ;
         MOV     WORD PTR OLD_COMM_INT,BX
         MOV     AX,ES
         MOV     WORD PTR OLD_COMM_INT[2],AX
 ;
 ;  SET UP COMM INT TO POINT TO OUR ROUTINE
 ;
         MOV     DX,OFFSET COMMSCOPE
         MOV     AH,25H
         MOV     AL,14H
         INT     21H
 ;
 ;  INSTALL CONTROL ROUTINE INT
 ;
         MOV     DX,OFFSET CONTROL
         MOV     AH,25H
         MOV     AL,COMMSCOPE_INT
         INT     21H
 ;
 ;  SET LENGTH TO 64K, EXIT AND STAY RESIDENT
 ;
         MOV     AX,3100H          ;TERM AND STAY RES COMMAND
         MOV     DX,1000H          ;64K RESERVED
         INT     21H               ;DONE
 VECTOR_INIT ENDP
 CSEG    ENDS
         END     INITIALIZE

 \SAMPCODE\DOS_ENCY\18\TESTCOM1.ASM

         TITLE TESTCOMM -- TEST COMMSCOP ROUTINE
 ; ***********************************************************************
 ; *                                                                     *
 ; *  TESTCOMM                                                           *
 ; *     THIS ROUTINE PROVIDES DATA FOR THE COMMSCOP ROUTINE.  IT READS  *
 ; *     CHARACTERS FROM THE KEYBOARD AND WRITES THEM TO COM1 USING      *
 ; *     INT 14H.  DATA IS ALSO READ FROM INT 14H AND DISPLAYED ON THE   *
 ; *     SCREEN.  THE ROUTINE RETURNS TO MS-DOS WHEN Ctrl-C IS PRESSED   *
 ; *     ON THE KEYBOARD.                                                *
 ; *                                                                     *
 ; ***********************************************************************

 SSEG    SEGMENT PARA STACK 'STACK'
         DW      128 DUP(?)
 SSEG    ENDS

 CSEG    SEGMENT
         ASSUME  CS:CSEG,SS:SSEG
 BEGIN   PROC    FAR
         PUSH    DS                      ;SET UP FOR RET TO MS-DOS
         XOR     AX,AX                   ; .
         PUSH    AX                      ; .

 MAINLOOP:
         MOV     AH,6                    ;USE MS-DOS CALL TO CHECK FOR
         MOV     DL,0FFH                 ; KEYBOARD ACTIVITY
         INT     21                      ; IF NO CHARACTER, JUMP TO
         JZ      TESTCOMM                ; COMM ACTIVITY TEST

         CMP     AL,03                   ;WAS CHARACTER A Ctrl-C?
         JNE     SENDCOMM                ; NO, SEND IT TO SERIAL PORT
         RET                             ; YES, RETURN TO MS-DOS

 SENDCOMM:
         MOV     AH,01                   ;USE INT 14H WRITE FUNCTION TO
         MOV     DX,0                    ; SEND DATA TO SERIAL PORT
         INT     14H                     ; .

 TESTCOMM:
         MOV     AH,3                    ;GET SERIAL PORT STATUS
         MOV     DX,0                    ; .
         INT     14H                     ; .
         AND     AH,1                    ;ANY DATA WAITING?
         JZ      MAINLOOP                ; NO, GO BACK TO KEYBOARD TEST
         MOV     AH,2                    ;READ SERIAL DATA
         MOV     DX,0                    ; .
         INT     14H                     ; .
         MOV     AH,6                    ;WRITE SERIAL DATA TO SCREEN
         INT     21H                     ; .
         JMP     MAINLOOP                ;CONTINUE

 BEGIN   ENDP
 CSEG    ENDS
         END     BEGIN

 \SAMPCODE\DOS_ENCY\18\TESTCOM2.ASM

      TITLE TESTCOMM -- TEST COMMSCOP ROUTINE
 ; ***********************************************************************
 ; *                                                                     *
 ; *  TESTCOMM                                                           *
 ; *     THIS ROUTINE PROVIDES DATA FOR THE COMMSCOP ROUTINE.  IT READS  *
 ; *  CHARACTERS FROM THE KEYBOARD AND WRITES THEM TO COM1 USING         *
 ; *  INT 14H.  DATA IS ALSO READ FROM INT 14H AND DISPLAYED ON THE      *
 ; *  SCREEN.  THE ROUTINE RETURNS TO MS-DOS WHEN Ctrl-C IS PRESSED      *
 ; *  ON THE KEYBOARD.                                                   *
 ; *                                                                     *
 ; ***********************************************************************

 SSEG    SEGMENT PARA STACK 'STACK'
         DW      128 DUP(?)
 SSEG    ENDS

 CSEG    SEGMENT
         ASSUME  CS:CSEG,SS:SSEG
 BEGIN   PROC    FAR
         PUSH    DS                      ;SET UP FOR RET TO MS-DOS
         XOR     AX,AX                   ; .
         PUSH    AX                      ; .

 MAINLOOP:
         MOV     AH,6                    ;USE DOS CALL TO CHECK FOR
         MOV     DL,0FFH                 ; KEYBOARD ACTIVITY
         INT     21H                     ; IF NO CHARACTER, JUMP TO
         JZ      TESTCOMM                ; COMM ACTIVITY TEST

         CMP     AL,03                   ;WAS CHARACTER A Ctrl-C?
         JNE     SENDCOMM                ; NO, SEND IT TO SERIAL PORT
         RET                             ; YES, RETURN TO MS-DOS

 SENDCOMM:
         MOV     AH,01                   ;USE INT 14H WRITE FUNCTION TO
         MOV     DX,0                    ; SEND DATA TO SERIAL PORT
         INT     14H                     ; .

 TESTCOMM:
         MOV     AH,3                    ;GET SERIAL PORT STATUS
         MOV     DX,0                    ; .
         INT     14H                     ; .
         AND     AH,1                    ;ANY DATA WAITING?
         JZ      MAINLOOP                ; NO, GO BACK TO KEYBOARD TEST
         MOV     AH,2                    ;READ SERIAL DATA
         MOV     DX,0                    ; .
         INT     14H                     ; .
         MOV     AH,6                    ;WRITE SERIAL DATA TO SCREEN
         MOV     DL,AL                   ; .
         INT     21H                     ; .
         JMP     MAINLOOP                ;CONTINUE

 BEGIN   ENDP
 CSEG    ENDS
         END     BEGIN

 \SAMPCODE\DOS_ENCY\18\COMMDUMP.BAS

 ' **********************************************************************
 ' *                                                                    *
 ' *  COMMDUMP                                                          *
 ' *                                                                    *
 ' *  This routine dumps the contents of the COMMSCOP trace buffer to   *
 ' *  the screen in a formatted manner.  Received data is shown in      *
 ' *  reverse video.  Where possible, the ASCII character for the byte  *
 ' *  is shown; otherwise a dot is shown.  The value of the byte is     *
 ' *  displayed in hex below the character.  Points where tracing was   *
 ' *  stopped are shown by a solid bar.                                 *
 ' *                                                                    *
 ' **********************************************************************

         '
         '  Establish system constants and variables
         '
         DEFINT A-Z

         DIM INREG(7), OUTREG(7)         'Define register arrays

         RAX = 0                         'Establish values for 8086
         RBX = 1                         ' registers
         RCX = 2                         '  .
         RDX = 3                         '  .
         RBP = 4                         '  .
         RSI = 5                         '  .
         RDI = 6                         '  .
         RFL = 7                         '  .

         '
         '  Interrogate COMMSCOP to obtain addresses and count of data in
         '  trace buffer
         '
         INREG(RAX) = &H0300             'Request address data and count
         CALL INT86(&H60, VARPTR(INREG(0)), VARPTR(OUTREG(0)))

         NUM = OUTREG(RCX)               'Number of bytes in buffer
         BUFSEG = OUTREG(RDX)            'Buffer segment address
         BUFOFF = OUTREG(RBX)            'Offset of buffer start

         IF NUM = 0 THEN END

         '
         '  Set screen up and display control data
         '
         CLS
         KEY OFF
         LOCATE 25, 1
         PRINT "NUM ="; NUM;"BUFSEG = "; HEX$(BUFSEG); " BUFOFF = ";
         PRINT HEX$(BUFOFF);
         LOCATE 4, 1
         PRINT STRING$(80,"-")
         DEF SEG = BUFSEG

         '
         '  Set up display control variables
         '
         DLINE = 1
         DCOL = 1
         DSHOWN = 0

         '
         '  Fetch and display each character in buffer
         '
         FOR I= BUFOFF TO BUFOFF+NUM-2 STEP 2
             STAT = PEEK(I)
             DAT = PEEK(I + 1)

             IF (STAT AND 1) = 0 THEN
                 COLOR 7, 0
             ELSE
                 COLOR 0, 7
             END IF

             RLINE = (DLINE-1) * 4 + 1
             IF (STAT AND &H80) = 0 THEN
                 LOCATE RLINE, DCOL
                 C$ = CHR$(DAT)
                 IF DAT < 32 THEN C$ = "."
                 PRINT C$;
                 H$ = RIGHT$("00" + HEX$(DAT), 2)
                 LOCATE RLINE + 1, DCOL
                 PRINT LEFT$(H$, 1);
                 LOCATE RLINE + 2, DCOL
                 PRINT RIGHT$(H$, 1);
             ELSE
                 LOCATE RLINE, DCOL
                 PRINT CHR$(178);
                 LOCATE RLINE + 1, DCOL
                 PRINT CHR$(178);
                 LOCATE RLINE + 2, DCOL
                 PRINT CHR$(178);
             END IF

             DCOL = DCOL + 1
             IF DCOL > 80 THEN
                 COLOR 7, 0
                 DCOL = 1
                 DLINE = DLINE + 1
                 SHOWN = SHOWN + 1
                 IF SHOWN = 6 THEN
                     LOCATE 25, 50
                     COLOR 0, 7
                     PRINT "ENTER ANY KEY TO CONTINUE:  ";
                     WHILE LEN(INKEY$) = 0
                     WEND
                     COLOR 7, 0
                     LOCATE 25, 50
                     PRINT SPACE$(29);
                     SHOWN = 0
                 END IF
                 IF DLINE > 6 THEN
                     LOCATE 24, 1
                     PRINT : PRINT : PRINT : PRINT
                     LOCATE 24, 1
                     PRINT STRING$(80, "-");
                     DLINE = 6
                 ELSE
                     LOCATE DLINE * 4, 1
                     PRINT STRING$(80, "-");
                 END IF
             END IF

         NEXT I

         END

 \SAMPCODE\DOS_ENCY\18\COMMSCMD.BAS

 ' ************************************************************************
 ' *                                                                      *
 ' *  COMMSCMD                                                            *
 ' *                                                                      *
 ' *  This routine controls the COMMSCOP program that has been in-        *
 ' *  stalled as a resident routine.  The operation performed is de-      *
 ' *  termined by the command line.  The COMMSCMD program is invoked      *
 ' *  as follows:                                                         *
 ' *                                                                      *
 ' *             COMMSCMD [[cmd][,port]]                                  *
 ' *                                                                      *
 ' *  where cmd is the command to be executed                             *
 ' *             STOP   -- stop trace                                     *
 ' *             START  -- flush trace buffer and start trace             *
 ' *             RESUME -- resume a stopped trace                         *
 ' *        port is the COMM  port to be traced (1=COM1, 2=COM2, etc.)    *
 ' *                                                                      *
 ' *  If cmd is omitted, STOP is assumed.  If port is omitted, 1 is       *
 ' *  assumed.                                                            *
 ' *                                                                      *
 ' ************************************************************************

         '
         '  Establish system constants and variables
         '
         DEFINT A-Z

         DIM INREG(7), OUTREG(7)         'Define register arrays

         RAX = 0                         'Establish values for 8086
         RBX = 1                         '  registers
         RCX = 2                         '  .
         RDX = 3                         '  .
         RBP = 4                         '  .
         RSI = 5                         '  .
         RDI = 6                         '  .
         RFL = 7                         '  .

         DIM TEXT$(2)

         TEXT$(0) = "STOPPED"
         TEXT$(1) = "STARTED"
         TEXT$(2) = "RESUMED"

         '
         '  Process command-line tail
         '
         C$ = COMMAND$                   'Get command-line data

         IF LEN(C$) = 0 THEN             'If no command line specified
             CMD = 0                     'Set CMD to STOP
             PORT = 0                    'Set PORT to COM1
             GOTO SENDCMD
         END IF

         COMMA = INSTR(C$, ", ")         'Extract operands
         IF COMMA = 0 THEN
             CMDTXT$ = C$
             PORT = 0
         ELSE
             CMDTXT$ = LEFT$(C$, COMMA - 1)
             PORT = VAL(MID$(C$, COMMA + 1)) - 1
         END IF

         IF PORT < 0 THEN PORT = 0

         IF CMDTXT$ = "STOP" THEN
             CMD = 0
         ELSEIF CMDTXT$ = "START" THEN
             CMD = 1
         ELSEIF CMDTXT$ = "RESUME" THEN
             CMD = 2
         ELSE
             CMD = 0
         END IF

         '
         '  Send command to COMMSCOP routine
         '
 SENDCMD:
         INREG(RAX) = 256 * CMD
         INREG(RDX) = PORT
         CALL INT86(&H60, VARPTR(INREG(0)), VARPTR(OUTREG(0)))
         '
         '  Notify user that action is complete
         '
         PRINT : PRINT
         PRINT "Communications tracing "; TEXT$(CMD);
         IF CMD <> 0 THEN
             PRINT " for port COM"; MID$(STR$(PORT + 1), 2); ":"
         ELSE
             PRINT
         END IF

         END

 \SAMPCODE\DOS_ENCY\18\EXP1.BAS

 '  EXP.BAS -- COMPUTE EXPONENTIAL WITH INFINITE SERIES
 '
 ' ***********************************************************************
 ' *                                                                     *
 ' *  EXP                                                                *
 ' *                                                                     *
 ' *  This routine computes EXP(x) using the following infinite series:  *
 ' *                                                                     *
 ' *                       x    x^2   x^3   x^4   x^5                    *
 ' *         EXP(x) = 1 + --- + --- + --- + --- + --- + ...              *
 ' *                       1!    2!    3!    4!    5!                    *
 ' *                                                                     *
 ' *                                                                     *
 ' *  The program requests a value for x and a value for the convergence *
 ' *  criterion, C.  The program will continue evaluating the terms of   *
 ' *  the series until the difference between two terms is less than C.  *
 ' *                                                                     *
 ' *  The result of the calculation and the number of terms required to  *
 ' *  converge are printed.  The program will repeat until an x of 0 is  *
 ' *  entered.                                                           *
 ' *                                                                     *
 ' ***********************************************************************

 '
 '  Initialize program variables
 '
 INITIALIZE:
         TERMS = 1
         FACT = 1
         LAST = 1.E35
         DELTA = 1.E34
         EX = 1
 '
 '  Input user data
 '
         INPUT "Enter number:  "; X
         IF X = 0 THEN END
         INPUT "Enter convergence criterion (.0001 for 4 places):  "; C

 '
 '  Compute exponential until difference of last 2 terms is < C
 '
         WHILE ABS(LAST - DELTA) >= C
             LAST = DELTA
             FACT = FACT * TERMS
             DELTA = X^TERMS / FACT
             EX = EX + DELTA
             TERMS = TERMS + 1
         WEND

 '
 '  Display answer and number of terms required to converge
 '
         PRINT EX
         PRINT TERMS; "elements required to converge"
         PRINT

         GOTO INITIALIZE

 \SAMPCODE\DOS_ENCY\18\EXP2.BAS

 '  EXP.BAS -- COMPUTE EXPONENTIAL WITH INFINITE SERIES
 '
 ' ***********************************************************************
 ' *                                                                     *
 ' *  EXP                                                                *
 ' *                                                                     *
 ' *  This routine computes EXP(x) using the following infinite series:  *
 ' *                                                                     *
 ' *                       x    x^2   x^3   x^4   x^5                    *
 ' *         EXP(x) = 1 + --- + --- + --- + --- + --- + ...              *
 ' *                       1!    2!    3!    4!    5!                    *
 ' *                                                                     *
 ' *                                                                     *
 ' *  The program requests a value for x and a value for the convergence *
 ' *  criterion, C.  The program will continue evaluating the terms of   *
 ' *  the series until the amount added with a term is less than C.      *
 ' *                                                                     *
 ' *  The result of the calculation and the number of terms required to  *
 ' *  converge are printed.  The program will repeat until an x of 0 is  *
 ' *  entered.                                                           *
 ' *                                                                     *
 ' ***********************************************************************

 '
 '  INITIALIZE program variables
 '
 Initialize:
         TERMS = 1
         FACT = 1
         DELTA = 1.E35
         EX = 1
 '
 '  Input user data
 '
         INPUT "Enter number:  "; X
         IF X = 0 THEN END
         INPUT "Enter convergence criterion (.0001 for 4 places):  "; C

 '
 '  Compute exponential until difference of last 2 terms is < C
 '
         WHILE DELTA > C
             FACT = FACT * TERMS
             DELTA = X^TERMS / FACT
             EX = EX + DELTA
             TERMS = TERMS + 1
         WEND

 '
 '  Display answer and number of terms required to converge
 '
         PRINT EX
         PRINT TERMS; "elements required to converge"
         PRINT

         GOTO INITIALIZE

 \SAMPCODE\DOS_ENCY\19
 \SAMPCODE\DOS_ENCY\19\HELLO.ASM

         NAME    HELLO

 _TEXT   SEGMENT byte public 'CODE'

         ASSUME  cs:_TEXT,ds:_DATA

 start:                                  ;program entry point
         mov     ax,seg msg
         mov     ds,ax
         mov     dx,offset msg           ;DS:DX -> msg
         mov     ah,09h
         int     21h                     ;perform int 21H function 09H
                                         ;(Output character string)
         mov     ax,4C00h
         int     21h                     ;perform int 21H function 4CH
                                         ;(Terminate with return code)
 _TEXT   ENDS


 _DATA   SEGMENT word public 'DATA'

 msg     DB      'Hello, world',0Dh,0Ah,'$'

 _DATA   ENDS


 _STACK  SEGMENT stack 'STACK'

         DW      80h dup(?)              ;stack depth = 128 words

 _STACK  ENDS

         END     start

 \SAMPCODE\DOS_ENCY\2
 \SAMPCODE\DOS_ENCY\2\SHELL.ASM

 ; SHELL.ASM   A simple program to run an application as an
 ;             MS-DOS shell program. The program name and
 ;             startup parameters must be adjusted before
 ;             SHELL is assembled.
 ;
 ; Written by William Wong
 ;
 ; To create SHELL.COM:
 ;
 ;             C>MASM SHELL;
 ;             C>LINK SHELL;
 ;             C>EXE2BIN SHELL.EXE SHELL.COM

 stderr  equ 2                ; standard error
 cr      equ 0dh              ; ASCII carriage return
 lf      equ 0ah              ; ASCII linefeed
 cseg    segment para public 'CODE'
 ;
 ; ----  Set up DS, ES, and SS:SP to run as .COM  ----
 ;
         assume  cs:cseg
 start   proc    far
         mov     ax,cs           ; set up segment registers
         add     ax,10h          ; AX = segment after PSP
         mov     ds,ax
         mov     ss,ax           ; set up stack pointer
         mov     sp,offset stk
         mov     ax,offset shell
         push    cs              ; push original CS
         push    ds              ; push segment of shell
         push    ax              ; push offset of shell
         ret                     ; jump to shell
 start   endp
 ;
 ; ----  Main program running as .COM  ----
 ;
 ; CS, DS, SS = cseg
 ; Original CS value on top of stack
 ;
         assume cs:cseg,ds:cseg,ss:cseg
 seg_size equ (((offset last) - (offset start)) + 10fh)/16
 shell   proc    near
         pop     es              ; ES = segment to shrink
         mov     bx,seg_size     ; BX = new segment size
         mov     ah,4ah          ; AH = modify memory block
         int     21h             ; free excess memory
         mov     cmd_seg,ds      ; setup segments in
         mov     fcb1_seg,ds     ; parameter block for EXEC
         mov     fcb2_seg,ds
         mov     dx,offset main_loop
         mov     ax,2523h        ; AX = set Control-C handler
         int     21h             ; set handler to DS:DX
         mov     dx,offset main_loop
         mov     ax,2524h        ; AX = set critical error handler
         int     21h             ; set handler to DS:DX
                                 ; Note: DS is equal to CS
 main_loop:
         push    ds              ; save segment registers
         push    es
         mov     cs:stk_seg,ss   ; save stack pointer
         mov     cs:stk_off,sp
         mov     dx,offset pgm_name
         mov     bx,offset par_blk
         mov     ax,4b00h        ; AX = EXEC/run program
         int     21h             ; carry = EXEC failed
         mov     ss,cs:stk_seg   ; restore stack pointer
         mov     sp,cs:stk_off
         pop     es              ; restore segment registers
         pop     ds
         jnc     main_loop       ; loop if program run
         mov     dx,offset load_msg
         mov     cx,load_msg_length
         call    print           ; display error message
         mov     ah,08h          ; AH = read without echo
         int     21h             ; wait for any character
         jmp     main_loop       ; execute forever
 shell   endp
 ;
 ; ----  Print string  ----
 ;
 ; DS:DX = address of string
 ; CX    = size
 ;
 print   proc    near
         mov     ah,40h          ; AH = write to file
         mov     bx,stderr       ; BX = file handle
         int     21h             ; print string
         ret
 print   endp
 ;
 ; ----  Message strings  ----
 ;
 load_msg db cr,lf
          db 'Cannot load program.',cr,lf
          db 'Press any key to try again.',cr,lf
 load_msg_length equ $-load_msg
 ;
 ; ----  Program data area  ----
 ;
 stk_seg  dw     0               ; stack segment pointer
 stk_off  dw     0               ; save area during EXEC
 pgm_name db     '\NEWSHELL.COM',0 ; any program will do
 par_blk  dw     0               ; use current environment
          dw     offset cmd_line ; command-line address
 cmd_seg  dw     0               ; fill in at initialization
          dw     offset fcb1     ; default FCB #1
 fcb1_seg dw     0               ; fill in at initialization
          dw     offset fcb2     ; default FCB #2
 fcb2_seg dw     0               ; fill in at initialization
 cmd_line db     0,cr            ; actual command line
 fcb1     db     0
          db     11 dup (' ')
          db     25 dup ( 0 )
 fcb2     db     0
          db     11 dup (' ')
          db     25 dup ( 0 )
          dw     200 dup ( 0 )   ; program stack area
 stk      dw     0
 last     equ    $               ; last address used
 cseg     ends
          end    start

 \SAMPCODE\DOS_ENCY\4
 \SAMPCODE\DOS_ENCY\4\FIG4_10.ASM

 ;Source Module MODULE_C

 ;Constant declarations ******************************************************

 CONST   SEGMENT WORD PUBLIC 'CONST'

 CONST_FIELD_C   DB      'Constant C'    ;declare a MODULE_C constant

 CONST   ENDS


 ;Preinitialized data fields *************************************************

 _DATA   SEGMENT WORD PUBLIC 'DATA'

 DATA_FIELD_C    DB      'Data C'        ;declare a MODULE_C preinitialized fi

 _DATA   ENDS


 ;Uninitialized data fields **************************************************

 _BSS    SEGMENT WORD PUBLIC 'BSS'

 BSS_FIELD_C     DB      5 DUP(?)        ;declare a MODULE_C uninitialized fie

 _BSS    ENDS


 ;Program text ***************************************************************

 DGROUP  GROUP   _DATA,CONST,_BSS

 _TEXT   SEGMENT BYTE PUBLIC 'CODE'

         ASSUME  CS:_TEXT,DS:DGROUP,ES:NOTHING,SS:NOTHING

         PUBLIC  PROC_C                  ;referenced in MODULE_A
 PROC_C  PROC    NEAR

         RET

 PROC_C  ENDP

 _TEXT   ENDS

         END

 \SAMPCODE\DOS_ENCY\4\FIG4_15.ASM

 COMSEG  SEGMENT BYTE PUBLIC 'CODE'
         ASSUME  CS:COMSEG,DS:COMSEG,ES:COMSEG,SS:COMSEG
         ORG     0100H

 BEGIN:
         JMP     START           ;skip over data fields
 ;Place your data fields here.

 START:
 ;Place your program text here.
         MOV     AX,4C00H        ;terminate (MS-DOS 2.0 or later only)
         INT     21H
 COMSEG  ENDS
         END     BEGIN

 \SAMPCODE\DOS_ENCY\4\FIG4_16.ASM

 CSEG    SEGMENT BYTE PUBLIC 'CODE'      ;establish segment order
 CSEG    ENDS
 DSEG    SEGMENT BYTE PUBLIC 'DATA'
 DSEG    ENDS
 COMGRP  GROUP   CSEG,DSEG               ;establish joint address base
 DSEG    SEGMENT
 ;Place your data fields here.
 DSEG    ENDS
 CSEG    SEGMENT

         ASSUME  CS:COMGRP,DS:COMGRP,ES:COMGRP,SS:COMGRP
         ORG     0100H

 BEGIN:
 ;Place your program text here.  Remember to use
 ;OFFSET COMGRP:LABEL whenever you use OFFSET.
         MOV     AX,4C00H                ;terminate (MS-DOS 2.0 or later only)
         INT     21H
 CSEG    ENDS
         END     BEGIN

 \SAMPCODE\DOS_ENCY\4\FIG4_3.ASM

 TEXT    SEGMENT PARA PUBLIC 'CODE'

         ASSUME  CS:TEXT,DS:NOTHING,ES:NOTHING,SS:NOTHING

 TERM_VECTOR     DD      ?

 ENTRY_PROC      PROC    FAR

 ;save pointer to termination vector in PSP

         MOV     WORD PTR CS:TERM_VECTOR+0,0000h ;save offset of Warm Boot vec
         MOV     WORD PTR CS:TERM_VECTOR+2,DS    ;save segment address of PSP

 ;***** Place main task here *****

 ;determine which MS-DOS version is active, take jump if 2.0 or later

         MOV     AH,30h          ;load Get MS-DOS Version Number function code
         INT     21h             ;call MS-DOS to get version number
         OR      AL,AL           ;see if pre-2.0 MS-DOS
         JNZ     TERM_0200       ;jump if 2.0 or later

 ;terminate under pre-2.0 MS-DOS

         JMP     CS:TERM_VECTOR  ;jump to Warm Boot vector in PSP

 ;terminate under MS-DOS 2.0 or later

 TERM_0200:
         MOV     AX,4C00h        ;load MS-DOS termination function code
                                 ;and return code
         INT     21h             ;call MS-DOS to terminate

 ENTRY_PROC      ENDP

 TEXT    ENDS

         END     ENTRY_PROC      ;define entry point

 \SAMPCODE\DOS_ENCY\4\FIG4_4.ASM

 TEXT    SEGMENT PARA PUBLIC 'CODE'

         ASSUME  CS:TEXT,DS:NOTHING,ES:NOTHING,SS:NOTHING

 ENTRY_PROC      PROC    FAR     ;make proc FAR so RET will be FAR

 ;Push pointer to termination vector in PSP
         PUSH    DS              ;push PSP's segment address
         XOR     AX,AX           ;ax = 0 = offset of Warm Boot vector in PSP
         PUSH    AX              ;push Warm Boot vector offset

 ;***** Place main task here *****

 ;Determine which MS-DOS version is active, take jump if 2.0 or later

         MOV     AH,30h          ;load Get MS-DOS Version Number function code
         INT     21h             ;call MS-DOS to get version number
         OR      AL,AL           ;see if pre-2.0 MS-DOS
         JNZ     TERM_0200       ;jump if 2.0 or later

 ;Terminate under pre-2.0 MS-DOS (this is a FAR proc, so RET will be FAR)
         RET                     ;pop PSP:00H into CS:IP to terminate

 ;Terminate under MS-DOS 2.0 or later
 TERM_0200:
         MOV     AX,4C00h        ;AH = MS-DOS Terminate Process with Return Co
                                 ;function code, AL = return code of 00H
         INT     21h             ;call MS-DOS to terminate

 ENTRY_PROC      ENDP

 TEXT    ENDS

         END     ENTRY_PROC      ;declare the program's entry point

 \SAMPCODE\DOS_ENCY\4\FIG4_8.ASM

 ;Source Module MODULE_A

 ;Predeclare all segments to force the linker's segment ordering *************

 _TEXT   SEGMENT BYTE PUBLIC 'CODE'
 _TEXT   ENDS

 _DATA   SEGMENT WORD PUBLIC 'DATA'
 _DATA   ENDS

 CONST   SEGMENT WORD PUBLIC 'CONST'
 CONST   ENDS

 _BSS    SEGMENT WORD PUBLIC 'BSS'
 _BSS    ENDS

 STACK   SEGMENT PARA STACK 'STACK'
 STACK   ENDS

 DGROUP  GROUP   _DATA,CONST,_BSS,STACK


 ;Constant declarations ******************************************************

 CONST   SEGMENT WORD PUBLIC 'CONST'

 CONST_FIELD_A   DB      'Constant A'    ;declare a MODULE_A constant

 CONST   ENDS


 ;Preinitialized data fields *************************************************

 _DATA   SEGMENT WORD PUBLIC 'DATA'

 DATA_FIELD_A    DB      'Data A'        ;declare a MODULE_A preinitialized fi

 _DATA   ENDS


 ;Uninitialized data fields **************************************************

 _BSS    SEGMENT WORD PUBLIC 'BSS'

 BSS_FIELD_A     DB      5 DUP(?)        ;declare a MODULE_A uninitialized fie

 _BSS    ENDS


 ;Program text ***************************************************************

 _TEXT   SEGMENT BYTE PUBLIC 'CODE'

         ASSUME  CS:_TEXT,DS:DGROUP,ES:NOTHING,SS:NOTHING

         EXTRN   PROC_B:NEAR             ;label is in _TEXT segment (NEAR)
         EXTRN   PROC_C:NEAR             ;label is in _TEXT segment (NEAR)

 PROC_A  PROC    NEAR

         CALL    PROC_B                  ;call into MODULE_B
         CALL    PROC_C                  ;call into MODULE_C

         MOV     AX,4C00H                ;terminate (MS-DOS 2.0 or later only)
         INT     21H

 PROC_A  ENDP

 _TEXT   ENDS


 ;Stack **********************************************************************

 STACK   SEGMENT PARA STACK 'STACK'

         DW      128 DUP(?)      ;declare some space to use as stack
 STACK_BASE      LABEL   WORD

 STACK   ENDS



         END     PROC_A          ;declare PROC_A as entry point

 \SAMPCODE\DOS_ENCY\4\FIG4_9.ASM

 ;Source Module MODULE_B

 ;Constant declarations ******************************************************

 CONST   SEGMENT WORD PUBLIC 'CONST'

 CONST_FIELD_B   DB      'Constant B'    ;declare a MODULE_B constant

 CONST   ENDS


 ;Preinitialized data fields *************************************************

 _DATA   SEGMENT WORD PUBLIC 'DATA'

 DATA_FIELD_B    DB      'Data B'        ;declare a MODULE_B preinitialized fi

 _DATA   ENDS


 ;Uninitialized data fields **************************************************

 _BSS    SEGMENT WORD PUBLIC 'BSS'

 BSS_FIELD_B     DB      5 DUP(?)        ;declare a MODULE_B uninitialized fie

 _BSS    ENDS


 ;Program text ***************************************************************

 DGROUP  GROUP   _DATA,CONST,_BSS

 _TEXT   SEGMENT BYTE PUBLIC 'CODE'

         ASSUME  CS:_TEXT,DS:DGROUP,ES:NOTHING,SS:NOTHING

         PUBLIC  PROC_B                  ;reference in MODULE_A
 PROC_B  PROC    NEAR

         RET

 PROC_B  ENDP

 _TEXT   ENDS

         END

 \SAMPCODE\DOS_ENCY\5
 \SAMPCODE\DOS_ENCY\5\FXN02H.ASM

         mov     ah,02h          ; function 02H = display character
         mov     dl,'*'          ; DL = character to display
         int     21h             ; transfer to MS-DOS

 \SAMPCODE\DOS_ENCY\5\FXN04H.ASM

 msg     db      'This is a test message'
 msg_len equ     $-msg
         .
         .
         .
         mov     bx,seg msg      ; set DS:BX = address of message
         mov     ds,bx
         mov     bx,offset msg
         mov     cx,msg_len      ; set CX = length of message
 L1:     mov     dl,[bx]         ; get next character into DL
         mov     ah,04H          ; function 04H = auxiliary output
         int     21h             ; transfer to MS-DOS
         inc     bx              ; bump pointer to output string
         loop    L1              ; and loop until all chars. sent

 \SAMPCODE\DOS_ENCY\5\FXN05H.ASM

 msg     db      'This is a test message'
 msg_len equ     $-msg
         .
         .
         .
         mov     bx,seg msg      ; DS:BX = address of message
         mov     ds,bx
         mov     bx,offset msg
         mov     cx,msg_len      ; CX = length of message
 L1:     mov     dl,[bx]         ; get next character into DL
         mov     ah,05H          ; function 05H = printer output
         int     21h             ; transfer to MS-DOS
         inc     bx              ; bump pointer to output string
         loop    L1              ; and loop until all chars. sent

 \SAMPCODE\DOS_ENCY\5\FXN08H.ASM

         mov     ah,08h          ; function 08H = character input
                                 ;   without echo
         int     21h             ; transfer to MS-DOS
                                 ; now AL = character

 \SAMPCODE\DOS_ENCY\5\FXN09H.ASM

 msg     db      'This is a test message','$'
         .
         .
         .
         mov     dx,seg msg      ; DS:DX = address of text
         mov     ds,dx           ; to display
         mov     dx,offset msg
         mov     ah,09H          ; function 09H = display string
         int     21h             ; transfer to MS-DOS

 \SAMPCODE\DOS_ENCY\5\FXN0AH.ASM

 kbuf    db      80              ; maximum length of read
         db      0               ; actual length of read
         db      80 dup (0)      ; keyboard input goes here
         .
         .
         .
         mov     dx,seg kbuf     ; set DS:DX = address of
         mov     ds,dx           ; keyboard input buffer
         mov     dx,offset kbuf
         mov     ah,0ah          ; function 0AH = read buffered line
         int     21h             ; transfer to MS-DOS
                                 ; terminated by a carriage return,
                                 ; and kbuf+1 = length of input,
                                 ; not including the carriage return

 \SAMPCODE\DOS_ENCY\5\FXN3FH.ASM

 kbuf    db      80 dup (0)      ; buffer for keyboard input
         .
         .
         .
         mov     dx,seg kbuf     ; set DS:DX = address of
         mov     ds,dx           ; keyboard input buffer
         mov     dx,offset kbuf
         mov     cx,80           ; CX = maximum length of input
         mov     bx,0            ; standard input handle = 0
         mov     ah,3fh          ; function 3FH = read file/device
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if function failed
                                 ; otherwise AX = actual
                                 ; length of keyboard input,
                                 ; including carriage-return and
                                 ; linefeed, and the data is
                                 ; in the buffer 'kbuf'

 \SAMPCODE\DOS_ENCY\5\FXN40H_1.ASM

 msg     db      'This is a test message'
 msg_len equ     $-msg
         .
         .
         .
         mov     dx,seg msg      ; DS:DX = address of text
         mov     ds,dx           ; to display
         mov     dx,offset msg
         mov     cx,msg_len      ; CX = length of text
         mov     bx,1            ; BX = handle for standard output
         mov     ah,40h          ; function 40H = write file/device
         int     21h             ; transfer to MS-DOS

 \SAMPCODE\DOS_ENCY\5\FXN40H_2.ASM

 msg     db      'This is a test message'
 msg_len equ     $-msg
         .
         .
         .
         mov     dx,seg msg      ; DS:DX = address of message
         mov     ds,dx
         mov     dx,offset msg
         mov     cx,msg_len      ; CX = length of message
         mov     bx,3            ; BX = handle for standard aux.
         mov     ah,40h          ; function 40H = write file/device
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if write operation failed

 \SAMPCODE\DOS_ENCY\5\FXN40H_3.ASM

 msg     db      'This is a test message'
 msg_len equ     $-msg
         .
         .
         .
         mov     dx,seg msg      ; DS:DX = address of message
         mov     ds,dx
         mov     dx,offset msg
         mov     cx,msg_len      ; CX = length of message
         mov     bx,4            ; BX = handle for standard printer
         mov     ah,40h          ; function 40H = write file/device
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if write operation failed

 \SAMPCODE\DOS_ENCY\5\INT10H.ASM

         mov     ah,0eh          ; subfunction 0EH = write character
                                 ; in teletype mode
         mov     al,'*'          ; AL = character to display
         mov     bh,0            ; select display page 0
         int     10h             ; transfer to ROM BIOS video driver

 \SAMPCODE\DOS_ENCY\5\INT14H.ASM

 msg     db      'This is a test message'
 msg_len equ     $-msg
         .
         .
         .
         mov     bx,seg msg      ; DS:BX = address of message
         mov     ds,bx
         mov     bx,offset msg
         mov     cx,msg_len      ; CX = length of message
         mov     dx,0            ; DX = 0 for COM1
 L1:     mov     al,[bx]         ; get next character into AL
         mov     ah,01h          ; subfunction 01H = output
         int     14h             ; transfer to ROM BIOS
         inc     bx              ; bump pointer to output string
         loop    L1              ; and loop until all chars. sent

 \SAMPCODE\DOS_ENCY\5\INT16H.ASM

         mov     ah,00h          ; subfunction 00H = read character
         int     16h             ; transfer to ROM BIOS
                                 ; now AH = scan code, AL = character

 \SAMPCODE\DOS_ENCY\5\INT17H.ASM

 msg     db      'This is a test message'
 msg_len equ     $-msg
         .
         .
         .
         mov     bx,seg msg      ; DS:BX = address of message
         mov     ds,bx
         mov     bx,offset msg
         mov     cx,msg_len      ; CX = length of message
         mov     dx,0            ; DX = 0 for LPT1
 L1:     mov     al,[bx]         ; get next character into AL
         mov     ah,00H          ; subfunction 00H = output
         int     17h             ; transfer to ROM BIOS
         inc     bx              ; bump pointer to output string
         loop    L1              ; and loop until all chars. sent

 \SAMPCODE\DOS_ENCY\5\IOCTL_1.ASM

 info    dw      ?               ; save device information word here
         .
         .
         .
         mov     ax,4400h        ; AH = function 44H, IOCTL
                                 ; AL = subfunction 00H, get device
                                 ; information word
         mov     bx,0            ; BX = handle for standard input
         int     21h             ; transfer to MS-DOS
         mov     info,dx         ; save device information word
                                 ; (assumes DS = data segment)
         or      dl,20h          ; set raw mode bit
         mov     dh,0            ; and clear DH as MS-DOS requires
         mov     ax,4401h        ; AL = subfunction 01H, set device
                                 ; information word
                                 ; (BX still contains handle)
         int     21h             ; transfer to MS-DOS

 \SAMPCODE\DOS_ENCY\5\IOCTL_2.ASM

         mov     ax,4406H        ; AH = function 44H, IOCTL
                                 ; AL = subfunction 06H, get
                                 ; input status
         mov     bx,3            ; BX = handle for standard aux
         int     21h             ; transfer to MS-DOS
         or      al,al           ; test status of AUX driver
         jnz     ready           ; jump if input character ready
                                 ; else no character is waiting

 \SAMPCODE\DOS_ENCY\6
 \SAMPCODE\DOS_ENCY\6\CDVUTL.C

 /* cdvutl.c - COMDVR Utility
  *     Jim Kyle - 1987
  *     for use with COMDVR.SYS Device Driver
  */

 #include <stdio.h>                 /* i/o definitions      */
 #include <conio.h>                 /* special console i/o  */
 #include <stdlib.h>                /* misc definitions     */
 #include <dos.h>                   /* defines intdos()     */

 /*       the following define the driver status bits       */

 #define HWINT 0x0800           /* MCR, first word, HW Ints gated */
 #define o_DTR 0x0200           /* MCR, first word, output DTR    */
 #define o_RTS 0x0100           /* MCR, first word, output RTS    */

 #define m_PG  0x0010           /* LCR, first word, parity ON     */
 #define m_PE  0x0008           /* LCR, first word, parity EVEN   */
 #define m_XS  0x0004           /* LCR, first word, 2 stop bits   */
 #define m_WL  0x0003           /* LCR, first word, wordlen mask  */

 #define i_CD  0x8000           /* MSR, 2nd word, Carrier Detect  */
 #define i_RI  0x4000           /* MSR, 2nd word, Ring Indicator  */
 #define i_DSR 0x2000           /* MSR, 2nd word, Data Set Ready  */
 #define i_CTS 0x1000           /* MSR, 2nd word, Clear to Send   */

 #define l_SRE 0x0040           /* LSR, 2nd word, Xmtr SR Empty   */
 #define l_HRE 0x0020           /* LSR, 2nd word, Xmtr HR Empty   */
 #define l_BRK 0x0010           /* LSR, 2nd word, Break Received  */
 #define l_ER1 0x0008           /* LSR, 2nd word, FrmErr          */
 #define l_ER2 0x0004           /* LSR, 2nd word, ParErr          */
 #define l_ER3 0x0002           /* LSR, 2nd word, OveRun          */
 #define l_RRF 0x0001           /* LSR, 2nd word, Rcvr DR Full    */

 /*           now define CLS string for ANSI.SYS            */
 #define CLS    "\033[2J"

 FILE * dvp;
 union REGS rvs;
 int iobf [ 5 ];

 main ()
 { cputs ( "\nCDVUTL - COMDVR Utility Version 1.0 - 1987\n" );
   disp ();                         /* do dispatch loop           */
 }

 disp ()                            /* dispatcher; infinite loop  */
 { int c,
     u;
   u = 1;
   while ( 1 )
     { cputs ( "\r\n\tCommand (? for help): " );
       switch ( tolower ( c = getche ()))   /* dispatch           */
         {
         case '1' :                 /* select port 1              */
           fclose ( dvp );
           dvp = fopen ( "ASY1", "rb+" );
              u = 1;
           break;

         case '2' :                 /* select port 2              */
           fclose ( dvp );
           dvp = fopen ( "ASY2", "rb+" );
           u = 2;
           break;

         case 'b' :                 /* set baud rate              */
           if ( iobf [ 4 ] == 300 )
             iobf [ 4 ] = 1200;
           else
             if ( iobf [ 4 ] == 1200 )
               iobf [ 4 ] = 2400;
           else
             if ( iobf [ 4 ] == 2400 )
               iobf [ 4 ] = 9600;
           else
             iobf [ 4 ] = 300;
           iocwr ();
           break;

         case 'e' :                 /* set parity even            */
           iobf [ 0 ] |= ( m_PG + m_PE );
           iocwr ();
           break;

         case 'f' :                 /* toggle flow control        */
           if ( iobf [ 3 ] == 1 )
             iobf [ 3 ] = 2;
           else
             if ( iobf [ 3 ] == 2 )
               iobf [ 3 ] = 4;
           else
             if ( iobf [ 3 ] == 4 )
               iobf [ 3 ] = 0;
           else
             iobf [ 3 ] = 1;
           iocwr ();
           break;

         case 'i' :                 /* initialize MCR/LCR to 8N1 : */
           iobf [ 0 ] = ( HWINT + o_DTR + o_RTS + m_WL );
           iocwr ();
           break;

         case '?' :                 /* this help list              */
           cputs ( CLS )            /* clear the display           */
           center ( "COMMAND LIST \n" );
 center ( "1 = select port 1           L = toggle word LENGTH  " );
 center ( "2 = select port 2           N = set parity to NONE  " );
 center ( "B = set BAUD rate           O = set parity to ODD   " );
 center ( "E = set parity to EVEN      R = toggle error REPORTS" );
 center ( "F = toggle FLOW control     S = toggle STOP bits    " );
 center ( "I = INITIALIZE ints, etc.   Q = QUIT                " );
          continue;

         case 'l' :                  /* toggle word length         */
           iobf [ 0 ] ^= 1;
           iocwr ();
           break;

         case 'n' :                  /* set parity off             */
           iobf [ 0 ] &=~ ( m_PG + m_PE );
           iocwr ();
           break;

         case 'o' :                  /* set parity odd             */
           iobf [ 0 ] |= m_PG;
           iobf [ 0 ] &=~ m_PE;
           iocwr ();
           break;

         case 'r' :                  /* toggle error reports       */
           iobf [ 2 ] ^= 1;
           iocwr ();
           break;

         case 's' :                  /* toggle stop bits           */
           iobf [ 0 ] ^= m_XS;
           iocwr ();
           break;

         case 'q' :
           fclose ( dvp );
           exit ( 0 );               /* break the loop, get out    */
         }
       cputs ( CLS );                /* clear the display          */
       center ( "CURRENT COMDVR STATUS" );
       report ( u, dvp );            /* report current status      */
     }
 }

 center ( s ) char * s;              /* centers a string on CRT    */
 { int i ;
   for ( i = 80 - strlen ( s ); i > 0; i -= 2 )
     putch ( ' ' );
   cputs ( s );
   cputs ( "\r\n" );
 }

 iocwr ()                            /* IOCTL Write to COMDVR      */
 { rvs . x . ax = 0x4403;
   rvs . x . bx = fileno ( dvp );
   rvs . x . cx = 10;
   rvs . x . dx = ( int ) iobf;
   intdos ( & rvs, & rvs );
 }

 char * onoff ( x ) int x ;
 { return ( x ? " ON" : " OFF" );
 }

 report ( unit ) int unit ;
 { char temp [ 80 ];
   rvs . x . ax = 0x4402;
   rvs . x . bx = fileno ( dvp );
   rvs . x . cx = 10;
   rvs . x . dx = ( int ) iobf;
   intdos ( & rvs, & rvs );         /* use IOCTL Read to get data */
   sprintf ( temp, "\nDevice ASY%d\t%d BPS, %d-c-%c\r\n\n",
            unit, iobf [ 4 ],           /* baud rate              */
            5 + ( iobf [ 0 ] & m_WL ),  /* word length            */
            ( iobf [ 0 ] & m_PG ?
              ( iobf [ 0 ] & m_PE ? 'E' : 'O' ) : 'N' ),
            ( iobf [ 0 ] & m_XS ? '2' : '1' )); /* stop bits      */
   cputs ( temp );

   cputs ( "Hardware Interrupts are" );
   cputs ( onoff ( iobf [ 0 ] & HWINT ));
   cputs ( ", Data Terminal Rdy" );
   cputs ( onoff ( iobf [ 0 ] & o_DTR ));
   cputs ( ", Rqst To Send" );
   cputs ( onoff ( iobf [ 0 ] & o_RTS ));
   cputs ( ".\r\n" );

   cputs ( "Carrier Detect" );
   cputs ( onoff ( iobf [ 1 ] & i_CD ));
   cputs ( ", Data Set Rdy" );
   cputs ( onoff ( iobf [ 1 ] & i_DSR ));
   cputs ( ", Clear to Send" );
   cputs ( onoff ( iobf [ 1 ] & i_CTS ));
   cputs ( ", Ring Indicator" );
   cputs ( onoff ( iobf [ 1 ] & i_RI ));
   cputs ( ".\r\n" );

   cputs ( l_SRE & iobf [ 1 ] ? "Xmtr SR Empty, " : "" );
   cputs ( l_HRE & iobf [ 1 ] ? "Xmtr HR Empty, " : "" );
   cputs ( l_BRK & iobf [ 1 ] ? "Break Received, " : "" );
   cputs ( l_ER1 & iobf [ 1 ] ? "Framing Error, " : "" );
   cputs ( l_ER2 & iobf [ 1 ] ? "Parity Error, " : "" );
   cputs ( l_ER3 & iobf [ 1 ] ? "Overrun Error, " : "" );
   cputs ( l_RRF & iobf [ 1 ] ? "Rcvr DR Full, " : "" );
   cputs ( "\b\b.\r\n" );

   cputs ( "Reception errors " );
   if ( iobf [ 2 ] == 1 )
     cputs ( "are encoded as graphics in buffer" );
   else
     cputs ( "set failure flag" );
   cputs ( ".\r\n" );

   cputs ( "Outgoing Flow Control " );
   if ( iobf [ 3 ] & 4 )
     cputs ( "by XON and XOFF" );
   else
     if ( iobf [ 3 ] & 2 )
       cputs ( "by RTS and CTS" );
   else
     if ( iobf [ 3 ] & 1 )
       cputs ( "by DTR and DSR" );
   else
     cputs ( "disabled" );
   cputs ( ".\r\n" );
 }

 /*end of cdvutl.c */

 \SAMPCODE\DOS_ENCY\6\CTERM.C

 /* Terminal Emulator    (cterm.c)
  *      Jim Kyle, 1987
  *
  *      Uses files CH1, CH1A, and CH2 for MASM support...
  */

 #include <stdio.h>
 #include <conio.h>                      /* special console i/o      */
 #include <stdlib.h>                     /* misc definitions         */
 #include <dos.h>                        /* defines intdos()         */
 #include <string.h>
 #define BRK  'C'-'@'                    /* control characters       */
 #define ESC  '['-'@'
 #define XON  'Q'-'@'
 #define XOFF 'S'-'@'

 #define True  1
 #define False  0

 #define Is_Function_Key(C) ( (C) == ESC )

 static char capbfr [ 4096 ];            /* capture buffer           */
 static int wh,
      ws;

 static int I,
     waitchr = 0,
     vflag = False,
     capbp,
     capbc,
     Ch,
     Want_7_Bit = True,
     ESC_Seq_State = 0;        /* escape sequence state variable     */

 int _cx ,
      _cy,
      _atr = 0x07,                       /* white on black           */
      _pag = 0,
      oldtop = 0,
      oldbot = 0x184f;

 FILE * in_file = NULL;         /* start with keyboard input         */
 FILE * cap_file = NULL;

 #include "cterm.h"             /* external declarations, etc.       */

 int Wants_To_Abort ()          /* checks for interrupt of script    */
 { return broke ();
 }
 void

 main ( argc, argv ) int argc ; /* main routine                      */
  char * argv [];
 { char * cp,
      * addext ();
   if ( argc > 1 )                /* check for script filename       */
     in_file = fopen ( addext ( argv [ 1 ], ".SCR" ), "r" );
   if ( argc > 2 )                /* check for capture filename      */
     cap_file = fopen ( addext ( argv [ 2 ], ".CAP" ), "w" );
   set_int ();                    /* install CH1 module              */
   Set_Vid ();                    /* get video setup                 */
   cls ();                        /* clear the screen                */
   cputs ( "Terminal Emulator" ); /* tell who's working              */
   cputs ( "\r\n< ESC for local commands >\r\n\n" );
   Want_7_Bit = True;
   ESC_Seq_State = 0;
   Init_Comm ();                  /* set up drivers, etc.            */
   while ( 1 )                           /* main loop                */
     { if (( Ch = kb_file ()) > 0 )      /* check local              */
         { if ( Is_Function_Key ( Ch ))
             { if ( docmd () < 0 )       /* command                  */
                 break;
             }
           else
             Send_Byte ( Ch & 0x7F );    /* else send it             */
         }
       if (( Ch = Read_Modem ()) >= 0 )  /* check remote             */
         { if ( Want_7_Bit )
             Ch &= 0x7F;                 /* trim off high bit        */
           switch ( ESC_Seq_State )      /* state machine            */
             {
             case 0 :                    /* no Esc sequence          */
               switch ( Ch )
                 {
                 case ESC  :             /* Esc char received        */
                   ESC_Seq_State = 1;
                   break;

                 default :
                   if ( Ch == waitchr )  /* wait if required         */
                     waitchr = 0;
                   if ( Ch == 12 )       /* clear screen on FF       */
                     cls ();
                   else
                     if ( Ch != 127 )    /* ignore rubouts           */
                       { putchx ( (char) Ch );  /* handle all others */
                         put_cap ( (char) Ch );
                       }
                 }
               break;

             case 1 : /* ESC -- process any escape sequences here    */
               switch ( Ch )
                 {
                 case 'A' :              /* VT52 up                  */
                   ;                     /* nothing but stubs here   */
                   ESC_Seq_State = 0;
                   break;

                 case 'B' :              /* VT52 down                */
                   ;
                   ESC_Seq_State = 0;
                   break;

                 case 'C' :              /* VT52 left                */
                   ;
                   ESC_Seq_State = 0;
                   break;

                 case 'D' :              /* VT52 right               */
                   ;
                   ESC_Seq_State = 0;
                   break;

                 case 'E' :              /* VT52 Erase CRT           */
                   cls ();               /* actually do this one     */
                   ESC_Seq_State = 0;
                   break;

                 case 'H' :              /* VT52 home cursor         */
                   locate ( 0, 0 );
                   ESC_Seq_State = 0;
                   break;

                 case 'j' :              /* VT52 Erase to EOS        */
                   deos ();
                   ESC_Seq_State = 0;
                   break;

                 case '[' :      /* ANSI.SYS - VT100 sequence        */
                   ESC_Seq_State = 2;
                   break;

                 default :
                   putchx ( ESC );       /* pass thru all others     */
                   putchx ( (char) Ch );
                   ESC_Seq_State = 0;
                 }
               break;

             case 2 :                    /* ANSI 3.64 decoder        */
               ESC_Seq_State = 0;        /* not implemented          */
             }
         }
       if ( broke ())                    /* check CH1A handlers      */
         { cputs ( "\r\n***BREAK***\r\n" );
           break;
         }
     }                                   /* end of main loop         */
   if ( cap_file )                       /* save any capture         */
     cap_flush ();
   Term_Comm ();                         /* restore when done        */
   rst_int ();                           /* restore break handlers   */
   exit ( 0 );                           /* be nice to MS-DOS        */
 }

 docmd ()                                /* local command shell      */
 { FILE * getfil ();
   int wp;
   wp = True;
   if ( ! in_file || vflag )
     cputs ( "\r\n\tCommand: " );        /* ask for command          */
   else
     wp = False;
   Ch = toupper ( kbd_wait ());          /* get response             */
   if ( wp )
     putchx ( (char) Ch );
   switch  ( Ch )                         /* and act on it           */
     {
     case 'S' :
       if ( wp )
         cputs ( "low speed\r\n" );
       Set_Baud ( 300 );
       break;

     case 'D' :
       if ( wp )
         cputs ( "elay (1-9 sec): " );
       Ch = kbd_wait ();
       if ( wp )
         putchx ( (char) Ch );
       Delay ( 1000 * ( Ch - '0' ));
       if ( wp )
         putchx ( '\n' );
       break;

     case 'E' :
       if ( wp )
         cputs ( "ven Parity\r\n" );
       Set_Parity ( 2 );
       break;

     case 'F' :
       if ( wp )
         cputs ( "ast speed\r\n" );
       Set_Baud ( 1200 );
       break;

     case 'H' :
       if ( wp )
         { cputs ( "\r\n\tVALID COMMANDS:\r\n" );
           cputs ( "\tD = delay 0-9 seconds.\r\n" );
           cputs ( "\tE = even parity.\r\n" );
           cputs ( "\tF = (fast) 1200-baud.\r\n" );
           cputs ( "\tN = no parity.\r\n" );
           cputs ( "\tO = odd parity.\r\n" );
           cputs ( "\tQ = quit, return to DOS.\r\n" );
           cputs ( "\tR = reset modem.\r\n" );
           cputs ( "\tS = (slow) 300-baud.\r\n" );
           cputs ( "\tU = use script file.\r\n" );
           cputs ( "\tV = verify file input.\r\n" );
           cputs ( "\tW = wait for char." );
         }
       break;

     case 'N' :
       if ( wp )
         cputs ( "o Parity\r\n" );
       Set_Parity ( 1 );
       break;

     case 'O' :
       if ( wp )
         cputs ( "dd Parity\r\n" );
       Set_Parity ( 3 );
       break;

     case 'R' :
       if ( wp )
         cputs ( "ESET Comm Port\r\n" );
       Init_Comm ();
       break;

     case 'Q' :
       if ( wp )
         cputs ( " = QUIT Command\r\n" );
       Ch = ( - 1 );
       break;

     case 'U' :
       if ( in_file && ! vflag )
         putchx ( 'U' );
       cputs ( "se file: " );
       getfil ();
       cputs ( "File " );
       cputs ( in_file ? "Open\r\n" : "Bad\r\n" );
       waitchr = 0;
       break;

     case 'V' :
       if ( wp )
         { cputs ( "erify flag toggled " );
           cputs ( vflag ? "OFF\r\n" : "ON\r\n" );
         }
       vflag = vflag ? False : True;
       break;

     case 'W' :
       if ( wp )
         cputs ( "ait for: <" );
       waitchr = kbd_wait ();
       if ( waitchr == ' ' )
         waitchr = 0;
       if ( wp )
         { if ( waitchr )
             putchx ( (char) waitchr );
           else
             cputs ( "no wait" );
           cputs ( ">\r\n" );
         }
       break;

     default :
       if ( wp )
         { cputs ( "Don't know " );
           putchx ( (char) Ch );
           cputs ( "\r\nUse 'H' command for Help.\r\n" );
         }
       Ch = '?';
     }
   if ( wp )                             /* if window open...        */
     { cputs ( "\r\n[any key]\r" );
       while ( Read_Keyboard () == EOF ) /* wait for response        */
         ;
     }
   return Ch ;
 }

 kbd_wait ()                             /* wait for input           */
 { int c ;
   while (( c = kb_file ()) == ( - 1 ))
     ;
   return c & 255;
 }

 kb_file ()                              /* input from kb or file    */
 { int c ;
   if ( in_file )                        /* USING SCRIPT             */
     { c = Wants_To_Abort ();            /* use first as flag        */
       if ( waitchr && ! c )
         c = ( - 1 );                    /* then for char            */
       else
         if ( c || ( c = getc ( in_file )) == EOF || c == 26 )
           { fclose ( in_file );
             cputs ( "\r\nScript File Closed\r\n" );
             in_file = NULL;
             waitchr = 0;
             c = ( - 1 );
           }
       else
         if ( c == '\n' )                /* ignore LF's in file      */
           c = ( - 1 );
       if ( c == '\\' )                  /* process Esc sequence     */
         c = esc ();
       if ( vflag && c != ( - 1 ))       /* verify file char         */
         { putchx ( '{' );
           putchx ( (char) c );
           putchx ( '}' );
         }
     }
   else                                  /* USING CONSOLE            */
     c = Read_Keyboard ();               /* if not using file        */
   return ( c );
 }

 esc ()                                  /* script translator        */
 { int c ;
   c = getc ( in_file );                 /* control chars in file    */
   switch ( toupper ( c ))
     {
     case 'E' :
       c = ESC;
       break;

     case 'N' :
       c = '\n';
       break;

     case 'R' :
       c = '\r';
       break;

     case 'T' :
       c = '\t';
       break;

     case '^' :
       c = getc ( in_file ) & 31;
       break;
     }
   return ( c );
 }

 FILE * getfil ()
 { char fnm [ 20 ];
   getnam ( fnm, 15 );                   /* get the name             */
   if ( ! ( strchr ( fnm, '.' )))
     strcat ( fnm, ".SCR" );
   return ( in_file = fopen ( fnm, "r" ));
 }

 void getnam ( b, s ) char * b;          /* take input to buffer     */
  int s ;
 { while ( s -- > 0 )
     { if (( * b = kbd_wait ()) != '\r' )
         putchx ( * b ++ );
       else
         break ;
     }
   putchx ( '\n' );
   * b = 0;
 }

 char * addext ( b,                      /* add default EXTension    */
      e ) char * b,
      * e;
 { static char bfr [ 20 ];
   if ( strchr ( b, '.' ))
     return ( b );
   strcpy ( bfr, b );
   strcat ( bfr, e );
   return ( bfr );
 }

 void put_cap ( c ) char c ;
 { if ( cap_file && c != 13 )            /* strip out CR's           */
     fputc ( c, cap_file );              /* use MS-DOS buffering     */
 }

 void cap_flush ()                       /* end Capture mode         */
 { if ( cap_file )
     { fclose ( cap_file );
       cap_file = NULL;
       cputs ( "\r\nCapture file closed\r\n" );
     }
 }

 /*      TIMER SUPPORT STUFF (IBMPC/MSDOS)                           */
 static long timr;                       /* timeout register         */

 static union REGS rgv ;

 long getmr ()
 { long now ;                            /* msec since midnite       */
   rgv.x.ax = 0x2c00;
   intdos ( & rgv, & rgv );
   now = rgv.h.ch;                       /* hours                    */
   now *= 60L;                           /* to minutes               */
   now += rgv.h.cl;                      /* plus min                 */
   now *= 60L;                           /* to seconds               */
   now += rgv.h.dh;                      /* plus sec                 */
   now *= 100L;                          /* to 1/100                 */
   now += rgv.h.dl;                      /* plus 1/100               */
   return ( 10L * now );                 /* msec value               */
 }

 void Delay ( n ) int n ;                /* sleep for n msec         */
 { long wakeup ;
   wakeup = getmr () + ( long ) n;       /* wakeup time              */
   while ( getmr () < wakeup )
     ;                                   /* now sleep                */
 }

 void Start_Timer ( n ) int n ;          /* set timeout for n sec    */
 { timr = getmr () + ( long ) n * 1000L;
 }

 Timer_Expired ()        /* if timeout return 1 else return 0        */
 { return ( getmr () > timr );
 }

 Set_Vid ()
 { _i_v ();                              /* initialize video         */
   return 0;
 }

 void locate ( row, col ) int row ,
      col;
 { _cy = row % 25;
   _cx = col % 80;
   _wrpos ( row, col );                  /* use ML from CH2.ASM      */
 }

 void deol ()
 { _deol ();                             /* use ML from CH2.ASM      */
 }

 void deos ()
 { deol ();
   if ( _cy < 24 )                       /* if not last, clear       */
     { rgv.x.ax = 0x0600;
       rgv.x.bx = ( _atr << 8 );
       rgv.x.cx = ( _cy + 1 ) << 8;
       rgv.x.dx = 0x184F;
       int86 ( 0x10, & rgv, & rgv );
     }
   locate ( _cy, _cx );
 }

 void cls ()
 { _cls ();                              /* use ML                   */
 }

 void cursor ( yn ) int yn ;
 { rgv.x.cx = yn ? 0x0607 : 0x2607;      /* ON/OFF                   */
   rgv.x.ax = 0x0100;
   int86 ( 0x10, & rgv, & rgv );
 }

 void revvid ( yn ) int yn ;
 { if ( yn )
     _atr = _color ( 8, 7 );             /* black on white           */
   else
     _atr = _color ( 15, 0 );            /* white on black           */
 }

 putchx ( c ) char c ;                   /* put char to CRT          */
 { if ( c == '\n' )
     putch ( '\r' );
   putch ( c );
   return c ;
 }

 Read_Keyboard ()                    /* get keyboard character
                                        returns -1 if none present   */
 { int c ;
   if ( kbhit ())                        /* no char at all           */
     return ( getch ());
   return ( EOF );
 }

 /*      MODEM SUPPORT                   */
 static char mparm,
      wrk [ 80 ];

 void Init_Comm ()               /* initialize comm port stuff       */
 { static int ft = 0;                    /* firstime flag            */
   if ( ft ++ == 0 )
     i_m ();
   Set_Parity ( 1 );                     /* 8,N,1                    */
   Set_Baud ( 1200 );                    /* 1200 baud                */
 }

 #define B1200 0x80                      /* baudrate codes           */
 #define B300 0x40

 Set_Baud ( n ) int n ;                  /* n is baud rate           */
 { if ( n == 300 )
     mparm = ( mparm & 0x1F ) + B300;
   else
     if ( n == 1200 )
       mparm = ( mparm & 0x1F ) + B1200;
   else
     return 0;                           /* invalid speed            */
   sprintf ( wrk, "Baud rate = %d\r\n", n );
   cputs ( wrk );
   set_mdm ( mparm );
   return n ;
 }

 #define PAREVN 0x18                     /* MCR bits for commands    */
 #define PARODD 0x10
 #define PAROFF 0x00
 #define STOP2 0x40
 #define WORD8 0x03
 #define WORD7 0x02
 #define WORD6 0x01

 Set_Parity ( n ) int n ;                /* n is parity code         */
 { static int mmode;
   if ( n == 1 )
     mmode = ( WORD8 | PAROFF );         /* off                      */
   else
     if ( n == 2 )
       mmode = ( WORD7 | PAREVN );       /* on and even              */
   else
     if ( n == 3 )
       mmode = ( WORD7 | PARODD );       /* on and odd               */
   else
     return 0;                           /* invalid code             */
   mparm = ( mparm & 0xE0 ) + mmode;
   sprintf ( wrk, "Parity is %s\r\n", ( n == 1 ? "OFF" :
                                      ( n == 2 ? "EVEN" : "ODD" )));
   cputs ( wrk );
   set_mdm ( mparm );
   return n ;
 }

 Write_Modem ( c ) char c ;              /* return 1 if ok, else 0   */
 { wrtmdm ( c );
   return ( 1 );                         /* never any error          */
 }

 Read_Modem ()
 { return ( rdmdm ());                   /* from int bfr             */
 }

 void Term_Comm ()               /* uninstall comm port drivers      */
 { u_m ();
 }

 /* end of cterm.c */

 \SAMPCODE\DOS_ENCY\6\CH1.ASM

         TITLE   CH1.ASM

 ; CH1.ASM -- support file for CTERM.C terminal emulator
 ;       set up to work with COM2
 ;       for use with Microsoft C and SMALL model only...

 _TEXT   segment byte public 'CODE'
 _TEXT   ends
 _DATA   segment byte public 'DATA'
 _DATA   ends
 CONST   segment byte public 'CONST'
 CONST   ends
 _BSS    segment byte public 'BSS'
 _BSS    ends

 DGROUP  GROUP   CONST, _BSS, _DATA
         assume  cs:_TEXT, DS:DGROUP, ES:DGROUP, SS:DGROUP

 _TEXT   segment

         public  _i_m,_rdmdm,_Send_Byte,_wrtmdm,_set_mdm,_u_m

 bport   EQU     02F8h           ; COM2 base address, use 03F8H for COM1
 getiv   EQU     350Bh           ; COM2 vectors, use 0CH for COM1
 putiv   EQU     250Bh
 imrmsk  EQU     00001000b       ; COM2 mask, use 00000100b for COM1
 oiv_o   DW      0               ; old int vector save space
 oiv_s   DW      0

 bf_pp   DW      in_bf           ; put pointer (last used)
 bf_gp   DW      in_bf           ; get pointer (next to use)
 bf_bg   DW      in_bf           ; start of buffer
 bf_fi   DW      b_last          ; end of buffer

 in_bf   DB      512 DUP (?)     ; input buffer

 b_last  EQU     $               ; address just past buffer end

 bd_dv   DW      0417h           ; baud rate divisors (0=110 bps)
         DW      0300h           ; code 1 =  150 bps
         DW      0180h           ; code 2 =  300 bps
         DW      00C0h           ; code 3 =  600 bps
         DW      0060h           ; code 4 = 1200 bps
         DW      0030h           ; code 5 = 2400 bps
         DW      0018h           ; code 6 = 4800 bps
         DW      000Ch           ; code 7 = 9600 bps

 _set_mdm proc   near            ; replaces BIOS 'init' function
         PUSH    BP
         MOV     BP,SP           ; establish stackframe pointer
         PUSH    ES              ; save registers
         PUSH    DS
         MOV     AX,CS           ; point them to CODE segment
         MOV     DS,AX
         MOV     ES,AX
         MOV     AH,[BP+4]       ; get parameter passed by C
         MOV     DX,BPORT+3      ; point to Line Control Reg
         MOV     AL,80h          ; set DLAB bit (see text)
         OUT     DX,AL
         MOV     DL,AH           ; shift param to BAUD field
         MOV     CL,4
         ROL     DL,CL
         AND     DX,00001110b    ; mask out all other bits
         MOV     DI,OFFSET bd_dv
         ADD     DI,DX           ; make pointer to true divisor
         MOV     DX,BPORT+1      ; set to high byte first
         MOV     AL,[DI+1]
         OUT     DX,AL           ; put high byte into UART
         MOV     DX,BPORT        ; then to low byte
         MOV     AL,[DI]
         OUT     DX,AL
         MOV     AL,AH           ; now use rest of parameter
         AND     AL,00011111b    ; to set Line Control Reg
         MOV     DX,BPORT+3
         OUT     DX,AL
         MOV     DX,BPORT+2      ; Interrupt Enable Register
         MOV     AL,1            ; Receive type only
         OUT     DX,AL
         POP     DS              ; restore saved registers
         POP     ES
         MOV     SP,BP
         POP     BP
         RET
 _set_mdm endp

 _wrtmdm proc    near            ; write char to modem
 _Send_Byte:                     ; name used by main program
         PUSH    BP
         MOV     BP,SP           ; set up pointer and save regs
         PUSH    ES
         PUSH    DS
         MOV     AX,CS
         MOV     DS,AX
         MOV     ES,AX
         MOV     DX,BPORT+4      ; establish DTR, RTS, and OUT2
         MOV     AL,0Bh
         OUT     DX,AL
         MOV     DX,BPORT+6      ; check for on line, CTS
         MOV     BH,30h
         CALL    w_tmr
         JNZ     w_out           ; timed out
         MOV     DX,BPORT+5      ; check for UART ready
         MOV     BH,20h
         CALL    w_tmr
         JNZ     w_out           ; timed out
         MOV     DX,BPORT        ; send out to UART port
         MOV     AL,[BP+4]       ; get char passed from C
         OUT     DX,AL
 w_out:  POP     DS              ; restore saved regs
         POP     ES
         MOV     SP,BP
         POP     BP
         RET
 _wrtmdm endp

 _rdmdm  proc    near            ; reads byte from buffer
         PUSH    BP
         MOV     BP,SP           ; set up ptr, save regs
         PUSH    ES
         PUSH    DS
         MOV     AX,CS
         MOV     DS,AX
         MOV     ES,AX
         MOV     AX,0FFFFh       ; set for EOF flag
         MOV     BX,bf_gp        ; use "get" ptr
         CMP     BX,bf_pp        ; compare to "put"
         JZ      nochr           ; same, empty
         INC     BX              ; else char available
         CMP     BX,bf_fi        ; at end of bfr?
         JNZ     noend           ; no
         MOV     BX,bf_bg        ; yes, set to beg
 noend:  MOV     AL,[BX]         ; get the char
         MOV     bf_gp,BX        ; update "get" ptr
         INC     AH              ; zero AH as flag
 nochr:  POP     DS              ; restore regs
         POP     ES
         MOV     SP,BP
         POP     BP
         RET
 _rdmdm  endp

 w_tmr   proc    near
         MOV     BL,1            ; wait timer, double loop
 w_tm1:  SUB     CX,CX           ; set up inner loop
 w_tm2:  IN      AL,DX           ; check for requested response
         MOV     AH,AL           ; save what came in
         AND     AL,BH           ; mask with desired bits
         CMP     AL,BH           ; then compare
         JZ      w_tm3           ; got it, return with ZF set
         LOOP    w_tm2           ; else keep trying
         DEC     BL              ; until double loop expires
         JNZ     w_tm1
         OR      BH,BH           ; timed out, return NZ
 w_tm3:  RET
 w_tmr   endp

 ; hardware interrupt service routine
 rts_m:  CLI
         PUSH    DS              ; save all regs
         PUSH    AX
         PUSH    BX
         PUSH    CX
         PUSH    DX
         PUSH    CS              ; set DS same as CS
         POP     DS
         MOV     DX,BPORT        ; grab the char from UART
         IN      AL,DX
         MOV     BX,bf_pp        ; use "put" ptr
         INC     BX              ; step to next slot
         CMP     BX,bf_fi        ; past end yet?
         JNZ     nofix           ; no
         MOV     BX,bf_bg        ; yes, set to begin
 nofix:  MOV     [BX],AL         ; put char in buffer
         MOV     bf_pp,BX        ; update "put" ptr
         MOV     AL,20h          ; send EOI to 8259 chip
         OUT     20h,AL
         POP     DX              ; restore regs
         POP     CX
         POP     BX
         POP     AX
         POP     DS
         IRET

 _i_m    proc    near            ; install modem service
         PUSH    BP
         MOV     BP,SP           ; save all regs used
         PUSH    ES
         PUSH    DS
         MOV     AX,CS           ; set DS,ES=CS
         MOV     DS,AX
         MOV     ES,AX
         MOV     DX,BPORT+1      ; Interrupt Enable Reg
         MOV     AL,0Fh          ; enable all ints now
         OUT     DX,AL

 im1:    MOV     DX,BPORT+2      ; clear junk from UART
         IN      AL,DX           ; read IID reg of UART
         MOV     AH,AL           ; save what came in
         TEST    AL,1            ; anything pending?
         JNZ     im5             ; no, all clear now
         CMP     AH,0            ; yes, Modem Status?
         JNZ     im2             ; no
         MOV     DX,BPORT+6      ; yes, read MSR to clear
         IN      AL,DX
 im2:    CMP     AH,2            ; Transmit HR empty?
         JNZ     im3             ; no (no action needed)
 im3:    CMP     AH,4            ; Received Data Ready?
         JNZ     im4             ; no
         MOV     DX,BPORT        ; yes, read it to clear
         IN      AL,DX
 im4:    CMP     AH,6            ; Line Status?
         JNZ     im1             ; no, check for more
         MOV     DX,BPORT+5      ; yes, read LSR to clear
         IN      AL,DX
         JMP     im1             ; then check for more

 im5:    MOV     DX,BPORT+4      ; set up working conditions
         MOV     AL,0Bh          ; DTR, RTS, OUT2 bits
         OUT     DX,AL
         MOV     AL,1            ; enable RCV interrupt only
         MOV     DX,BPORT+1
         OUT     DX,AL
         MOV     AX,GETIV        ; get old int vector
         INT     21h
         MOV     oiv_o,BX        ; save for restoring later
         MOV     oiv_s,ES
         MOV     DX,OFFSET rts_m ; set in new one
         MOV     AX,PUTIV
         INT     21h
         IN      AL,21h          ; now enable 8259 PIC
         AND     AL,NOT IMRMSK
         OUT     21h,AL
         MOV     AL,20h          ; then send out an EOI
         OUT     20h,AL
         POP     DS              ; restore regs
         POP     ES
         MOV     SP,BP
         POP     BP
         RET
 _i_m    endp

 _u_m    proc    near            ; uninstall modem service
         PUSH    BP
         MOV     BP,SP           ; save registers
         IN      AL,21h          ; disable COM int in 8259
         OR      AL,IMRMSK
         OUT     21h,AL
         PUSH    ES
         PUSH    DS
         MOV     AX,CS           ; set same as CS
         MOV     DS,AX
         MOV     ES,AX
         MOV     AL,0            ; disable UART ints
         MOV     DX,BPORT+1
         OUT     DX,AL
         MOV     DX,oiv_o        ; restore original vector
         MOV     DS,oiv_s
         MOV     AX,PUTIV
         INT     21h
         POP     DS              ; restore registers
         POP     ES
         MOV     SP,BP
         POP     BP
         RET
 _u_m    endp

 _TEXT   ends

         END

 \SAMPCODE\DOS_ENCY\6\CH1A.ASM

         TITLE   CH1A.ASM

 ; CH1A.ASM -- support file for CTERM.C terminal emulator
 ;       this set of routines replaces Ctrl-C/Ctrl-BREAK
 ;       usage: void set_int(), rst_int();
 ;               int broke();    /* boolean if BREAK     */
 ;       for use with Microsoft C and SMALL model only...

 _TEXT   segment byte public 'CODE'
 _TEXT   ends
 _DATA   segment byte public 'DATA'
 _DATA   ends
 CONST   segment byte public 'CONST'
 CONST   ends
 _BSS    segment byte public 'BSS'
 _BSS    ends

 DGROUP  GROUP   CONST, _BSS, _DATA
         ASSUME  CS:_TEXT, DS:DGROUP, ES:DGROUP, SS:DGROUP

 _DATA   SEGMENT BYTE PUBLIC 'DATA'

 OLDINT1B DD     0               ; storage for original INT 1BH vector

 _DATA   ENDS

 _TEXT   SEGMENT

         PUBLIC  _set_int,_rst_int,_broke

 myint1b:
         mov     word ptr cs:brkflg,1Bh          ; make it nonzero
         iret

 myint23:
         mov     word ptr cs:brkflg,23h          ; make it nonzero
         iret

 brkflg  dw      0               ; flag that BREAK occurred

 _broke  proc    near            ; returns 0 if no break
         xor     ax,ax           ; prepare to reset flag
         xchg    ax,cs:brkflg    ; return current flag value
         ret
 _broke  endp

 _set_int proc near
         mov     ax,351bh        ; get interrupt vector for 1BH
         int     21h             ; (don't need to save for 23H)
         mov     word ptr oldint1b,bx     ; save offset in first word
         mov     word ptr oldint1b+2,es   ; save segment in second word

         push    ds              ; save our data segment
         mov     ax,cs           ; set DS to CS for now
         mov     ds,ax
         lea     dx,myint1b      ; DS:DX points to new routine
         mov     ax,251bh        ; set interrupt vector
         int     21h
         mov     ax,cs           ; set DS to CS for now
         mov     ds,ax
         lea     dx,myint23      ; DS:DX points to new routine
         mov     ax,2523h        ; set interrupt vector
         int     21h
         pop     ds              ; restore data segment
         ret
 _set_int endp

 _rst_int proc near
         push    ds              ; save our data segment
         lds     dx,oldint1b     ; DS:DX points to original
         mov     ax,251bh        ; set interrupt vector
         int     21h
         pop     ds              ; restore data segment
         ret
 _rst_int endp

 _TEXT   ends

         END

 \SAMPCODE\DOS_ENCY\6\CH2.ASM

         TITLE   CH2.ASM

 ; CH2.ASM -- support file for CTERM.C terminal emulator
 ;       for use with Microsoft C and SMALL model only...

 _TEXT   segment byte public 'CODE'
 _TEXT   ends
 _DATA   segment byte public 'DATA'
 _DATA   ends
 CONST   segment byte public 'CONST'
 CONST   ends
 _BSS    segment byte public 'BSS'
 _BSS    ends

 DGROUP  GROUP   CONST, _BSS, _DATA
         assume  CS:_TEXT, DS:DGROUP, ES:DGROUP, SS:DGROUP

 _TEXT   segment

         public  __cls,__color,__deol,__i_v,__key,__wrchr,__wrpos

 atrib   DB      0               ; attribute
 _colr   DB      0               ; color
 v_bas   DW      0               ; video segment
 v_ulc   DW      0               ; upper left corner cursor
 v_lrc   DW      184Fh           ; lower right corner cursor
 v_col   DW      0               ; current col/row

 __key   proc    near            ; get keystroke
         PUSH    BP
         MOV     AH,1            ; check status via BIOS
         INT     16h
         MOV     AX,0FFFFh
         JZ      key00           ; none ready, return EOF
         MOV     AH,0            ; have one, read via BIOS
         INT     16h
 key00:  POP     BP
         RET
 __key   endp

 __wrchr proc    near
         PUSH    BP
         MOV     BP,SP
         MOV     AL,[BP+4]       ; get char passed by C
         CMP     AL,' '
         JNB     prchr           ; printing char, go do it
         CMP     AL,8
         JNZ     notbs
         DEC     BYTE PTR v_col  ; process backspace
         MOV     AL,byte ptr v_col
         CMP     AL,byte ptr v_ulc
         JB      nxt_c           ; step to next column
         JMP     norml

 notbs:  CMP     AL,9
         JNZ     notht
         MOV     AL,byte ptr v_col       ; process HTAB
         ADD     AL,8
         AND     AL,0F8h
         MOV     byte ptr v_col,AL
         CMP     AL,byte ptr v_lrc
         JA      nxt_c
         JMP     SHORT   norml

 notht:  CMP     AL,0Ah
         JNZ     notlf
         MOV     AL,byte ptr v_col+1     ; process linefeed
         INC     AL
         CMP     AL,byte ptr v_lrc+1
         JBE     noht1
         CALL    scrol
         MOV     AL,byte ptr v_lrc+1
 noht1:  MOV     byte ptr v_col+1,AL
         JMP     SHORT   norml

 notlf:  CMP     AL,0Ch
         JNZ     ck_cr
         CALL    __cls           ; process formfeed
         JMP     SHORT   ignor

 ck_cr:  CMP     AL,0Dh
         JNZ     ignor           ; ignore all other CTL chars
         MOV     AL,byte ptr v_ulc       ; process CR
         MOV     byte ptr v_col,AL
         JMP     SHORT   norml

 prchr:  MOV     AH,_colr        ; process printing char
         PUSH    AX
         XOR     AH,AH
         MOV     AL,byte ptr v_col+1
         PUSH    AX
         MOV     AL,byte ptr v_col
         PUSH    AX
         CALL    wrtvr
         MOV     SP,BP
 nxt_c:  INC     BYTE PTR v_col  ; advance to next column
         MOV     AL,byte ptr v_col
         CMP     AL,byte ptr v_lrc
         JLE     norml
         MOV     AL,0Dh          ; went off end, do CR/LF
         PUSH    AX
         CALL    __wrchr
         POP     AX
         MOV     AL,0Ah
         PUSH    AX
         CALL    __wrchr
         POP     AX
 norml:  CALL    set_cur
 ignor:  MOV     SP,BP
         POP     BP
         RET
 __wrchr endp

 __i_v   proc    near            ; establish video base segment
         PUSH    BP
         MOV     BP,SP
         MOV     AX,0B000h       ; mono, B800 for CGA
         MOV     v_bas,AX        ; could be made automatic
         MOV     SP,BP
         POP     BP
         RET
 __i_v   endp

 __wrpos proc    near            ; set cursor position
         PUSH    BP
         MOV     BP,SP
         MOV     DH,[BP+4]       ; row from C program
         MOV     DL,[BP+6]       ; col from C program
         MOV     v_col,DX        ; cursor position
         MOV     BH,atrib        ; attribute
         MOV     AH,2
         PUSH    BP
         INT     10h
         POP     BP
         MOV     AX,v_col        ; return cursor position
         MOV     SP,BP
         POP     BP
         RET
 __wrpos endp

 set_cur proc    near            ; set cursor to v_col
         PUSH    BP
         MOV     BP,SP
         MOV     DX,v_col        ; use where v_col says
         MOV     BH,atrib
         MOV     AH,2
         PUSH    BP
         INT     10h
         POP     BP
         MOV     AX,v_col
         MOV     SP,BP
         POP     BP
         RET
 set_cur endp

 __color proc    near            ; _color(fg, bg)
         PUSH    BP
         MOV     BP,SP
         MOV     AH,[BP+6]       ; background from C
         MOV     AL,[BP+4]       ; foreground from C
         MOV     CX,4
         SHL     AH,CL
         AND     AL,0Fh
         OR      AL,AH           ; pack up into 1 byte
         MOV     _colr,AL        ; store for handler's use
         XOR     AH,AH
         MOV     SP,BP
         POP     BP
         RET
 __color endp

 scrol   proc    near            ; scroll CRT up by one line
         PUSH    BP
         MOV     BP,SP
         MOV     AL,1            ; count of lines to scroll
         MOV     CX,v_ulc
         MOV     DX,v_lrc
         MOV     BH,_colr
         MOV     AH,6
         PUSH    BP
         INT     10h             ; use BIOS
         POP     BP
         MOV     SP,BP
         POP     BP
         RET
 scrol   endp

 __cls   proc    near            ; clear CRT
         PUSH    BP
         MOV     BP,SP
         MOV     AL,0            ; flags CLS to BIOS
         MOV     CX,v_ulc
         MOV     v_col,CX        ; set to HOME
         MOV     DX,v_lrc
         MOV     BH,_colr
         MOV     AH,6
         PUSH    BP
         INT     10h             ; use BIOS scroll up
         POP     BP
         CALL    set_cur         ; cursor to HOME
         MOV     SP,BP
         POP     BP
         RET
 __cls   endp

 __deol  proc    near            ; delete to end of line
         PUSH    BP
         MOV     BP,SP
         MOV     AL,' '
         MOV     AH,_colr        ; set up blanks
         PUSH    AX
         MOV     AL,byte ptr v_col+1
         XOR     AH,AH           ; set up row value
         PUSH    AX
         MOV     AL,byte ptr v_col

 deol1:  CMP     AL,byte ptr v_lrc
         JA      deol2           ; at RH edge
         PUSH    AX              ; current location
         CALL    wrtvr           ; write a blank
         POP     AX
         INC     AL              ; next column
         JMP     deol1           ; do it again

 deol2:  MOV     AX,v_col        ; return cursor position
         MOV     SP,BP
         POP     BP
         RET
 __deol  endp

 wrtvr   proc    near            ; write video RAM (col, row, char/atr)
         PUSH    BP
         MOV     BP,SP           ; set up arg ptr
         MOV     DL,[BP+4]       ; column
         MOV     DH,[BP+6]       ; row
         MOV     BX,[BP+8]       ; char/atr
         MOV     AL,80           ; calc offset
         MUL     DH
         XOR     DH,DH
         ADD     AX,DX
         ADD     AX,AX           ; adjust bytes to words
         PUSH    ES              ; save seg reg
         MOV     DI,AX
         MOV     AX,v_bas        ; set up segment
         MOV     ES,AX
         MOV     AX,BX           ; get the data
         STOSW                   ; put on screen
         POP     ES              ; restore regs
         MOV     SP,BP
         POP     BP
         RET
 wrtvr   endp

 _TEXT   ends

         END

 \SAMPCODE\DOS_ENCY\6\COMDVR.ASM

 Title   COMDVR  Driver for IBM COM Ports
 ;       Jim Kyle, 1987
 ;           Based on ideas from many sources......
 ;               including Mike Higgins, CLM March 1985;
 ;               public-domain INTBIOS program from BBS's;
 ;               COMBIOS.COM from CIS Programmers' SIG; and
 ;               ADVANCED MS-DOS by Ray Duncan.
 Subttl  MS-DOS Driver Definitions

         Comment *       This comments out the Dbg macro.....
 Dbg     Macro   Ltr1,Ltr2,Ltr3  ; used only to debug driver...
         Local   Xxx
         Push    Es              ; save all regs used
         Push    Di
         Push    Ax
         Les     Di,Cs:Dbgptr    ; get pointer to CRT
         Mov     Ax,Es:[di]
         Mov     Al,Ltr1         ; move in letters
         Stosw
         Mov     Al,Ltr2
         Stosw
         Mov     Al,Ltr3
         Stosw
         Cmp     Di,1600         ; top 10 lines only
         Jb      Xxx
         Xor     Di,Di
 Xxx:    Mov     Word Ptr Cs:Dbgptr,Di
         Pop     Ax
         Pop     Di
         Pop     Es
         Endm
         *                       ; asterisk ends commented-out region
 ;
 ;               Device Type Codes
 DevChr  Equ     8000h   ; this is a character device
 DevBlk  Equ     0000h   ; this is a block (disk) device
 DevIoc  Equ     4000h   ; this device accepts IOCTL requests
 DevNon  Equ     2000h   ; non-IBM disk driver (block only)
 DevOTB  Equ     2000h   ; MS-DOS 3.x out until busy supported (char)
 DevOCR  Equ     0800h   ; MS-DOS 3.x open/close/rm supported
 DevX32  Equ     0040h   ; MS-DOS 3.2 functions supported
 DevSpc  Equ     0010h   ; accepts special interrupt 29H
 DevClk  Equ     0008h   ; this is the CLOCK device
 DevNul  Equ     0004h   ; this is the NUL device
 DevSto  Equ     0002h   ; this is standard output
 DevSti  Equ     0001h   ; this is standard input
 ;
 ;               Error Status BITS
 StsErr  Equ     8000h   ; general error
 StsBsy  Equ     0200h   ; device busy
 StsDne  Equ     0100h   ; request completed
 ;
 ;               Error Reason values for lower-order bits
 ErrWp   Equ     0       ; write protect error
 ErrUu   Equ     1       ; unknown unit
 ErrDnr  Equ     2       ; drive not ready
 ErrUc   Equ     3       ; unknown command
 ErrCrc  Equ     4       ; cyclical redundancy check error
 ErrBsl  Equ     5       ; bad drive request structure length
 ErrSl   Equ     6       ; seek error
 ErrUm   Equ     7       ; unknown media
 ErrSnf  Equ     8       ; sector not found
 ErrPop  Equ     9       ; printer out of paper
 ErrWf   Equ     10      ; write fault
 ErrRf   Equ     11      ; read fault
 ErrGf   Equ     12      ; general failure
 ;
 ;       Structure of an I/O request packet header.
 ;
 Pack    Struc
 Len     Db      ?       ; length of record
 Prtno   Db      ?       ; unit code
 Code    Db      ?       ; command code
 Stat    Dw      ?       ; return status
 Dosq    Dd      ?       ; (unused MS-DOS queue link pointer)
 Devq    Dd      ?       ; (unused driver queue link pointer)
 Media   Db      ?       ; media code on read/write
 Xfer    Dw      ?       ; xfer address offset
 Xseg    Dw      ?       ; xfer address segment
 Count   Dw      ?       ; transfer byte count
 Sector  Dw      ?       ; starting sector value (block only)
 Pack    Ends

 Subttl  IBM-PC Hardware Driver Definitions
 page
 ;
 ;               8259 data
 PIC_b   Equ     020h    ; port for EOI
 PIC_e   Equ     021h    ; port for Int enabling
 EOI     Equ     020h    ; EOI control word
 ;
 ;               8250 port offsets
 RxBuf   Equ     0F8h    ; base address
 Baud1   Equ     RxBuf+1 ; baud divisor high byte
 IntEn   Equ     RxBuf+1 ; interrupt enable register
 IntId   Equ     RxBuf+2 ; interrupt identification register
 Lctrl   Equ     RxBuf+3 ; line control register
 Mctrl   Equ     RxBuf+4 ; modem control register
 Lstat   Equ     RxBuf+5 ; line status register
 Mstat   Equ     RxBuf+6 ; modem status register
 ;
 ;               8250 LCR constants
 Dlab    Equ     10000000b ; divisor latch access bit
 SetBrk  Equ     01000000b ; send break control bit
 StkPar  Equ     00100000b ; stick parity control bit
 EvnPar  Equ     00010000b ; even parity bit
 GenPar  Equ     00001000b ; generate parity bit
 Xstop   Equ     00000100b ; extra stop bit
 Wd8     Equ     00000011b ; word length = 8
 Wd7     Equ     00000010b ; word length = 7
 Wd6     Equ     00000001b ; word length = 6
 ;
 ;               8250 LSR constants
 xsre    Equ     01000000b ; xmt SR empty
 xhre    Equ     00100000b ; xmt HR empty
 BrkRcv  Equ     00010000b ; break received
 FrmErr  Equ     00001000b ; framing error
 ParErr  Equ     00000100b ; parity error
 OveRun  Equ     00000010b ; overrun error
 rdta    Equ     00000001b ; received data ready
 AnyErr  Equ     BrkRcv+FrmErr+ParErr+OveRun
 ;
 ;               8250 MCR constants
 LpBk    Equ     00010000b ; UART out loops to in (test)
 Usr2    Equ     00001000b ; Gates 8250 interrupts
 Usr1    Equ     00000100b ; aux user1 output
 SetRTS  Equ     00000010b ; sets RTS output
 SetDTR  Equ     00000001b ; sets DTR output
 ;
 ;               8250 MSR constants
 CDlvl   Equ     10000000b ; carrier detect level
 RIlvl   Equ     01000000b ; ring indicator level
 DSRlvl  Equ     00100000b ; DSR level
 CTSlvl  Equ     00010000b ; CTS level
 CDchg   Equ     00001000b ; Carrier Detect change
 RIchg   Equ     00000100b ; Ring Indicator change
 DSRchg  Equ     00000010b ; DSR change
 CTSchg  Equ     00000001b ; CTS change
 ;
 ;               8250 IER constants
 S_Int   Equ     00001000b ; enable status interrupt
 E_Int   Equ     00000100b ; enable error interrupt
 X_Int   Equ     00000010b ; enable transmit interrupt
 R_Int   Equ     00000001b ; enable receive interrupt
 Allint  Equ     00001111b ; enable all interrupts

 Subttl  Definitions for THIS Driver
 page
 ;
 ;               Bit definitions for the output status byte
 ;                      ( this driver only )
 LinIdl  Equ     0ffh    ; if all bits off, xmitter is idle
 LinXof  Equ     1       ; output is suspended by XOFF
 LinDSR  Equ     2       ; output is suspended until DSR comes on again
 LinCTS  Equ     4       ; output is suspended until CTS comes on again
 ;
 ;               Bit definitions for the input status byte
 ;                       ( this driver only )
 BadInp  Equ     1       ; input line errors have been detected
 LostDt  Equ     2       ; receiver buffer overflowed, data lost
 OffLin  Equ     4       ; device is off line now
 ;
 ;               Bit definitions for the special characteristics words
 ;                       ( this driver only )
 ;               InSpec controls how input from the UART is treated
 ;
 InEpc   Equ     0001h   ; errors translate to codes with parity bit on
 ;
 ;               OutSpec controls how output to the UART is treated
 ;
 OutDSR  Equ     0001h   ; DSR is used to throttle output data
 OutCTS  Equ     0002h   ; CTS is used to throttle output data
 OutXon  Equ     0004h   ; XON/XOFF is used to throttle output data
 OutCdf  Equ     0010h   ; carrier detect is off-line signal
 OutDrf  Equ     0020h   ; DSR is off-line signal
 ;
 Unit    Struc           ; each unit has a structure defining its state:
 Port    Dw      ?       ; I/O port address
 Vect    Dw      ?       ; interrupt vector offset (NOT interrupt number!)
 Isradr  Dw      ?       ; offset to interrupt service routine
 OtStat  Db      Wd8     ; default LCR bit settings during INIT,
                         ; output status bits after
 InStat  Db      Usr2+SetRTS+SetDTR   ; MCR bit settings during INIT,
                         ; input status bits after
 InSpec  Dw      InEpc   ; special mode bits for INPUT
 OutSpec Dw      OutXon  ; special mode bits for OUTPUT
 Baud    Dw      96      ; current baud rate divisor value (1200 b)
 Ifirst  Dw      0       ; offset of first character in input buffer
 Iavail  Dw      0       ; offset of next available byte
 Ibuf    Dw      ?       ; pointer to input buffer
 Ofirst  Dw      0       ; offset of first character in output buffer
 Oavail  Dw      0       ; offset of next avail byte in output buffer
 Obuf    Dw      ?       ; pointer to output buffer
 Unit    Ends

 ;
 ;       Beginning of driver code and data
 ;
 Driver  Segment
         Assume  Cs:driver, ds:driver, es:driver
         Org     0               ; drivers start at 0

         Dw      Async2,-1       ; pointer to next device
         Dw      DevChr + DevIoc ; character device with IOCTL
         Dw      Strtegy         ; offset of Strategy routine
         Dw      Request1        ; offset of interrupt entry point 1
         Db      'ASY1    '      ; device 1 name
 Async2:
         Dw      -1,-1           ; pointer to next device: MS-DOS fills in
         Dw      DevChr + DevIoc ; character device with IOCTL
         Dw      Strtegy         ; offset of Strategy routine
         Dw      Request2        ; offset of interrupt entry point 2
         Db      'ASY2    '      ; device 2 name

 ;dbgptr dd      0b0000000h
 ;
 ;         Following is the storage area for the request packet pointer
 ;
 PackHd  Dd      0
 ;
 ;         baud rate conversion table
 Asy_baudt Dw           50,2304          ; first value is desired baud rate
           Dw           75,1536          ; second is divisor register value
           Dw          110,1047
           Dw          134, 857
           Dw          150, 786
           Dw          300, 384
           Dw          600, 192
           Dw         1200,  96
           Dw         1800,  64
           Dw         2000,  58
           Dw         2400,  48
           Dw         3600,  32
           Dw         4800,  24
           Dw         7200,  16
           Dw         9600,  12

 ; table of structures
 ;       ASY1 defaults to the COM1 port, INT 0CH vector, XON,
 ;       no parity, 8 databits, 1 stop bit, and 1200 baud
 Asy_tab1:
         Unit    <3f8h,30h,asy1isr,,,,,,,,in1buf,,,out1buf>

 ;       ASY2 defaults to the COM2 port, INT 0BH vector, XON,
 ;       no parity, 8 databits, 1 stop bit, and 1200 baud
 Asy_tab2:
         Unit    <2f8h,2ch,asy2isr,,,,,,,,in2buf,,,out2buf>

 Bufsiz  Equ     256        ; input buffer size
 Bufmsk  =       Bufsiz-1   ; mask for calculating offsets modulo bufsiz
 In1buf  Db      Bufsiz DUP (?)
 Out1buf Db      Bufsiz DUP (?)
 In2buf  Db      Bufsiz DUP (?)
 Out2buf Db      Bufsiz DUP (?)
 ;
 ;               Following is a table of offsets to all the driver functions

 Asy_funcs:
         Dw      Init            ;  0 initialize driver
         Dw      Mchek           ;  1 media check (block only)
         Dw      BldBPB          ;  2 build BPB (block only)
         Dw      Ioctlin         ;  3 IOCTL read
         Dw      Read            ;  4 read
         Dw      Ndread          ;  5 nondestructive read
         Dw      Rxstat          ;  6 input status
         Dw      Inflush         ;  7 flush input buffer
         Dw      Write           ;  8 write
         Dw      Write           ;  9 write with verify
         Dw      Txstat          ; 10 output status
         Dw      Txflush         ; 11 flush output buffer
         Dw      Ioctlout        ; 12 IOCTL write
 ; Following are not used in this driver.....
         Dw      Zexit           ; 13 open (3.x only, not used)
         Dw      Zexit           ; 14 close (3.x only, not used)
         Dw      Zexit           ; 15 rem med (3.x only, not used)
         Dw      Zexit           ; 16 out until bsy (3.x only, not used)
         Dw      Zexit           ; 17
         Dw      Zexit           ; 18
         Dw      Zexit           ; 19 generic IOCTL request (3.2 only)
         Dw      Zexit           ; 20
         Dw      Zexit           ; 21
         Dw      Zexit           ; 22
         Dw      Zexit           ; 23 get logical drive map (3.2 only)
         Dw      Zexit           ; 24 set logical drive map (3.2 only)

 Subttl  Driver Code
 Page
 ;
 ;       The Strategy routine itself:
 ;
 Strtegy Proc    Far
 ;       dbg     'S','R',' '
         Mov     Word Ptr CS:PackHd,BX   ; store the offset
         Mov     Word Ptr CS:PackHd+2,ES ; store the segment
         Ret
 Strtegy Endp
 ;
 Request1:                       ; async1 has been requested
         Push    Si              ; save SI
         Lea     Si,Asy_tab1     ; get the device unit table address
         Jmp     Short   Gen_request

 Request2:                       ; async2 has been requested
         Push    Si              ; save SI
         Lea     Si,Asy_tab2     ; get unit table two's address

 Gen_request:
 ;       dbg     'R','R',' '
         Pushf                   ; save all regs
         Cld
         Push    Ax
         Push    Bx
         Push    Cx
         Push    Dx
         Push    Di
         Push    Bp
         Push    Ds
         Push    Es
         Push    Cs              ; set DS = CS
         Pop     Ds
         Les     Bx,PackHd       ; get packet pointer
         Lea     Di,Asy_funcs    ; point DI to jump table
         Mov     Al,es:code[bx]  ; command code
         Cbw
         Add     Ax,Ax           ; double to word
         Add     Di,ax
         Jmp     [di]            ; go do it
 ;
 ;       Exit from driver request
 ;
 ExitP   Proc    Far
 Bsyexit:
         Mov     Ax,StsBsy
         Jmp     Short   Exit

 Mchek:
 BldBPB:
 Zexit:  Xor     Ax,Ax
 Exit:   Les     Bx,PackHd       ; get packet pointer
         Or      Ax,StsDne
         Mov     Es:Stat[Bx],Ax  ; set return status
         Pop     Es              ; restore registers
         Pop     Ds
         Pop     Bp
         Pop     Di
         Pop     Dx
         Pop     Cx
         Pop     Bx
         Pop     Ax
         Popf
         Pop     Si
         Ret
 ExitP   Endp

 Subttl  Driver Service Routines
 Page

 ;       Read data from device

 Read:
 ;       dbg     'R','d',' '
         Mov     Cx,Es:Count[bx] ; get requested nbr
         Mov     Di,Es:Xfer[bx]  ; get target pointer
         Mov     Dx,Es:Xseg[bx]
         Push    Bx              ; save for count fixup
         Push    Es
         Mov     Es,Dx
         Test    InStat[si],BadInp Or LostDt
         Je      No_lerr         ; no error so far...
         Add     Sp,4            ; error, flush SP
         And     InStat[si],Not ( BadInp Or LostDt )
         Mov     Ax,ErrRf        ; error, report it
         Jmp     Exit
 No_lerr:
         Call    Get_in          ; go for one
         Or      Ah,Ah
         Jnz     Got_all         ; none to get now
         Stosb                   ; store it
         Loop    No_lerr         ; go for more
 Got_all:
         Pop     Es
         Pop     Bx
         Sub     Di,Es:Xfer[bx]  ; calc number stored
         Mov     Es:Count[bx],Di ; return as count
         Jmp     Zexit

 ;       Nondestructive read from device

 Ndread:
         Mov     Di,ifirst[si]
         Cmp     Di,iavail[si]
         Jne     Ndget
         Jmp     Bsyexit         ; buffer empty
 Ndget:
         Push    Bx
         Mov     Bx,ibuf[si]
         Mov     Al,[bx+di]
         Pop     Bx
         Mov     Es:media[bx],al ; return char
         Jmp     Zexit

 ;       Input status request

 Rxstat:
         Mov     Di,ifirst[si]
         Cmp     Di,iavail[si]
         Jne     Rxful
         Jmp     Bsyexit         ; buffer empty
 Rxful:
         Jmp     Zexit           ; have data

 ;       Input flush request

 Inflush:
         Mov     Ax,iavail[si]
         Mov     Ifirst[si],ax
         Jmp     Zexit

 ;       Output data to device

 Write:
 ;       dbg     'W','r',' '
         Mov     Cx,es:count[bx]
         Mov     Di,es:xfer[bx]
         Mov     Ax,es:xseg[bx]
         Mov     Es,ax
 Wlup:
         Mov     Al,es:[di]      ; get the byte
         Inc     Di
 Wwait:
         Call    Put_out         ; put away
         Cmp     Ah,0
         Jne     Wwait           ; wait for room!
         Call    Start_output    ; get it going
         Loop    Wlup

         Jmp     Zexit

 ;       Output status request

 Txstat:
         Mov     Ax,ofirst[si]
         Dec     Ax
         And     Ax,bufmsk
         Cmp     Ax,oavail[si]
         Jne     Txroom
         Jmp     Bsyexit         ; buffer full
 Txroom:
         Jmp     Zexit           ; room exists

 ;       IOCTL read request, return line parameters

 Ioctlin:
         Mov     Cx,es:count[bx]
         Mov     Di,es:xfer[bx]
         Mov     Dx,es:xseg[bx]
         Mov     Es,dx
         Cmp     Cx,10
         Je      Doiocin
         Mov     Ax,errbsl
         Jmp     Exit
 Doiocin:
         Mov     Dx,port[si]     ; base port
         Mov     Dl,Lctrl        ; line status
         Mov     Cx,4            ; LCR, MCR, LSR, MSR
 Getport:
         In      Al,dx
         Stos    Byte Ptr [DI]
         Inc     Dx
         Loop    Getport

         Mov     Ax,InSpec[si]   ; spec in flags
         Stos    Word Ptr [DI]
         Mov     Ax,OutSpec[si]  ; out flags
         Stos    Word Ptr [DI]
         Mov     Ax,baud[si]     ; baud rate
         Mov     Bx,di
         Mov     Di,offset Asy_baudt+2
         Mov     Cx,15
 Baudcin:
         Cmp     [di],ax
         Je      Yesinb
         Add     Di,4
         Loop    Baudcin
 Yesinb:
         Mov     Ax,-2[di]
         Mov     Di,bx
         Stos    Word Ptr [DI]
         Jmp     Zexit

 ;       Flush output buffer request

 Txflush:
         Mov     Ax,oavail[si]
         Mov     Ofirst[si],ax
         Jmp     Zexit

 ;       IOCTL request: change line parameters for this driver

 Ioctlout:
         Mov     Cx,es:count[bx]
         Mov     Di,es:xfer[bx]
         Mov     Dx,es:xseg[bx]
         Mov     Es,dx
         Cmp     Cx,10
         Je      Doiocout
         Mov     Ax,errbsl
         Jmp     Exit

 Doiocout:
         Mov     Dx,port[si]     ; base port
         Mov     Dl,Lctrl        ; line ctrl
         Mov     Al,es:[di]
         Inc     Di
         Or      Al,Dlab         ; set baud
         Out     Dx,al
         Clc
         Jnc     $+2
         Inc     Dx              ; mdm ctrl
         Mov     Al,es:[di]
         Or      Al,Usr2         ; Int Gate
         Out     Dx,al
         Add     Di,3            ; skip LSR,MSR
         Mov     Ax,es:[di]
         Add     Di,2
         Mov     InSpec[si],ax
         Mov     Ax,es:[di]
         Add     Di,2
         Mov     OutSpec[si],ax
         Mov     Ax,es:[di]      ; set baud
         Mov     Bx,di
         Mov     Di,offset Asy_baudt
         Mov     Cx,15
 Baudcout:
         Cmp     [di],ax
         Je      Yesoutb
         Add     Di,4
         Loop    Baudcout

         Mov     Dl,Lctrl        ; line ctrl
         In      Al,dx           ; get LCR data
         And     Al,not Dlab     ; strip
         Clc
         Jnc     $+2
         Out     Dx,al           ; put back
         Mov     Ax,ErrUm        ; "unknown media"
         Jmp     Exit

 Yesoutb:
         Mov     Ax,2[di]        ; get divisor
         Mov     Baud[si],ax     ; save to report later
         Mov     Dx,port[si]     ; set divisor
         Out     Dx,al
         Clc
         Jnc     $+2
         Inc     Dx
         Mov     Al,ah
         Out     Dx,al
         Clc
         Jnc     $+2
         Mov     Dl,Lctrl        ; line ctrl
         In      Al,dx           ; get LCR data
         And     Al,not Dlab     ; strip
         Clc
         Jnc     $+2
         Out     Dx,al           ; put back
         Jmp     Zexit

 Subttl  Ring Buffer Routines
 Page

 Put_out Proc    Near    ; puts AL into output ring buffer
         Push    Cx
         Push    Di
         Pushf
         Cli
         Mov     Cx,oavail[si]   ; put ptr
         Mov     Di,cx
         Inc     Cx              ; bump
         And     Cx,bufmsk
         Cmp     Cx,ofirst[si]   ; overflow?
         Je      Poerr           ; yes, don't
         Add     Di,obuf[si]     ; no
         Mov     [di],al         ; put in buffer
         Mov     Oavail[si],cx
 ;       dbg     'p','o',' '
         Mov     Ah,0
         Jmp     Short   Poret
 Poerr:
         Mov     Ah,-1
 Poret:
         Popf
         Pop     Di
         Pop     Cx
         Ret
 Put_out Endp

 Get_out Proc    Near    ; gets next character from output ring buffer
         Push    Cx
         Push    Di
         Pushf
         Cli
         Mov     Di,ofirst[si]   ; get ptr
         Cmp     Di,oavail[si]   ; put ptr
         Jne     Ngoerr
         Mov     Ah,-1           ; empty
         Jmp     Short   Goret
 Ngoerr:
 ;       dbg     'g','o',' '
         Mov     Cx,di
         Add     Di,obuf[si]
         Mov     Al,[di]         ; get char
         Mov     Ah,0
         Inc     Cx              ; bump ptr
         And     Cx,bufmsk       ; wrap
         Mov     Ofirst[si],cx
 Goret:
         Popf
         Pop     Di
         Pop     Cx
         Ret
 Get_out Endp

 Put_in  Proc    Near    ; puts the char from AL into input ring buffer
         Push    Cx
         Push    Di
         Pushf
         Cli
         Mov     Di,iavail[si]
         Mov     Cx,di
         Inc     Cx
         And     Cx,bufmsk
         Cmp     Cx,ifirst[si]
         Jne     Npierr
         Mov     Ah,-1
         Jmp     Short   Piret
 Npierr:
         Add     Di,ibuf[si]
         Mov     [di],al
         Mov     Iavail[si],cx
 ;       dbg     'p','i',' '
         Mov     Ah,0
 Piret:
         Popf
         Pop     Di
         Pop     Cx
         Ret
 Put_in  Endp

 Get_in  Proc    Near    ; gets one from input ring buffer into AL
         Push    Cx
         Push    Di
         Pushf
         Cli
         Mov     Di,ifirst[si]
         Cmp     Di,iavail[si]
         Je      Gierr
         Mov     Cx,di
         Add     Di,ibuf[si]
         Mov     Al,[di]
         Mov     Ah,0
 ;       dbg     'g','i',' '
         Inc     Cx
         And     Cx,bufmsk
         Mov     Ifirst[si],cx
         Jmp     Short   Giret
 Gierr:
         Mov     Ah,-1
 Giret:
         Popf
         Pop     Di
         Pop     Cx
         Ret
 Get_in  Endp

 Subttl  Interrupt Dispatcher Routine
 Page

 Asy1isr:
         Sti
         Push    Si
         Lea     Si,asy_tab1
         Jmp     Short   Int_serve

 Asy2isr:
         Sti
         Push    Si
         Lea     Si,asy_tab2

 Int_serve:
         Push    Ax              ; save all regs
         Push    Bx
         Push    Cx
         Push    Dx
         Push    Di
         Push    Ds
         Push    Cs              ; set DS = CS
         Pop     Ds
 Int_exit:
 ;       dbg     'I','x',' '
         Mov     Dx,Port[si]     ; base address
         Mov     Dl,IntId        ; check Int ID
         In      Al,Dx
         Cmp     Al,00h          ; dispatch filter
         Je      Int_modem
         Jmp     Int_mo_no
 Int_modem:
 ;       dbg     'M','S',' '
         Mov     Dl,Mstat
         In      Al,dx           ; read MSR content
         Test    Al,CDlvl        ; carrier present?
         Jnz     Msdsr           ; yes, test for DSR
         Test    OutSpec[si],OutCdf      ; no, is CD off line?
         Jz      Msdsr
         Or      InStat[si],OffLin
 Msdsr:
         Test    Al,DSRlvl       ; DSR present?
         Jnz     Dsron           ; yes, handle it
         Test    OutSpec[si],OutDSR      ; no, is DSR throttle?
         Jz      Dsroff
         Or      OtStat[si],LinDSR       ; yes, throttle down
 Dsroff:
         Test    OutSpec[si],OutDrf      ; is DSR off line?
         Jz      Mscts
         Or      InStat[si],OffLin       ; yes, set flag
         Jmp     Short   Mscts
 Dsron:
         Test    OtStat[si],LinDSR       ; throttled for DSR?
         Jz      Mscts
         Xor     OtStat[si],LinDSR       ; yes, clear it out
         Call    Start_output
 Mscts:
         Test    Al,CTSlvl       ; CTS present?
         Jnz     Ctson           ; yes, handle it
         Test    OutSpec[si],OutCTS      ; no, is CTS throttle?
         Jz      Int_exit2
         Or      OtStat[si],LinCTS       ; yes, shut it down
         Jmp     Short   Int_exit2
 Ctson:
         Test    OtStat[si],LinCTS       ; throttled for CTS?
         Jz      Int_exit2
         Xor     OtStat[si],LinCTS       ; yes, clear it out
         Jmp     Short   Int_exit1
 Int_mo_no:
         Cmp     Al,02h
         Jne     Int_tx_no
 Int_txmit:
 ;       dbg     'T','x',' '
 Int_exit1:
         Call    Start_output    ; try to send another
 Int_exit2:
         Jmp     Int_exit
 Int_tx_no:
         Cmp     Al,04h
         Jne     Int_rec_no
 Int_receive:
 ;       dbg     'R','x',' '
         Mov     Dx,port[si]
         In      Al,dx           ; take char from 8250
         Test    OutSpec[si],OutXon  ; is XON/XOFF enabled?
         Jz      Stuff_in        ; no
         Cmp     Al,'S' And 01FH ; yes, is this XOFF?
         Jne     Isq             ; no, check for XON
         Or      OtStat[si],LinXof ; yes, disable output
         Jmp     Int_exit2       ; don't store this one
 Isq:
         Cmp     Al,'Q' And 01FH ; is this XON?
         Jne     Stuff_in        ; no, save it
         Test    OtStat[si],LinXof ; yes, waiting?
         Jz      Int_exit2       ; no, ignore it
         Xor     OtStat[si],LinXof ; yes, clear the XOFF bit
         Jmp     Int_exit1       ; and try to resume xmit
 Int_rec_no:
         Cmp     Al,06h
         Jne     Int_done
 Int_rxstat:
 ;       dbg     'E','R',' '
         Mov     Dl,Lstat
         In      Al,dx
         Test    InSpec[si],InEpc ; return them as codes?
         Jz      Nocode          ; no, just set error alarm
         And     Al,AnyErr       ; yes, mask off all but error bits
         Or      Al,080h
 Stuff_in:
         Call    Put_in          ; put input char in buffer
         Cmp     Ah,0            ; did it fit?
         Je      Int_exit3       ; yes, all OK
         Or      InStat[si],LostDt  ; no, set DataLost bit
 Int_exit3:
         Jmp     Int_exit
 Nocode:
         Or      InStat[si],BadInp
         Jmp     Int_exit3
 Int_done:
         Clc
         Jnc     $+2
         Mov     Al,EOI          ; all done now
         Out     PIC_b,Al
         Pop     Ds              ; restore regs
         Pop     Di
         Pop     Dx
         Pop     Cx
         Pop     Bx
         Pop     Ax
         Pop     Si
         Iret

 Start_output    Proc    Near
         Test    OtStat[si],LinIdl ; Blocked?
         Jnz     Dont_start      ; yes, no output
         Mov     Dx,port[si]     ; no, check UART
         Mov     Dl,Lstat
         In      Al,Dx
         Test    Al,xhre         ; empty?
         Jz      Dont_start      ; no
         Call    Get_out         ; yes, anything waiting?
         Or      Ah,Ah
         Jnz     Dont_start      ; no
         Mov     Dl,RxBuf        ; yes, send it out
         Out     Dx,al
 ;       dbg     's','o',' '
 Dont_start:
         ret
 Start_output    Endp

 Subttl  Initialization Request Routine
 Page

 Init:   Lea     Di,$            ; release rest...
         Mov     Es:Xfer[bx],Di
         Mov     Es:Xseg[bx],Cs

         Mov     Dx,Port[si]     ; base port
         Mov     Dl,Lctrl
         Mov     Al,Dlab         ; enable divisor
         Out     Dx,Al
         Clc
         Jnc     $+2
         Mov     Dl,RxBuf
         Mov     Ax,Baud[si]     ; set baud
         Out     Dx,Al
         Clc
         Jnc     $+2
         Inc     Dx
         Mov     Al,Ah
         Out     Dx,Al
         Clc
         Jnc     $+2

         Mov     Dl,Lctrl        ; set LCR
         Mov     Al,OtStat[si]   ; from table
         Out     Dx,Al
         Mov     OtStat[si],0    ; clear status
         Clc
         Jnc     $+2
         Mov     Dl,IntEn        ; IER
         Mov     Al,AllInt       ; enable ints in 8250
         Out     Dx,Al
         Clc
         Jnc     $+2
         Mov     Dl,Mctrl        ; set MCR
         Mov     Al,InStat[si]   ; from table
         Out     Dx,Al
         Mov     InStat[si],0    ; clear status

 ClRgs:  Mov     Dl,Lstat        ; clear LSR
         In      Al,Dx
         Mov     Dl,RxBuf        ; clear RX reg
         In      Al,Dx
         Mov     Dl,Mstat        ; clear MSR
         In      Al,Dx
         Mov     Dl,IntId        ; IID reg
         In      Al,Dx
         In      Al,Dx
         Test    Al,1            ; int pending?
         Jz      ClRgs           ; yes, repeat

         Cli
         Xor     Ax,Ax           ; set int vec
         Mov     Es,Ax
         Mov     Di,Vect[si]
         Mov     Ax,IsrAdr[si]   ; from table
         Stosw
         Mov     Es:[di],cs

         In      Al,PIC_e        ; get 8259
         And     Al,0E7h         ; com1/2 mask
         Clc
         Jnb     $+2
         Out     PIC_e,Al
         Sti

         Mov     Al,EOI          ; now send EOI just in case
         Out     PIC_b,Al

 ;       dbg     'D','I',' '     ; driver installed
         Jmp     Zexit

 Driver  Ends
         End

 \SAMPCODE\DOS_ENCY\6\ENGINE.ASM

         TITLE   engine

 CODE    SEGMENT PUBLIC 'CODE'

         ASSUME  CS:CODE,DS:CODE,ES:CODE,SS:CODE

         ORG     0100h

 START:  mov     dx,offset devnm ; open named device (ASY1)
         mov     ax,3d02h
         int     21h
         mov     handle,ax       ; save the handle
         jc      quit
 alltim: call    getmdm          ; main engine loop
         call    putcrt
         call    getkbd
         call    putmdm
         jmp     alltim
 quit:   mov     ah,4ch          ; come here to quit
         int     21h

 getmdm  proc                    ; get input from modem
         mov     cx,256
         mov     bx,handle
         mov     dx,offset mbufr
         mov     ax,3F00h
         int     21h
         jc      quit
         mov     mdlen,ax
         ret
 getmdm  endp

 getkbd  proc                    ; get input from keyboard
         mov     kblen,0         ; first zero the count
         mov     ah,11           ; key pressed?
         int     21h
         inc     al
         jnz     nogk            ; no
         mov     ah,7            ; yes, get it
         int     21h
         cmp     al,3            ; was it Ctrl-C?
         je      quit            ; yes, get out
         mov     kbufr,al        ; no, save it
         inc     kblen
         cmp     al,13           ; was it Enter?
         jne     nogk            ; no
         mov     byte ptr kbufr+1,10 ; yes, add LF
         inc     kblen
 nogk:   ret
 getkbd  endp

 putmdm  proc                    ; put output to modem
         mov     cx,kblen
         jcxz    nopm
         mov     bx,handle
         mov     dx,offset kbufr
         mov     ax,4000h
         int     21h
         jc      quit
 nopm:   ret
 putmdm  endp

 putcrt  proc                    ; put output to CRT
         mov     cx,mdlen
         jcxz    nopc
         mov     bx,1
         mov     dx,offset mbufr
         mov     ah,40h
         int     21h
         jc      quit
 nopc:   ret
 putcrt  endp

 devnm   db      'ASY1',0        ; miscellaneous data and buffers
 handle  dw      0
 kblen   dw      0
 mdlen   dw      0
 mbufr   db      256 dup (0)
 kbufr   db      80 dup (0)

 CODE    ENDS
         END     START

 \SAMPCODE\DOS_ENCY\6\ENGINE_1.ASM

 ;Incomplete (and Unworkable) Implementation

 LOOP:   MOV     AH,08h          ; read keyboard, no echo
         INT     21h
         MOV     DL,AL           ; set up to send
         MOV     AH,04h          ; send to AUX device
         INT     21h
         MOV     AH,03h          ; read from AUX device
         INT     21h
         MOV     DL,AL           ; set up to send
         MOV     AH,02h          ; send to screen
         INT     21h
         JMP     LOOP            ; keep doing it

 \SAMPCODE\DOS_ENCY\6\ENGINE_2.ASM

 ;Improved, (but Still Unworkable) Implementation

 LOOP:   MOV     AH,0Bh          ; test keyboard for char
         INT     21h
         OR      AL,AL           ; test for zero
         JZ      RMT             ; no char avail, skip
         MOV     AH,08h          ; have char, read it in
         INT     21h
         MOV     DL,AL           ; set up to send
         MOV     AH,04h          ; send to AUX device
         INT     21h
 RMT:
         MOV     AH,03h          ; read from AUX device
         INT     21h
         MOV     DL,AL           ; set up to send
         MOV     AH,02h          ; send to screen
         INT     21h
         JMP     LOOP            ; keep doing it

 \SAMPCODE\DOS_ENCY\6\UART.ASM

         MOV     DX,03FBh        ; base port COM1 (03F8) + LCR (3)
         MOV     AL,080h         ; enable Divisor Latch
         OUT     DX,AL
         MOV     DX,03F8h        ; set for Baud0
         MOV     AX,96           ; set divisor to 1200 BPS
         OUT     DX,AL
         INC     DX              ; to offset 1 for Baud1
         MOV     AL,AH           ; high byte of divisor
         OUT     DX,AL
         MOV     DX,03FBh        ; back to the LCR offset
         MOV     AL,03           ; DLAB = 0, Parity = N, WL = 8
         OUT     DX,AL
         MOV     DX,03F9h        ; offset 1 for IER
         MOV     AL,0Fh          ; enable all ints in 8250
         OUT     DX,AL
         MOV     DX,03FCh        ; COM1 + MCR (4)
         MOV     AL,0Bh          ; OUT2 + RTS + DTR bits
         OUT     DX,AL
 CLRGS:
         MOV     DX,03FDh        ; clear LSR
         IN      AL,DX
         MOV     DX,03F8h        ; clear RX reg
         IN      AL,DX
         MOV     DX,03FEh        ; clear MSR
         IN      AL,DX
         MOV     DX,03FAh        ; IID reg
         IN      AL,DX
         IN      AL,DX           ; repeat to be sure
         TEST    AL,1            ; int pending?
         JZ      CLRGS           ; yes, repeat

 \SAMPCODE\DOS_ENCY\7
 \SAMPCODE\DOS_ENCY\7\CMDTAIL.ASM

 cmdtail equ     80h             ; PSP offset of command tail
 fname   db      64 dup (?)
 fhandle dw      ?

         .
         .
         .
                                 ; assume that DS already
                                 ; contains segment of PSP

                                 ; prepare to copy filename...
         mov     si,cmdtail      ; DS:SI = command tail
         mov     di,seg fname    ; ES:DI = buffer to receive
         mov     es,di           ; filename from command tail
         mov     di,offset fname
         cld                     ; safety first!

         lodsb                   ; check length of command tail
         or      al,al
         jz      error           ; jump, command tail empty

 label1:                         ; scan off leading spaces
         lodsb                   ; get next character
         cmp     al,20h          ; is it a space?
         jz      label1          ; yes, skip it

 label2:
         cmp     al,0dh          ; look for terminator
         jz      label3          ; quit if return found
         cmp     al,20h
         jz      label3          ; quit if space found
         stosb                   ; else copy this character
         lodsb                   ; get next character
         jmp     label2

 label3:
         xor     al,al           ; store final NULL to
         stosb                   ; create ASCIIZ string

                                 ; now open the file...
         mov     dx,seg fname    ; DS:DX = address of
         mov     ds,dx           ; pathname for file
         mov     dx,offset fname
         mov     ax,3d02h        ; Function 3DH = open r/w
         int     21h             ; transfer to MS-DOS
         jnc     label4          ; jump if file found

         cmp     ax,2            ; error 2 = file not found
         jnz     error           ; jump if other error
                                 ; else make the file...
         xor     cx,cx           ; CX = normal attribute
         mov     ah,3ch          ; Function 3CH = create
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if create failed

 label4:
         mov     fhandle,ax      ; save handle for file
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\COPYFILE.ASM

 fcb1    db      0               ; drive = default
         db      'MYFILE  '      ; 8 character filename
         db      'DAT'           ; 3 character extension
         db      25 dup (0)      ; remainder of fcb1
 fcb2    db      0               ; drive = default
         db      'MYFILE  '      ; 8 character filename
         db      'BAK'           ; 3 character extension
         db      25 dup (0)      ; remainder of fcb2
 buff    db      512 dup (?)     ; buffer for file I/O
         .
         .
         .
                                 ; open MYFILE.DAT...
         mov     dx,seg fcb1     ; DS:DX = address of FCB
         mov     ds,dx
         mov     dx,offset fcb1
         mov     ah,0fh          ; Function 0FH = open
         int     21h             ; transfer to MS-DOS
         or      al,al           ; did open succeed?
         jnz     error           ; jump if open failed
                                 ; create MYFILE.BAK...
         mov     dx,offset fcb2  ; DS:DX = address of FCB
         mov     ah,16h          ; Function 16H = create
         int     21h             ; transfer to MS-DOS
         or      al,al           ; did create succeed?
         jnz     error           ; jump if create failed
                                 ; set record length to 512
         mov     word ptr fcb1+0eh,512
         mov     word ptr fcb2+0eh,512
                                 ; set DTA to our buffer...
         mov     dx,offset buff  ; DS:DX = buffer address
         mov     ah,1ah          ; Function 1AH = set DTA
         int     21h             ; transfer to MS-DOS
 loop:                           ; read MYFILE.DAT
         mov     dx,offset fcb1  ; DS:DX = FCB address
         mov     ah,14h          ; Function 14H = seq. read
         int     21h             ; transfer to MS-DOS
         or      al,al           ; was read successful?
         jnz     done            ; no, quit
                                 ; write MYFILE.BAK...
         mov     dx,offset fcb2  ; DS:DX = FCB address
         mov     ah,15h          ; Function 15H = seq. write
         int     21h             ; transfer to MS-DOS
         or      al,al           ; was write successful?
         jnz     error           ; jump if write failed
         jmp     loop            ; continue to end of file
 done:                           ; now close files...
         mov     dx,offset fcb1  ; DS:DX = FCB for MYFILE.DAT
         mov     ah,10h          ; Function 10H = close file
         int     21h             ; transfer to MS-DOS
         or      al,al           ; did close succeed?
         jnz     error           ; jump if close failed
         mov     dx,offset fcb2  ; DS:DX = FCB for MYFILE.BAK
         mov     ah,10h          ; Function 10H = close file
         int     21h             ; transfer to MS-DOS
         or      al,al           ; did close succeed?
         jnz     error           ; jump if close failed
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\FXN13H.ASM

 delfcb  db      0               ; default drive
         db      'A???????'      ; wildcard filename
         db      'BAK'           ; extension
         db      25 dup (0)      ; remainder of FCB

         .
         .
         .
         mov     dx,seg delfcb   ; DS:DX = FCB address
         mov     ds,dx
         mov     dx,offset delfcb
         mov     ah,13h          ; Function 13H = delete
         int     21h             ; transfer to MS-DOS
         or      al,al           ; did function succeed?
         jnz     error           ; jump if delete failed
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\FXN17H.ASM

 renfcb  db      0               ; default drive
         db      '????????'      ; wildcard filename
         db      'ASM'           ; old extension
         db      5 dup (0)       ; reserved area
         db      '????????'      ; wildcard filename
         db      'COD'           ; new extension
         db      15 dup (0)      ; remainder of FCB

         .
         .
         .
         mov     dx,seg renfcb   ; DS:DX = address of
         mov     ds,dx           ; "special" FCB
         mov     dx,offset renfcb
         mov     ah,17h          ; Function 17H = rename
         int     21h             ; transfer to MS-DOS
         or      al,al           ; did function succeed?
         jnz     error           ; jump if rename failed
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\FXN3CH.ASM

 fname   db      'C:\LETTERS\MEMO.TXT',0
 fhandle dw      ?
         .
         .
         .
         mov     dx,seg fname    ; DS:DX = address of
         mov     ds,dx           ; pathname for file
         mov     dx,offset fname
         xor     cx,cx           ; CX = normal attribute
         mov     ah,3ch          ; Function 3CH = create
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if create failed
         mov     fhandle,ax      ; else save file handle
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\FXN41H.ASM

 fname   db      'C:\WORK\MYFILE.DAT',0
         .
         .
         .
         mov     dx,seg fname    ; DS:DX = address of filename
         mov     ds,dx
         mov     dx,offset fname
         mov     ah,41h          ; Function 41H = delete
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if delete failed
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\FXN43H.ASM

 fname   db      'C:\BACKUP\MYFILE.DAT',0
         .
         .
         .
         mov     dx,seg fname    ; DS:DX = address of filename
         mov     ds,dx
         mov     dx,offset fname
         mov     cx,1            ; CX = attribute (read-only)
         mov     al,1            ; AL = mode (0 = get, 1 = set)
         mov     ah,43h          ; Function 43H = get/set attr
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if set attrib. failed
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\FXN45H.ASM

 fhandle dw      ?               ; handle from previous open

         .
         .
         .
                                 ; duplicate the handle...
         mov     bx,fhandle      ; BX = handle for file
         mov     ah,45h          ; Function 45H = dup handle
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if function call failed

                                 ; now close the new handle...
         mov     bx,ax           ; BX = duplicated handle
         mov     ah,3eh          ; Function 3EH = close
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if close failed
         mov     bx,fhandle      ; replace closed handle with active handle
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\FXN56H.ASM

 file1   db      '\WORK\MYFILE.DAT',0
 file2   db      '\BACKUP\MYFILE.OLD',0
         .
         .
         .
         mov     dx,seg file1    ; DS:DX = old filename
         mov     ds,dx
         mov     es,dx
         mov     dx,offset file1
         mov     di,offset file2 ; ES:DI = new filename
         mov     ah,56h          ; Function 56H = rename
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if rename failed
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\FXN5AH.ASM

 fname   db      'C:\TEMP\'      ; generated ASCIIZ filename
         db      13 dup (0)      ; is appended by MS-DOS

 fhandle dw      ?
         .
         .
         .
         mov     dx,seg fname    ; DS:DX = address of
         mov     ds,dx           ; path for temporary file
         mov     dx,offset fname
         xor     cx,cx           ; CX = normal attribute
         mov     ah,5ah          ; Function 5AH = create
                                 ; temporary file
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if create failed
         mov     fhandle,ax      ; else save file handle
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\MYFILE.ASM

 file1   db      'MYFILE.DAT',0
 file2   db      'MYFILE.BAK',0

 handle1 dw      ?               ; handle for MYFILE.DAT
 handle2 dw      ?               ; handle for MYFILE.BAK

 buff    db      512 dup (?)     ; buffer for file I/O
         .
         .
         .
                                 ; open MYFILE.DAT...
         mov     dx,seg file1    ; DS:DX = address of filename
         mov     ds,dx
         mov     dx,offset file1
         mov     ax,3d00h        ; Function 3DH = open (read-only)
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if open failed
         mov     handle1,ax      ; save handle for file

                                 ; create MYFILE.BAK...
         mov     dx,offset file2 ; DS:DX = address of filename
         mov     cx,0            ; CX = normal attribute
         mov     ah,3ch          ; Function 3CH = create
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if create failed
         mov     handle2,ax      ; save handle for file

 loop:                           ; read MYFILE.DAT
         mov     dx,offset buff  ; DS:DX = buffer address
         mov     cx,512          ; CX = length to read
         mov     bx,handle1      ; BX = handle for MYFILE.DAT
         mov     ah,3fh          ; Function 3FH = read
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if read failed
         or      ax,ax           ; were any bytes read?
         jz      done            ; no, end of file reached

                                 ; write MYFILE.BAK
         mov     dx,offset buff  ; DS:DX = buffer address
         mov     cx,ax           ; CX = length to write
         mov     bx,handle2      ; BX = handle for MYFILE.BAK
         mov     ah,40h          ; Function 40H = write
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if write failed
         cmp     ax,cx           ; was write complete?
         jne     error           ; jump if disk full
         jmp     loop            ; continue to end of file

 done:                           ; now close files...
         mov     bx,handle1      ; handle for MYFILE.DAT
         mov     ah,3eh          ; Function 3EH = close file
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if close failed

         mov     bx,handle2      ; handle for MYFILE.BAK
         mov     ah,3eh          ; Function 3EH = close file
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if close failed

         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\OPENFILE.ASM

 kbuf    db      64,0,64 dup (0)
 prompt  db      0dh,0ah,'Enter filename: $'
 myfcb   db      37 dup (0)

         .
         .
         .
                                 ; display the prompt...
         mov     dx,seg prompt   ; DS:DX = prompt address
         mov     ds,dx
         mov     es,dx
         mov     dx,offset prompt
         mov     ah,09H          ; Function 09H = print string
         int     21h             ; transfer to MS-DOS

                                 ; now input filename...
         mov     dx,offset kbuf  ; DS:DX = buffer address
         mov     ah,0ah          ; Function 0AH = enter string
         int     21h             ; transfer to MS-DOS

                                 ; parse filename into FCB...
         mov     si,offset kbuf+2 ; DS:SI = address of filename
         mov     di,offset myfcb ; ES:DI = address of fcb
         mov     ax,2900h        ; Function 29H = parse name
         int     21h             ; transfer to MS-DOS
         or      al,al           ; jump if bad drive or
         jnz     error           ; wildcard characters in name

                                 ; try to open file...
         mov     dx,offset myfcb ; DS:DX = FCB address
         mov     ah,0fh          ; Function 0FH = open file
         int     21h             ; transfer to MS-DOS
         or      al,al           ; check status
         jz      proceed         ; jump if open successful

                                 ; else create file...
         mov     dx,offset myfcb ; DS:DX = FCB address
         mov     ah,16h          ; Function 16H = create
         int     21h             ; transfer to MS-DOS
         or      al,al           ; did create succeed?
         jnz     error           ; jump if create failed

 proceed:
         .                       ; file has been opened or
         .                       ; created, and FCB is valid
         .                       ; for read/write operations...

 \SAMPCODE\DOS_ENCY\7\POINTER1.ASM

 fhandle dw      ?               ; handle from previous open
 buff    db      512 dup (?)     ; buffer for data from file

         .
         .
         .
                                 ; position the file pointer...
         mov     cx,0            ; CX = high part of file offset
         mov     dx,32768        ; DX = low part of file offset
         mov     bx,fhandle      ; BX = handle for file
         mov     al,0            ; AL = positioning mode
         mov     ah,42h          ; Function 42H = position
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if function call failed

                                 ; now read 512 bytes from file
         mov     dx,offset buff  ; DS:DX = address of buffer
         mov     cx,512          ; CX = length of 512 bytes
         mov     bx,fhandle      ; BX = handle for file
         mov     ah,3fh          ; Function 3FH = read
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if read failed
         cmp     ax,512          ; was 512 bytes read?
         jne     error           ; jump if partial rec. or EOF
         .
         .
         .

 \SAMPCODE\DOS_ENCY\7\POINTER2.ASM

 fhandle dw      ?               ; handle from previous open

         .
         .
         .
                                 ; position the file pointer
                                 ; to the end of file...
         mov     cx,0            ; CX = high part of offset
         mov     dx,0            ; DX = low part of offset
         mov     bx,fhandle      ; BX = handle for file
         mov     al,2            ; AL = positioning mode
         mov     ah,42h          ; Function 42H = position
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if function call failed

                                 ; if call succeeded, DX:AX
                                 ; now contains the file size
         .
         .
         .

 \SAMPCODE\DOS_ENCY\8
 \SAMPCODE\DOS_ENCY\8\DIRDUMP.C

 /* DIRDUMP.C */

 #define AllAttributes   0x3F            /* bits set for all attributes */

 main()
 {
         static  char CurrentDir[64];
         int     ErrorCode;
         int     FileCount = 0;

         struct
         {
           char    reserved[21];
           char    attrib;
           int     time;
           int     date;
           long    size;
           char    name[13];
         }         DTA;

 /* display current directory name */

         ErrorCode = GetCurrentDir( CurrentDir );
         if( ErrorCode )
         {
           printf( "\nError %d:  GetCurrentDir", ErrorCode );
           exit( 1 );
         }

         printf( "\nCurrent directory is \\%s", CurrentDir );


 /* display files and attributes */

         SetDTA( &DTA );                 /* pass DTA to MS-DOS */

         ErrorCode = FindFirstFile( "*.*", AllAttributes );

         while( !ErrorCode )
         {
           printf( "\n%12s -- ", DTA.name );
           ShowAttributes( DTA.attrib );
           ++FileCount;

           ErrorCode = FindNextFile( );
         }

 /* display file count and exit */

         printf( "\nCurrent directory contains %d files\n", FileCount );
         return( 0 );
 }


 ShowAttributes( a )
 int     a;
 {
         int     i;
         int     mask = 1;

         static char *AttribName[] =
         {
           "read-only ",
           "hidden ",
           "system ",
           "volume ",
           "subdirectory ",
           "archive "
         };


         for( i=0; i<6; i++ )            /* test each attribute bit */
         {
           if( a & mask )
             printf( AttribName[i] );    /* display a message if bit is set */
           mask = mask << 1;
         }
 }

 \SAMPCODE\DOS_ENCY\8\DIRS.ASM

                 TITLE   'DIRS.ASM'

 ;
 ; Subroutines for DIRDUMP.C
 ;


 ARG1            EQU     [bp + 4]        ; stack frame addressing for C argume
 ARG2            EQU     [bp + 6]


 _TEXT           SEGMENT byte public 'CODE'
                 ASSUME  cs:_TEXT

 ;----------------------------------------------------------------------------
 ;
 ; void SetDTA( DTA );
 ;         char *DTA;
 ;
 ;----------------------------------------------------------------------------

                 PUBLIC  _SetDTA
 _SetDTA         PROC    near

                 push    bp
                 mov     bp,sp

                 mov     dx,ARG1         ; DS:DX -> DTA
                 mov     ah,1Ah          ; AH = INT 21H function number
                 int     21h             ; pass DTA to MS-DOS

                 pop     bp
                 ret

 _SetDTA         ENDP

 ;----------------------------------------------------------------------------
 ;
 ; int GetCurrentDir( *path );           /* returns error code */
 ;         char *path;                   /* pointer to buffer to contain path
 ;
 ;----------------------------------------------------------------------------

                 PUBLIC  _GetCurrentDir
 _GetCurrentDir  PROC    near

                 push    bp
                 mov     bp,sp
                 push    si

                 mov     si,ARG1         ; DS:SI -> buffer
                 xor     dl,dl           ; DL = 0 (default drive number)
                 mov     ah,47h          ; AH = INT 21H function number
                 int     21h             ; call MS-DOS; AX = error code
                 jc      L01             ; jump if error

                 xor     ax,ax           ; no error, return AX = 0

 L01:            pop     si
                 pop     bp
                 ret

 _GetCurrentDir  ENDP

 ;----------------------------------------------------------------------------
 ;
 ; int FindFirstFile( path, attribute ); /* returns error code */
 ;         char *path;
 ;         int  attribute;
 ;
 ;----------------------------------------------------------------------------

                 PUBLIC  _FindFirstFile
 _FindFirstFile  PROC    near

                 push    bp
                 mov     bp,sp

                 mov     dx,ARG1         ; DS:DX -> path
                 mov     cx,ARG2         ; CX = attribute
                 mov     ah,4Eh          ; AH = INT 21H function number
                 int     21h             ; call MS-DOS; AX = error code
                 jc      L02             ; jump if error

                 xor     ax,ax           ; no error, return AX = 0

 L02:            pop     bp
                 ret

 _FindFirstFile  ENDP

 ;----------------------------------------------------------------------------
 ;
 ; int FindNextFile();                   /* returns error code */
 ;
 ;----------------------------------------------------------------------------

                 PUBLIC  _FindNextFile
 _FindNextFile   PROC    near

                 push    bp
                 mov     bp,sp

                 mov     ah,4Fh          ; AH = INT 21H function number
                 int     21h             ; call MS-DOS; AX = error code
                 jc      L03             ; jump if error

                 xor     ax,ax           ; if no error, set AX = 0

 L03:            pop     bp
                 ret

 _FindNextFile   ENDP

 _TEXT           ENDS


 _DATA           SEGMENT word public 'DATA'

 CurrentDir      DB      64 dup(?)
 DTA             DB      64 dup(?)

 _DATA           ENDS

                 END

 \SAMPCODE\DOS_ENCY\8\VOLS.ASM

                 TITLE   'VOLS.ASM'

 ;----------------------------------------------------------------------------
 ;
 ; C-callable routines for manipulating MS-DOS volume labels
 ; Note: These routines modify the current DTA address.
 ;
 ;----------------------------------------------------------------------------

 ARG1            EQU     [bp + 4]        ; stack frame addressing

 DGROUP          GROUP   _DATA

 _TEXT           SEGMENT byte public 'CODE'
                 ASSUME  cs:_TEXT,ds:DGROUP

 ;----------------------------------------------------------------------------
 ;
 ; char *GetVolLabel();          /* returns pointer to volume label name */
 ;
 ;----------------------------------------------------------------------------

                 PUBLIC  _GetVolLabel
 _GetVolLabel    PROC    near

                 push    bp
                 mov     bp,sp
                 push    si
                 push    di

                 call    SetDTA          ; pass DTA address to MS-DOS
                 mov     dx,offset DGROUP:ExtendedFCB
                 mov     ah,11h          ; AH = INT 21H function number
                 int     21h             ; Search for First Entry
                 test    al,al
                 jnz     L01
                                         ; label found so make a copy
                 mov     si,offset DGROUP:DTA + 8
                 mov     di,offset DGROUP:VolLabel
                 call    CopyName
                 mov     ax,offset DGROUP:VolLabel ; return the copy's address
                 jmp     short L02

 L01:            xor     ax,ax           ; no label, return 0 (null pointer)

 L02:            pop     di
                 pop     si
                 pop     bp
                 ret

 _GetVolLabel    ENDP

 ;----------------------------------------------------------------------------
 ;
 ; int RenameVolLabel( label );          /* returns error code */
 ;         char *label;                  /* pointer to new volume label name *
 ;
 ;----------------------------------------------------------------------------

                 PUBLIC  _RenameVolLabel
 _RenameVolLabel PROC    near

                 push    bp
                 mov     bp,sp
                 push    si
                 push    di

                 mov     si,offset DGROUP:VolLabel  ; DS:SI -> old volume name
                 mov     di,offset DGROUP:Name1
                 call    CopyName        ; copy old name to FCB

                 mov     si,ARG1
                 mov     di,offset DGROUP:Name2
                 call    CopyName        ; copy new name into FCB

                 mov     dx,offset DGROUP:ExtendedFCB    ; DS:DX -> FCB
                 mov     ah,17h          ; AH = INT 21H function number
                 int     21h             ; rename
                 xor     ah,ah           ; AX = 00H (success) or 0FFH (failure

                 pop     di              ; restore registers and return
                 pop     si
                 pop     bp
                 ret

 _RenameVolLabel ENDP

 ;----------------------------------------------------------------------------
 ;
 ; int NewVolLabel( label );             /* returns error code */
 ;         char *label;                  /* pointer to new volume label name *
 ;
 ;----------------------------------------------------------------------------

                 PUBLIC  _NewVolLabel
 _NewVolLabel    PROC    near

                 push    bp
                 mov     bp,sp
                 push    si
                 push    di

                 mov     si,ARG1
                 mov     di,offset DGROUP:Name1
                 call    CopyName        ; copy new name to FCB

                 mov     dx,offset DGROUP:ExtendedFCB
                 mov     ah,16h          ; AH = INT 21H function number
                 int     21h             ; create directory entry
                 xor     ah,ah           ; AX = 00H (success) or 0FFH (failure

                 pop     di              ; restore registers and return
                 pop     si
                 pop     bp
                 ret

 _NewVolLabel    ENDP

 ;----------------------------------------------------------------------------
 ;
 ; int DeleteVolLabel();                 /* returns error code */
 ;
 ;----------------------------------------------------------------------------

                 PUBLIC  _DeleteVolLabel
 _DeleteVolLabel PROC    near

                 push    bp
                 mov     bp,sp
                 push    si
                 push    di

                 mov     si,offset DGROUP:VolLabel
                 mov     di,offset DGROUP:Name1
                 call    CopyName        ; copy current volume name to FCB

                 mov     dx,offset DGROUP:ExtendedFCB
                 mov     ah,13h          ; AH = INT 21H function number
                 int     21h             ; delete directory entry
                 xor     ah,ah           ; AX = 0 (success) or 0FFH (failure)

                 pop     di              ; restore registers and return
                 pop     si
                 pop     bp
                 ret

 _DeleteVolLabel ENDP

 ;----------------------------------------------------------------------------
 ;
 ; miscellaneous subroutines
 ;
 ;----------------------------------------------------------------------------

 SetDTA          PROC    near

                 push    ax              ; preserve registers used
                 push    dx

                 mov     dx,offset DGROUP:DTA    ; DS:DX -> DTA
                 mov     ah,1Ah          ; AH = INT 21H function number
                 int     21h             ; set DTA

                 pop     dx              ; restore registers and return
                 pop     ax
                 ret

 SetDTA          ENDP


 CopyName        PROC    near            ; Caller:  SI -> ASCIIZ source
                                         ;          DI -> destination
                 push    ds
                 pop     es              ; ES = DGROUP
                 mov     cx,11           ; length of name field

 L11:            lodsb                   ; copy new name into FCB ..
                 test    al,al
                 jz      L12             ; .. until null character is reached
                 stosb
                 loop    L11

 L12:            mov     al,' '          ; pad new name with blanks
                 rep     stosb
                 ret

 CopyName        ENDP

 _TEXT           ENDS


 _DATA           SEGMENT word public 'DATA'

 VolLabel        DB      11 dup(0),0

 ExtendedFCB     DB      0FFh            ; must be 0FFH for extended FCB
                 DB      5 dup(0)        ; (reserved)
                 DB      1000b           ; attribute byte (bit 3 = 1)
                 DB      0               ; default drive ID
 Name1           DB      11 dup('?')     ; global wildcard name
                 DB      5 dup(0)        ; (unused)
 Name2           DB      11 dup(0)       ; second name (for renaming entry)
                 DB      9 dup(0)        ; (unused)

 DTA             DB      64 dup(0)

 _DATA           ENDS

                 END

 \SAMPCODE\DOS_ENCY\9
 \SAMPCODE\DOS_ENCY\9\FIG9_2.ASM

         .
         .
         .
 _TEXT   segment para public 'CODE'

         org     100h

         assume  cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT

 main    proc    near            ; entry point from MS-DOS
                                 ; CS = DS = ES = SS = PSP

                                 ; first move our stack
         mov     sp,offset stk   ; to a safe place...

                                 ; now release extra memory...
         mov     bx,offset stk   ; calculate paragraphs to keep
         mov     cl,4            ; (divide offset of end of
         shr     bx,cl           ; program by 16 and round up)
         inc     bx
         mov     ah,4ah          ; Fxn 4AH = resize mem block
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if resize failed
         .
         .                       ; otherwise go on with work...
         .

 main    endp

         .
         .
         .

         dw      64 dup (?)
 stk     equ     $               ; base of new stack area

 _TEXT   ends

         end     main            ; defines program entry point

 \SAMPCODE\DOS_ENCY\9\FIG9_3.ASM

         .
         .
         .

 _TEXT   segment word public 'CODE'      ; executable code segment

         assume  cs:_TEXT,ds:_DATA,ss:_STACK

 main    proc    far             ; entry point from MS-DOS
                                 ; CS = _TEXT segment,
                                 ; DS = ES = PSP

         mov     ax,_DATA        ; set DS = our data segment
         mov     ds,ax

                                 ; give back extra memory...
         mov     ax,es           ; let AX = segment of PSP base
         mov     bx,ss           ; and BX = segment of stack base
         sub     bx,ax           ; reserve seg stack - seg psp
         add     bx,stksize/16   ; plus paragraphs of stack
         inc     bx              ; round up
         mov     ah,4ah          ; Fxn 4AH = resize memory block
         int     21h
         jc      error

 main    endp

 _TEXT   ends


 _DATA   segment word public 'DATA'      ; static & variable data

         .
         .
         .

 _DATA   ends


 STACK   segment para stack 'STACK'

         db      stksize dup (?)

 STACK   ends

         end     main            ; defines program entry point

 \SAMPCODE\DOS_ENCY\9\FIG9_4.ASM

         .
         .
         .
         mov     bx,800h         ; 800H paragraphs = 32 KB
         mov     ah,48h          ; Fxn 48H = allocate block
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if allocation failed
         mov     bufseg,ax       ; save segment of block

                                 ; open working file...
         mov     dx,offset file1 ; DS:DX = filename address
         mov     ax,3d00h        ; Fxn 3DH = open, read only
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if open failed
         mov     handle1,ax      ; save handle for work file

                                 ; create backup file...
         mov     dx,offset file2 ; DS:DX = filename address
         mov     cx,0            ; CX = attribute (normal)
         mov     ah,3ch          ; Fxn 3CH = create file
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if create failed
         mov     handle2,ax      ; save handle for backup file

         push    ds              ; set ES = our data segment
         pop     es
         mov     ds,bufseg       ; set DS:DX = allocated block
         xor     dx,dx

         assume  ds:NOTHING,es:_DATA     ; tell assembler

 next:                           ; read working file...
         mov     bx,handle1      ; handle for work file
         mov     cx,8000h        ; try to read 32 KB
         mov     ah,3fh          ; Fxn 3FH = read
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if read failed
         or      ax,ax           ; was end of file reached?
         jz      done            ; yes, exit this loop

                                 ; now write backup file...
         mov     cx,ax           ; set write length = read length
         mov     bx,handle2      ; handle for backup file
         mov     ah,40h          ; Fxn 40H = write
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if write failed
         cmp     ax,cx           ; was write complete?
         jne     error           ; no, disk must be full
         jmp     next            ; transfer another record

 done:   push    es              ; restore DS = data segment
         pop     ds

         assume  ds:_DATA,es:NOTHING     ; tell assembler

                                 ; release allocated block...
         mov     es,bufseg       ; segment base of block
         mov     ah,49h          ; Fxn 49H = release block
         int     21h             ; transfer to MS-DOS
         jc      error           ; (should never fail)

                                 ; now close backup file...
         mov     bx,handle2      ; handle for backup file
         mov     ah,3eh          ; Fxn 3EH = close
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if close failed
         .
         .
         .

 file1   db      'MYFILE.DAT',0  ; name of working file
 file2   db      'MYFILE.BAK',0  ; name of backup file

 handle1 dw      ?               ; handle for working file
 handle2 dw      ?               ; handle for backup file
 bufseg  dw      ?               ; segment of allocated block

 \SAMPCODE\DOS_ENCY\9\FIG9_6.ASM

         .
         .
         .
                                 ; attempt to "open" EMM...
         mov     dx,seg emm_name ; DS:DX = address of name
         mov     ds,dx           ; of EMM
         mov     dx,offset emm_name
         mov     ax,3d00h        ; Fxn 3DH, Mode = 00H
                                 ; = open, read-only
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if open failed

                                 ; open succeeded, make sure
                                 ; it was not a file...
         mov     bx,ax           ; BX = handle from open
         mov     ax,4400h        ; Fxn 44H Subfxn 00H
                                 ; = IOCTL Get Device Information
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if IOCTL call failed
         and     dx,80h          ; Bit 7 = 1 if character device
         jz      error           ; jump if it was a file

                                 ; EMM is present, make sure
                                 ; it is available...
                                 ; (BX still contains handle)
         mov     ax,4407h        ; Fxn 44H Subfxn 07H
                                 ; = IOCTL Get Output Status
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if IOCTL call failed
         or      al,al           ; test device status
         jz      error           ; if AL = 0 EMM is not available

                                 ; now close handle ...
                                 ; (BX still contains handle)
         mov     ah,3eh          ; Fxn 3EH = Close
         int     21h             ; transfer to MS-DOS
         jc      error           ; jump if close failed
         .
         .
         .

 emm_name db     'EMMXXXX0',0    ; guaranteed device name for EMM

 \SAMPCODE\DOS_ENCY\9\FIG9_7.ASM

 emm_int equ     67h             ; EMM software interrupt

         .
         .
         .
                                 ; first fetch contents of
                                 ; EMM interrupt vector...
         mov     al,emm_int      ; AL = EMM int number
         mov     ah,35h          ; Fxn 35H = get vector
         int     21h             ; transfer to MS-DOS
                                 ; now ES:BX = handler address

                                 ; assume ES:0000 points
                                 ; to base of the EMM...
         mov     di,10           ; ES:DI = address of name
                                 ; field in device header
         mov     si,seg emm_name ; DS:SI = address of
         mov     ds,si           ; expected EMM driver name
         mov     si,offset emm_name
         mov     cx,8            ; length of name field
         cld
         repz cmpsb              ; compare names...
         jnz     error           ; jump if driver absent
         .
         .
         .


 emm_name db     'EMMXXXX0'      ; guaranteed device name for EMM

 \SAMPCODE\DOS_ENCY\9\FIG9_8.ASM

         .
         .
         .
         mov     ah,40h          ; test EMM status
         int     67h
         or      ah,ah
         jnz     error           ; jump if bad status from EMM

         mov     ah,46h          ; check EMM version
         int     67h
         or      ah,ah
         jnz     error           ; jump if couldn't get version
         cmp     al,30h          ; make sure at least ver. 3.0
         jb      error           ; jump if wrong EMM version

         mov     ah,41h          ; get page frame segment
         int     67h
         or      ah,ah
         jnz     error           ; jump if failed to get frame
         mov     page_frame,bx   ; save segment of page frame

         mov     ah,42h          ; get no. of available pages
         int     67h
         or      ah,ah
         jnz     error           ; jump if get pages error
         mov     total_pages,dx  ; save total EMM pages
         mov     avail_pages,bx  ; save available EMM pages
         or      bx,bx
         jz      error           ; abort if no pages available

         mov     ah,43h          ; try to allocate EMM pages
         mov     bx,needed_pages
         int     67h             ; if allocation is successful
         or      ah,ah
         jnz     error           ; jump if allocation failed

         mov     emm_handle,dx   ; save handle for allocated pages

         .
         .                       ; now we are ready for other
         .                       ; processing using EMM pages
         .
                                 ; map in EMM memory page...
         mov     bx,log_page     ; BX <- EMM logical page number
         mov     al,phys_page    ; AL <- EMM physical page (0-3)
         mov     dx,emm_handle   ; EMM handle for our pages
         mov     ah,44h          ; Fxn 44H = map EMM page
         int     67h
         or      ah,ah
         jnz     error           ; jump if mapping error

         .
         .
         .
                                 ; program ready to terminate,
                                 ; give up allocated EMM pages...
         mov     dx,emm_handle   ; handle for our pages
         mov     ah,45h          ; EMM Fxn 45H = release pages
         int     67h
         or      ah,ah
         jnz     error           ; jump if release failed
         .
         .
         .

 \SAMPCODE\DOS_ENCY\APP_A
 \SAMPCODE\DOS_ENCY\APP_A\6501_BUF.ASM

 buffer  db      1               ; Information ID code
         dw      38              ; Length of following buffer
         dw      1               ; Country ID (USA)
         dw      437             ; Code-page number
         dw      0               ; Date format
         db      '$',0,0,0,0     ; Currency symbol
         db      ',',0           ; Thousands separator
         db      '.',0           ; Decimal separator
         db      '-',0           ; Date separator
         db      ':',0           ; Time separator
         db      0               ; Currency format flags
         db      2               ; Digits in currency
         db      0               ; Time format
         dd      026ah:176ch     ; Monocase routine entry point
         db      ',',0           ; Data list separator
         db      10 dup (0)      ; Reserved

 \SAMPCODE\DOS_ENCY\APP_A\6502_BUF.ASM

 buffer  db      2               ; Information ID code
         dw      0204h           ; Offset of uppercase table
         dw      1140h           ; Segment of uppercase table

 \SAMPCODE\DOS_ENCY\APP_A\FXN6501H.ASM

 buffer  db      41 dup (0)      ; Receives country information.
         .
         .
         .
         mov     ax,6501h        ; Function = get extended info.
         mov     bx,437          ; Code page.
         mov     cx,41           ; Length of buffer.
         mov     dx,-1           ; Default country.
         mov     di,seg buffer   ; ES:DI = buffer address.
         mov     es,di
         mov     di,offset buffer
         int     21h             ; Transfer to MS-DOS.
         jc      error           ; Jump if function failed.
         .
         .
         .

 \SAMPCODE\DOS_ENCY\APP_A\FXN6502H.ASM

 buffer  db      5 dup (0)       ; Receives pointer information.
         .
         .
         .
         mov     ax,6502h        ; Function = get pointer to
                                 ; uppercase table.
         mov     bx,437          ; Code page.
         mov     cx,5            ; Length of buffer.
         mov     dx,-1           ; Default country.
         mov     di,seg buffer   ; ES:DI = buffer address.
         mov     es,di
         mov     di,offset buffer
         int     21h             ; Transfer to MS-DOS.
         jc      error           ; Jump if function failed.
         .
         .
         .

 \SAMPCODE\DOS_ENCY\APP_A\FXN67H.ASM

         .
         .
         .
         mov     ah,67h          ; Function 67H = set handle count.
         mov     bx,30           ; Maximum number of handles.
         int     21h             ; Transfer to MS-DOS.
         jc      error           ; Jump if function failed.
         .
         .
         .

 \SAMPCODE\DOS_ENCY\APP_A\FXN68H.ASM

 fname   db      'MYFILE.DAT',0  ; ASCIIZ filename.
 fhandle dw      ?               ; Handle from Open operation.
         .
         .
         .
         mov     ah,68h          ; Function 68H = commit file.
         mov     bx,fhandle      ; Handle from previous open.
         int     21h             ; Transfer to MS-DOS.
         jc      error           ; Jump if function failed.
         .
         .
         .

 \SAMPCODE\DOS_ENCY\APP_L
 \SAMPCODE\DOS_ENCY\APP_L\BIN2HEX.BAS

 'Binary-to-Hex file conversion utility.
 'Requires Microsoft QuickBASIC version 3.0 or later.

 DEFINT A-Z                                    ' All variables are integers
                                               ' unless otherwise declared.
 CONST FALSE = 0                               ' Value of logical FALSE.
 CONST TRUE = NOT FALSE                        ' Value of logical TRUE.

 DEF FNHXB$(X) = RIGHT$(HEX$(&H100 + X), 2)    ' Return 2-digit hex value for
 DEF FNHXW$(X!) = RIGHT$("000" + HEX$(X!), 4)  ' Return 4-digit hex value for
 DEF FNMOD(X, Y) = X! - INT(X!/Y) * Y          ' X! MOD Y (the MOD operation i
                                               ' only for integers).
 CONST SRCCNL = 1                              ' Source (.BIN) file channel.
 CONST TGTCNL = 2                              ' Target (.HEX) file channel.

 LINE INPUT "Enter full name of source .BIN file        :  ";SRCFIL$
 OPEN SCRCFIL$ FOR INPUT AS SRCCNL             ' Test for source (.BIN) file.
 SRCSIZ! = LOF(SRCCNL)                         ' Save file's size.
 CLOSE SRCCNL
 IF (SRCSIZ! > 65536) THEN                     ' Reject if file exceeds 64 KB.
     PRINT "Cannot convert file larger than 64 KB."
     END
 END IF

 LINE INPUT "Enter full name of target .HEX file        :  ";TGTFIL$
 OPEN TGTFIL$ FOR OUTPUT AS TGTCNL             ' Test target (.HEX) filename.
 CLOSE TGTCNL

 DO
     LINE INPUT "Enter starting address of .BIN file in HEX :  ";L$
     ADRBGN! = VAL("&H" + L$)                  ' Convert ASCII HEX address val
                                               ' to binary value.
     IF (ADRBGN! < 0) THEN                     ' HEX values 8000-FFFFH convert
      ADRBGN! = 65536 + ADRBGN!                ' to negative values.
     END IF
     ADREND! = ADRBGN! + SRCSIZ! - 1           ' Calculate resulting end addre
     IF (ADREND! > 65535) THEN                 ' Reject if address exceeds FFF
      PRINT "Entered start address causes end address to exceed FFFFH."
     END IF
 LOOP UNTIL (ADRFLD! >= 0) AND (ADRFLD! <= 65535) AND (ADREND! <= 65535)

 DO
     LINE INPUT "Enter byte count for each record in HEX    :  ";L$
     SRCRLN = VAL("&H" + L$)                   ' Convert ASCII HEX max record
                                               ' length value to binary value.
     IF (SRCRLN < 0) THEN                      ' HEX values 8000H-FFFFH conver
      SRCRLN = 65536 + SRCRLN                  ' to negative values.
     END IF
 LOOP UNTIL (SRCRLN > 0) AND (SRCRLN < 256)    ' Ask again if not 1-255.

 OPEN SRCFIL$ AS SRCCNL LEN = SRCRLN           ' Reopen source for block I/O.
 FIELD#SRCCNL,SRCRLN AS SRCBLK$
 OPEN TGTFIL$ FOR OUTPUT AS TGTCNL             ' Reopen target for text output
 SRCREC = 0                                    ' Starting source block # minus

 FOR ADRFLD! = ADRBGN! TO ADREND! STEP SRCRLN  ' Convert one block per loop.
     SRCREC = SRCREC + 1                       ' Next source block.
     GET SRCCNL,SRCREC                         ' Read the source block.
     IF (ADRFLD! + SRCRLN > ADREND!) THEN      ' If last block less than full
      BLK$=LEFT$(SRCBLK$,ADREND!-ADRFLD!+1)    ' size:  trim it.
     ELSE                                      ' Else:
         BLK$ = SRCBLK$                        ' Use full block.
     END IF

     PRINT#TGTCNL, ":";                        ' Write record mark.

     PRINT#TGTCNL, FNHXB$(LEN(BLK$));          ' Write data field size.
     CHKSUM = LEN(BLK$)                        ' Initialize checksum accumulat
                                               ' with first value.
     PRINT#TGTCNL,FNHXW$(ADRFLD!);             ' Write record's load address.

 ' The following "AND &HFF" operations limit CHKSUM to a byte value.
     CHKSUM = CHKSUM + INT(ADRFLD!/256) AND &HFF   ' Add hi byte of adrs to cs
     CHKSUM = CHKSUM + FNMOD(ADRFLD!,256) AND &HFF ' Add lo byte of adrs to cs

     PRINT#TGTCNL,FNHXB$(0);                   ' Write record type.

 ' Don't bother to add record type byte to checksum since it's 0.
     FOR IDX = 1 TO LEN(BLK$)                  ' Write all bytes.
      PRINT#TGTCNL,FNHXB$(ASC(MID$(BLK$,IDX,1)));      ' Write next byte.
      CHKSUM = CHKSUM + ASC(MID$(BLK$,IDX,1)) AND &HFF ' Incl byte in csum.
     NEXT IDX

     CHKSUM = 0 - CHKSUM AND &HFF              ' Negate checksum then limit
                                               ' to byte value.
     PRINT #TGTCNL,FNHXB$(CHKSUM)              ' End record with checksum.

 NEXT ADRFLD!

 PRINT#TGTCNL, ":00000001FF"                   ' Write end-of-file record.

 CLOSE TGTCNL                                  ' Close target file.
 CLOSE SRCCNL                                  ' Close source file.

 END

 \SAMPCODE\DOS_ENCY\APP_N
 \SAMPCODE\DOS_ENCY\APP_N\OBJDUMP.C

 /****************************************************************************
 *
 * OBJDUMP.C -- display contents of an object file
 *
 *
 *     Compile:  msc objdump;   (Microsoft C version 4.0 or later)
 *     Link:     link objdump;
 *     Execute:  objdump <filename>
 *
 *****************************************************************************

 #include        <fcntl.h>

 #define         TRUE    1
 #define         FALSE   0

 main( argc, argv )
 int        argc;
 char       **argv;
 {
         unsigned char        CurrentByte;
         int     ObjFileHandle;
         int     CurrentLineLength;                 /* length of output line *
         int     ObjRecordNumber = 0;
         int     ObjRecordLength;
         int     ObjRecordOffset = 0;   /* offset into current object record *
         char    ASCIIEquiv[17];
         char    FormatString[24];
         char    *ObjRecordName();
         char    *memset();


 /* open the object file */

         ObjFileHandle = open( argv[1],O_BINARY );

         if( ObjFileHandle == -1 )
         {
           printf( "\nCan't open object file\n" );
           exit( 1 );
         }

 /* process the object file character by character */

         while( read( ObjFileHandle, &CurrentByte, 1 ) )
         {
           switch( ObjRecordOffset ) /* action depends on offset into record *
           {
             case(0):                              /* start of object record *
               printf( "\n\nRecord %d:  %02Xh %s",
                 ++ObjRecordNumber, CurrentByte, ObjRecordName(CurrentByte) );
               printf( "\n%02X ", CurrentByte );
               ++ObjRecordOffset;
               break;

             case(1):                          /* first byte of length field *
               ObjRecordLength = CurrentByte;
               ++ObjRecordOffset;
               break;

             case(2):                         /* second byte of length field *
               ObjRecordLength += CurrentByte << 8; /* compute record length *
               printf( "%04Xh ", ObjRecordLength );           /* show length *
               CurrentLineLength = 0;
               memset( ASCIIEquiv,'\0', 17 );            /* zero this string *
               ++ObjRecordOffset;
               break;

             default:                    /* remaining bytes in object record *
               printf( "%02X ", CurrentByte );                        /* hex *

               if( CurrentByte < 0x20 || CurrentByte > 0x7F )       /* ASCII *
                 CurrentByte = '.';
               ASCIIEquiv[CurrentLineLength++] = CurrentByte;

               if( CurrentLineLength == 16 ||   /* if end of output line ... *
                  ObjRecordOffset == ObjRecordLength+2 )
               {                                           /* ... display it *
                 sprintf( FormatString, "%%%ds%%s\n         ",
                   3*(16-CurrentLineLength)+2 );
                 printf( FormatString, " ", ASCIIEquiv );
                 memset( ASCIIEquiv, '\0', 17 );
                 CurrentLineLength = 0;
               }

               if( ++ObjRecordOffset == ObjRecordLength+3 )  /* if done .. */
                 ObjRecordOffset = 0;          /* .. process another record */
               break;
           }
         }

         if( CurrentLineLength )    /* display remainder of last output line *
           printf( "  %s", ASCIIEquiv );

         close( ObjFileHandle );

         printf( "\n%d object records\n", ObjRecordNumber );

         return( 0 );
 }


 char *ObjRecordName( n )                       /* return object record name *
 int        n;                                            /* n = record type *
 {
         int        i;

         static     struct
         {
           int        RecordNumber;
           char       *RecordName;
         }                RecordStruct[] =
                          {
                           0x80,"THEADR",
                           0x88,"COMENT",
                           0x8A,"MODEND",
                           0x8C,"EXTDEF",
                           0x8E,"TYPDEF",
                           0x90,"PUBDEF",
                           0x94,"LINNUM",
                           0x96,"LNAMES",
                           0x98,"SEGDEF",
                           0x9A,"GRPDEF",
                           0x9C,"FIXUPP",
                           0xA0,"LEDATA",
                           0xA2,"LIDATA",
                           0xB0,"COMDEF",
                           0x00,"******"
                          };

         int      RecordTableSize = sizeof(RecordStruct)/sizeof(RecordStruct[0


         for( i=0; i<RecordTableSize-1; i++ )         /* scan table for name *
           if ( RecordStruct[i].RecordNumber == n )
             break;

         return( RecordStruct[i].RecordName );
 }

 \SAMPCODE\DOS_ENCY\SECTION5
 \SAMPCODE\DOS_ENCY\SECTION5\TESTCM.C

 main()
 {
         printf("The sum is %d",addnums(12,33));
 }

 \SAMPCODE\DOS_ENCY\SECTION5\FXN01H.ASM

         ;************************************************************;
         ;                                                            ;
         ;           Function 01H: Character Input with Echo          ;
         ;                                                            ;
         ;           int read_kbd_echo()                              ;
         ;                                                            ;
         ;           Returns a character from standard input          ;
         ;           after sending it to standard output.             ;
         ;                                                            ;
         ;************************************************************;

 cProc   read_kbd_echo,PUBLIC
 cBegin
         mov     ah,01h          ; Set function code.
         int     21h             ; Wait for character.
         mov     ah,0            ; Character is in AL, so clear high
                                 ; byte.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN02H.ASM

         ;************************************************************;
         ;                                                            ;
         ;                Function 02H: Character Output              ;
         ;                                                            ;
         ;                int disp_ch(c)                              ;
         ;                    char c;                                 ;
         ;                                                            ;
         ;                Returns 0.                                  ;
         ;                                                            ;
         ;************************************************************;

 cProc   disp_ch,PUBLIC
 parmB   c
 cBegin
         mov     dl,c            ; Get character into DL.
         mov     ah,02h          ; Set function code.
         int     21h             ; Send character.
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN03H.ASM

         ;************************************************************;
         ;                                                            ;
         ;           Function 03H: Auxiliary Input                    ;
         ;                                                            ;
         ;           int aux_in()                                     ;
         ;                                                            ;
         ;           Returns next character from AUX device.          ;
         ;                                                            ;
         ;************************************************************;

 cProc   aux_in,PUBLIC
 cBegin
         mov     ah,03h          ; Set function code.
         int     21h             ; Wait for character from AUX.
         mov     ah,0            ; Character is in AL
                                 ; so clear high byte.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN04H.ASM

         ;************************************************************;
         ;                                                            ;
         ;                Function 04H: Auxiliary Output              ;
         ;                                                            ;
         ;                int aux_out(c)                              ;
         ;                    char c;                                 ;
         ;                                                            ;
         ;                Returns 0.                                  ;
         ;                                                            ;
         ;************************************************************;

 cProc   aux_out,PUBLIC
 parmB   c
 cBegin
         mov     dl,c            ; Get character into DL.
         mov     ah,04h          ; Set function code.
         int     21h             ; Write character to AUX.
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN05H.ASM

         ;************************************************************;
         ;                                                            ;
         ;                Function 05H: Print Character               ;
         ;                                                            ;
         ;                int print_ch(c)                             ;
         ;                    char c;                                 ;
         ;                                                            ;
         ;                Returns 0.                                  ;
         ;                                                            ;
         ;************************************************************;

 cProc   print_ch,PUBLIC
 parmB   c
 cBegin
         mov     dl,c            ; Get character into DL.
         mov     ah,05h          ; Set function code.
         int     21h             ; Write character to standard printer.
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN06H.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 06H: Direct Console I/O                  ;
         ;                                                            ;
         ;          int con_io(c)                                     ;
         ;              char c;                                       ;
         ;                                                            ;
         ;          Returns meaningless data if c is not 0FFH,        ;
         ;          otherwise returns next character from             ;
         ;          standard input.                                   ;
         ;                                                            ;
         ;************************************************************;

 cProc   con_io,PUBLIC
 parmB   c
 cBegin
         mov     dl,c            ; Get character into DL.
         mov     ah,06h          ; Set function code.
         int     21h             ; This function does NOT wait in
                                 ; input case (c = 0FFH)!
         mov     ah,0            ; Return the contents of AL.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN07H.ASM

         ;************************************************************;
         ;                                                            ;
         ;         Function 07H: Unfiltered Character Input           ;
         ;                       Without Echo                         ;
         ;                                                            ;
         ;         int con_in()                                       ;
         ;                                                            ;
         ;         Returns next character from standard input.        ;
         ;                                                            ;
         ;************************************************************;

 cProc   con_in,PUBLIC
 cBegin
         mov     ah,07h          ; Set function code.
         int     21h             ; Wait for character, no echo.
         mov     ah,0            ; Clear high byte.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN08H.ASM

         ;************************************************************;
         ;                                                            ;
         ;    Function 08H:  Unfiltered Character Input Without Echo  ;
         ;                                                            ;
         ;    int read_kbd()                                          ;
         ;                                                            ;
         ;    Returns next character from standard input.             ;
         ;                                                            ;
         ;************************************************************;

 cProc   read_kbd,PUBLIC
 cBegin
         mov     ah,08h          ; Set function code.
         int     21h             ; Wait for character, no echo.
         mov     ah,0            ; Clear high byte.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN09H.ASM

         ;************************************************************;
         ;                                                            ;
         ;                 Function 09H: Display String               ;
         ;                                                            ;
         ;                 int disp_str(pstr)                         ;
         ;                     char *pstr;                            ;
         ;                                                            ;
         ;                 Returns 0.                                 ;
         ;                                                            ;
         ;************************************************************;

 cProc   disp_str,PUBLIC,<ds,di>
 parmDP  pstr
 cBegin
         loadDP  ds,dx,pstr      ; DS:DX = pointer to string.
         mov     ax,0900h        ; Prepare to write dollar-terminated
                                 ; string to standard output, but
                                 ; first replace the 0 at the end of
                                 ; the string with '$'.
         push    ds              ; Set ES equal to DS.
         pop     es              ; (MS-C does not require ES to be
                                 ; saved.)
         mov     di,dx           ; ES:DI points at string.
         mov     cx,0ffffh       ; Allow string to be 64KB long.
         repne   scasb           ; Look for 0 at end of string.
         dec     di              ; Scasb search always goes 1 byte too
                                 ; far.
         mov     byte ptr [di],'$' ; Replace 0 with dollar sign.
         int     21h             ; Have MS-DOS print string.
         mov     [di],al         ; Restore 0 terminator.
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN0AH.ASM

         ;************************************************************;
         ;                                                            ;
         ;        Function 0AH: Buffered Keyboard Input               ;
         ;                                                            ;
         ;        int read_str(pbuf,len)                              ;
         ;            char *pbuf;                                     ;
         ;            int len;                                        ;
         ;                                                            ;
         ;        Returns number of bytes read into buffer.           ;
         ;                                                            ;
         ;        Note: pbuf must be at least len+3 bytes long.       ;
         ;                                                            ;
         ;************************************************************;

 cProc   read_str,PUBLIC,<ds,di>
 parmDP  pbuf
 parmB   len
 cBegin
         loadDP  ds,dx,pbuf      ; DS:DX = pointer to buffer.
         mov     al,len          ; AL = len.
         inc     al              ; Add 1 to allow for CR in buf.
         mov     di,dx
         mov     [di],al         ; Store max length into buffer.
         mov     ah,0ah          ; Set function code.
         int     21h             ; Ask MS-DOS to read string.
         mov     al,[di+1]       ; Return number of characters read.
         mov     ah,0
         mov     bx,ax
         mov     [bx+di+2],ah    ; Store 0 at end of buffer.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN0BH.ASM

         ;************************************************************;
         ;                                                            ;
         ;             Function 0BH: Check Keyboard Status            ;
         ;                                                            ;
         ;             int key_ready()                                ;
         ;                                                            ;
         ;             Returns 1 if key is ready, 0 if not.           ;
         ;                                                            ;
         ;************************************************************;

 cProc   key_ready,PUBLIC
 cBegin
         mov     ah,0bh          ; Set function code.
         int     21h             ; Ask MS-DOS if key is available.
         and     ax,0001h        ; Keep least significant bit only.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN0CH.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 0CH: Flush Buffer, Read Keyboard         ;
         ;                                                            ;
         ;          int flush_kbd()                                   ;
         ;                                                            ;
         ;          Returns 0.                                        ;
         ;                                                            ;
         ;************************************************************;

 cProc   flush_kbd,PUBLIC
 cBegin
         mov     ax,0c00h        ; Just flush type-ahead buffer.
         int     21h             ; Call MS-DOS.
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN0DH.ASM

         ;************************************************************;
         ;                                                            ;
         ;                   Function 0DH: Disk Reset                 ;
         ;                                                            ;
         ;                   int reset_disk()                         ;
         ;                                                            ;
         ;                   Returns 0.                               ;
         ;                                                            ;
         ;************************************************************;

 cProc   reset_disk,PUBLIC
 cBegin
         mov     ah,0dh          ; Set function code.
         int     21h             ; Ask MS-DOS to write all dirty file
                                 ; buffers to the disk.
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN0EH.ASM

         ;************************************************************;
         ;                                                            ;
         ;     Function 0EH: Select Disk                              ;
         ;                                                            ;
         ;     int select_drive(drive_ltr)                            ;
         ;         char drive_ltr;                                    ;
         ;                                                            ;
         ;     Returns number of logical drives present in system.    ;
         ;                                                            ;
         ;************************************************************;

 cProc   select_drive,PUBLIC
 parmB   disk_ltr
 cBegin
         mov     dl,disk_ltr     ; Get new drive letter.
         and     dl,not 20h      ; Make sure letter is uppercase.
         sub     dl,'A'          ; Convert drive letter to number,
                                 ; 'A' = 0, 'B' = 1, etc.
         mov     ah,0eh          ; Set function code.
         int     21h             ; Ask MS-DOS to set default drive.
         cbw                     ; Clear high byte of return value.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN0FH.ASM

         ;************************************************************;
         ;                                                            ;
         ;        Function 0FH: Open File, FCB-based                  ;
         ;                                                            ;
         ;        int FCB_open(uXFCB,recsize)                         ;
         ;            char *uXFCB;                                    ;
         ;            int recsize;                                    ;
         ;                                                            ;
         ;        Returns 0 if file opened OK, otherwise returns -1.  ;
         ;                                                            ;
         ;        Note: uXFCB must have the drive and filename        ;
         ;        fields (bytes 07H through 12H) and the extension    ;
         ;        flag (byte 00H) set before the call to FCB_open     ;
         ;        (see Function 29H).                                 ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_open,PUBLIC,ds
 parmDP  puXFCB
 parmW   recsize
 cBegin
         loadDP  ds,dx,puXFCB    ; Pointer to unopened extended FCB.
         mov     ah,0fh          ; Ask MS-DOS to open an existing file.
         int     21h
         add     dx,7            ; Advance pointer to start of regular
                                 ; FCB.
         mov     bx,dx           ; BX = FCB pointer.
         mov     dx,recsize      ; Get record size parameter.
         mov     [bx+0eh],dx     ; Store record size in FCB.
         xor     dx,dx
         mov     [bx+20h],dl     ; Set current-record
         mov     [bx+21h],dx     ; and relative-record
         mov     [bx+23h],dx     ; fields to 0.
         cbw                     ; Set return value to 0 or -1.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN10H.ASM

         ;************************************************************;
         ;                                                            ;
         ;           Function 10H: Close file, FCB-based              ;
         ;                                                            ;
         ;           int FCB_close(oXFCB)                             ;
         ;               char *oXFCB;                                 ;
         ;                                                            ;
         ;           Returns 0 if file closed OK, otherwise           ;
         ;           returns -1.                                      ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_close,PUBLIC,ds
 parmDP  poXFCB
 cBegin
         loadDP  ds,dx,poXFCB    ; Pointer to opened extended FCB.
         mov     ah,10h          ; Ask MS-DOS to close file.
         int     21h
         cbw                     ; Set return value to 0 or -1.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN11H.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 11H: Find First File, FCB-based             ;
         ;                                                            ;
         ;       int FCB_first(puXFCB,attrib)                         ;
         ;           char *puXFCB;                                    ;
         ;           char  attrib;                                    ;
         ;                                                            ;
         ;       Returns 0 if match found, otherwise returns -1.      ;
         ;                                                            ;
         ;       Note: The FCB must have the drive and                ;
         ;       filename fields (bytes 07H through 12H) and          ;
         ;       the extension flag (byte 00H) set before             ;
         ;       the call to FCB_first (see Function 29H).            ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_first,PUBLIC,ds
 parmDP  puXFCB
 parmB   attrib
 cBegin
         loadDP  ds,dx,puXFCB    ; Pointer to unopened extended FCB.
         mov     bx,dx           ; BX points at FCB, too.
         mov     al,attrib       ; Get search attribute.
         mov     [bx+6],al       ; Put attribute into extended FCB
                                 ; area.
         mov     byte ptr [bx],0ffh ; Set flag for extended FCB.
         mov     ah,11h          ; Ask MS-DOS to find 1st matching
                                 ; file in current directory.
         int     21h             ; If match found, directory entry can
                                 ; be found at DTA address.
         cbw                     ; Set return value to 0 or -1.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN12H.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 12H: Find Next File, FCB-based              ;
         ;                                                            ;
         ;       int FCB_next(puXFCB)                                 ;
         ;           char *puXFCB;                                    ;
         ;                                                            ;
         ;       Returns 0 if match found, otherwise returns -1.      ;
         ;                                                            ;
         ;       Note: The FCB must have the drive and                ;
         ;       filename fields (bytes 07H through 12H) and          ;
         ;       the extension flag (byte 00H) set before             ;
         ;       the call to FCB_next (see Function 29H).             ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_next,PUBLIC,ds
 parmDP  puXFCB
 cBegin
         loadDP  ds,dx,puXFCB    ; Pointer to unopened extended FCB.
         mov     ah,12h          ; Ask MS-DOS to find next matching
                                 ; file in current directory.
         int     21h             ; If match found, directory entry can
                                 ; be found at DTA address.
         cbw                     ; Set return value to 0 or -1.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN13H.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 13H: Delete File(s), FCB-based              ;
         ;                                                            ;
         ;       int FCB_delete(uXFCB)                                ;
         ;           char *uXFCB;                                     ;
         ;                                                            ;
         ;       Returns 0 if file(s) were deleted OK, otherwise      ;
         ;       returns -1.                                          ;
         ;                                                            ;
         ;       Note: uXFCB must have the drive and                  ;
         ;       filename fields (bytes 07H through 12H) and          ;
         ;       the extension flag (byte 00H) set before             ;
         ;       the call to FCB_delete (see Function 29H).           ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_delete,PUBLIC,ds
 parmDP  puXFCB
 cBegin
         loadDP  ds,dx,puXFCB    ; Pointer to unopened extended FCB.
         mov     ah,13h          ; Ask MS-DOS to delete file(s).
         int     21h
         cbw                     ; Return value of 0 or -1.
 cEnd
 \SAMPCODE\DOS_ENCY\SECTION5\FXN14H.ASM

         ;************************************************************;
         ;                                                            ;
         ;           Function 14H: Sequential Read, FCB-based         ;
         ;                                                            ;
         ;           int FCB_sread(oXFCB)                             ;
         ;               char *oXFCB;                                 ;
         ;                                                            ;
         ;           Returns 0 if record read OK, otherwise           ;
         ;           returns error code 1, 2, or 3.                   ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_sread,PUBLIC,ds
 parmDP  poXFCB
 cBegin
         loadDP  ds,dx,poXFCB    ; Pointer to opened extended FCB.
         mov     ah,14h          ; Ask MS-DOS to read next record,
                                 ; placing it at DTA.
         int     21h
         cbw                     ; Clear high byte for return value.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN15H.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 15H: Sequential Write, FCB-based         ;
         ;                                                            ;
         ;          int FCB_swrite(oXFCB)                             ;
         ;              char *oXFCB;                                  ;
         ;                                                            ;
         ;          Returns 0 if record read OK, otherwise            ;
         ;          returns error code 1 or 2.                        ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_swrite,PUBLIC,ds
 parmDP  poXFCB
 cBegin
         loadDP  ds,dx,poXFCB    ; Pointer to opened extended FCB.
         mov     ah,15h          ; Ask MS-DOS to write next record
                                 ; from DTA to disk file.
         int     21h
         cbw                     ; Clear high byte for return value.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN16H.ASM

         ;************************************************************;
         ;                                                            ;
         ;         Function 16H: Create File, FCB-based               ;
         ;                                                            ;
         ;         int FCB_create(uXFCB,recsize)                      ;
         ;             char *uXFCB;                                   ;
         ;             int recsize;                                   ;
         ;                                                            ;
         ;         Returns 0 if file created OK, otherwise            ;
         ;         returns -1.                                        ;
         ;                                                            ;
         ;         Note: uXFCB must have the drive and filename       ;
         ;         fields (bytes 07H through 12H) and the             ;
         ;         extension flag (byte 00H) set before the           ;
         ;         call to FCB_create (see Function 29H).             ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_create,PUBLIC,ds
 parmDP  puXFCB
 parmW   recsize
 cBegin
         loadDP  ds,dx,puXFCB    ; Pointer to unopened extended FCB.
         mov     ah,16h          ; Ask MS-DOS to create file.
         int     21h
         add     dx,7            ; Advance pointer to start of regular
                                 ; FCB.
         mov     bx,dx           ; BX = FCB pointer.
         mov     dx,recsize      ; Get record size parameter.
         mov     [bx+0eh],dx     ; Store record size in FCB.
         xor     dx,dx
         mov     [bx+20h],dl     ; Set current-record
         mov     [bx+21h],dx     ; and relative-record
         mov     [bx+23h],dx     ; fields to 0.
         cbw                     ; Set return value to 0 or -1.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN17H.ASM

         ;************************************************************;
         ;                                                            ;
         ;         Function 17H: Rename File(s), FCB-based            ;
         ;                                                            ;
         ;         int FCB_rename(uXFCBold,uXFCBnew)                  ;
         ;             char *uXFCBold,*uXFCBnew;                      ;
         ;                                                            ;
         ;         Returns 0 if file(s) renamed OK, otherwise         ;
         ;         returns -1.                                        ;
         ;                                                            ;
         ;         Note: Both uXFCB's must have the drive and         ;
         ;         filename fields (bytes 07H through 12H) and        ;
         ;         the extension flag (byte 00H) set before           ;
         ;         the call to FCB_rename (see Function 29H).         ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_rename,PUBLIC,<ds,si,di>
 parmDP  puXFCBold
 parmDP  puXFCBnew
 cBegin
         loadDP  es,di,puXFCBold ; ES:DI = Pointer to uXFCBold.
         mov     dx,di           ; Save offset in DX.
         add     di,7            ; Advance pointer to start of regular
                                 ; FCBold.
         loadDP  ds,si,puXFCBnew ; DS:SI = Pointer to uXFCBnew.
         add     si,8            ; Advance pointer to filename field
                                 ; FCBnew.
                                 ; Copy name from FCBnew into FCBold
                                 ; at offset 11H:
         add     di,11h          ; DI points 11H bytes into old FCB.
         mov     cx,0bh          ; Copy 0BH bytes, moving new
         rep     movsb           ; name into old FCB.
         push    es              ; Set DS to segment of FCBold.
         pop     ds
         mov     ah,17h          ; Ask MS-DOS to rename old
         int     21h             ; file(s) to new name(s).
         cbw                     ; Set return flag to 0 or -1.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN19H.ASM

         ;************************************************************;
         ;                                                            ;
         ;           Function 19H: Get Current Disk                   ;
         ;                                                            ;
         ;           int cur_drive()                                  ;
         ;                                                            ;
         ;           Returns letter of current "logged" disk.         ;
         ;                                                            ;
         ;************************************************************;

 cProc   cur_drive,PUBLIC
 cBegin
         mov     ah,19h          ; Set function code.
         int     21h             ; Get number of logged disk.
         add     al,'A'          ; Convert number to letter.
         cbw                     ; Clear the high byte of return value.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN1AH.ASM

         ;************************************************************;
         ;                                                            ;
         ;                Function 1AH: Set DTA Address               ;
         ;                                                            ;
         ;                int set_DTA(pDTAbuffer)                     ;
         ;                    char far *pDTAbuffer;                   ;
         ;                                                            ;
         ;                Returns 0.                                  ;
         ;                                                            ;
         ;************************************************************;

 cProc   set_DTA,PUBLIC,ds
 parmD   pDTAbuffer
 cBegin
         lds     dx,pDTAbuffer   ; DS:DX = pointer to buffer.
         mov     ah,1ah          ; Set function code.
         int     21h             ; Ask MS-DOS to change DTA address.
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN1CH.ASM

         ;************************************************************;
         ;                                                            ;
         ;      Function 1CH: Get Drive Data                          ;
         ;                                                            ;
         ;      Get information about the disk in the specified       ;
         ;      drive.  Set drive_ltr to 0 for default drive info.    ;
         ;                                                            ;
         ;      int get_drive_data(drive_ltr,                         ;
         ;             pbytes_per_sector,                             ;
         ;             psectors_per_cluster,                          ;
         ;             pclusters_per_drive)                           ;
         ;           int  drive_ltr;                                  ;
         ;           int *pbytes_per_sector;                          ;
         ;           int *psectors_per_cluster;                       ;
         ;           int *pclusters_per_drive;                        ;
         ;                                                            ;
         ;      Returns -1 for invalid drive, otherwise returns       ;
         ;      the disk's type (from the 1st byte of the FAT).       ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_drive_data,PUBLIC,<ds,si>
 parmB   drive_ltr
 parmDP  pbytes_per_sector
 parmDP  psectors_per_cluster
 parmDP  pclusters_per_drive
 cBegin
         mov     si,ds           ; Save DS in SI to use later.
         mov     dl,drive_ltr    ; Get drive letter.
         or      dl,dl           ; Leave 0 alone.
         jz      gdd
         and     dl,not 20h      ; Convert letter to uppercase.
         sub     dl,'A'-1        ; Convert to drive number: 'A' = 1,
                                 ; 'B' = 2, etc.
 gdd:
         mov     ah,1ch          ; Set function code.
         int     21h             ; Ask MS-DOS for data.
         cbw                     ; Extend AL into AH.
         cmp     al,0ffh         ; Bad drive letter?
         je      gddx            ; If so, exit with error code -1.
         mov     bl,[bx]         ; Get FAT ID byte from DS:BX.
         mov     ds,si           ; Get back original DS.
         loadDP  ds,si,pbytes_per_sector
         mov     [si],cx         ; Return bytes per sector.
         loadDP  ds,si,psectors_per_cluster
         mov     ah,0
         mov     [si],ax         ; Return sectors per cluster.
         loadDP  ds,si,pclusters_per_drive
         mov     [si],dx         ; Return clusters per drive.
         mov     al,bl           ; Return FAT ID byte.
 gddx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN21H.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 21H: Random File Read, FCB-based         ;
         ;                                                            ;
         ;          int FCB_rread(oXFCB,recnum)                       ;
         ;              char *oXFCB;                                  ;
         ;              long recnum;                                  ;
         ;                                                            ;
         ;          Returns 0 if record read OK, otherwise            ;
         ;          returns error code 1, 2, or 3.                    ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_rread,PUBLIC,ds
 parmDP  poXFCB
 parmD   recnum
 cBegin
         loadDP  ds,dx,poXFCB    ; Pointer to opened extended FCB.
         mov     bx,dx           ; BX points at FCB, too.
         mov     ax,word ptr (recnum)    ; Get low 16 bits of record
         mov     [bx+28h],ax             ; number and store in FCB.
         mov     ax,word ptr (recnum+2)  ; Get high 16 bits of record
         mov     [bx+2ah],ax             ; number and store in FCB.
         mov     ah,21h          ; Ask MS-DOS to read recnum'th
                                 ; record, placing it at DTA.
         int     21h
         cbw                     ; Clear high byte of return value.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN22H.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 22H: Random File Write, FCB-based        ;
         ;                                                            ;
         ;          int FCB_rwrite(oXFCB,recnum)                      ;
         ;              char *oXFCB;                                  ;
         ;              long recnum;                                  ;
         ;                                                            ;
         ;          Returns 0 if record read OK, otherwise            ;
         ;          returns error code 1 or 2.                        ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_rwrite,PUBLIC,ds
 parmDP  poXFCB
 parmD   recnum
 cBegin
         loadDP  ds,dx,poXFCB    ; Pointer to opened extended FCB.
         mov     bx,dx           ; BX points at FCB, too.
         mov     ax,word ptr (recnum)    ; Get low 16 bits of record
         mov     [bx+28h],ax             ; number and store in FCB.
         mov     ax,word ptr (recnum+2)  ; Get high 16 bits of record
         mov     [bx+2ah],ax             ; number and store in FCB.
         mov     ah,22h          ; Ask MS-DOS to write DTA to
         int     21h             ; recnum'th record of file.
         cbw                     ; Clear high byte for return value.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN23H.ASM

         ;************************************************************;
         ;                                                            ;
         ;      Function 23H: Get File Size, FCB-based                ;
         ;                                                            ;
         ;      long FCB_nrecs(uXFCB,recsize)                         ;
         ;           char *uXFCB;                                     ;
         ;           int recsize;                                     ;
         ;                                                            ;
         ;      Returns a long -1 if file not found, otherwise        ;
         ;      returns the number of records of size recsize.        ;
         ;                                                            ;
         ;      Note: uXFCB must have the drive and                   ;
         ;      filename fields (bytes 07H through 12H) and           ;
         ;      the extension flag (byte 00H) set before              ;
         ;      the call to FCB_nrecs (see Function 29H).             ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_nrecs,PUBLIC,ds
 parmDP  puXFCB
 parmW   recsize
 cBegin
         loadDP  ds,dx,puXFCB    ; Pointer to unopened extended FCB.
         mov     bx,dx           ; Copy FCB pointer into BX.
         mov     ax,recsize      ; Get record size
         mov     [bx+15h],ax     ; and store it in FCB.
         mov     ah,23h          ; Ask MS-DOS for file size (in
                                 ; records).
         int     21h
         cbw                     ; If AL = 0FFH, set AX to -1.
         cwd                     ; Extend to long.
         or      dx,dx           ; Is DX negative?
         js      nr_exit         ; If so, exit with error flag.
         mov     [bx+2bh],al     ; Only low 24 bits of the relative-
                                 ; record field are used, so clear the
                                 ; top 8 bits.
         mov     ax,[bx+28h]     ; Return file length in DX:AX.
         mov     dx,[bx+2ah]
 nr_exit:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN24H.ASM

         ;************************************************************;
         ;                                                            ;
         ;              Function 24H: Set Relative Record             ;
         ;                                                            ;
         ;              int FCB_set_rrec(oXFCB)                       ;
         ;                  char *oXFCB;                              ;
         ;                                                            ;
         ;              Returns 0.                                    ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_set_rrec,PUBLIC,ds
 parmDP  poXFCB
 cBegin
         loadDP  ds,dx,poXFCB    ; Pointer to opened extended FCB.
         mov     bx,dx           ; BX points at FCB, too.
         mov     byte ptr [bx+2bh],0 ; Zero high byte of high word of
                                     ; relative-record field.
         mov     ah,24h          ; Ask MS-DOS to set relative record
                                 ; to current record.
         int     21h
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN25H.ASM

         ;************************************************************;
         ;                                                            ;
         ;              Function 25H: Set Interrupt Vector            ;
         ;                                                            ;
         ;              typedef void (far *FCP)();                    ;
         ;              int set_vector(intnum,vector)                 ;
         ;                  int intnum;                               ;
         ;                  FCP vector;                               ;
         ;                                                            ;
         ;              Returns 0.                                    ;
         ;                                                            ;
         ;************************************************************;

 cProc   set_vector,PUBLIC,ds
 parmB   intnum
 parmD   vector
 cBegin
         lds     dx,vector       ; Get vector segment:offset into
                                 ; DS:DX.
         mov     al,intnum       ; Get interrupt number into AL.
         mov     ah,25h          ; Select "set vector" function.
         int     21h             ; Ask MS-DOS to change vector.
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN26H.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 26H: Create New Program Segment Prefix      ;
         ;                                                            ;
         ;       int create_psp(pspseg)                               ;
         ;           int  pspseg;                                     ;
         ;                                                            ;
         ;       Returns 0.                                           ;
         ;                                                            ;
         ;************************************************************;

 cProc   create_psp,PUBLIC
 parmW   pspseg
 cBegin
         mov     dx,pspseg       ; Get segment address of new PSP.
         mov     ah,26h          ; Set function code.
         int     21h             ; Ask MS-DOS to create new PSP.
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN27H.ASM

         ;************************************************************;
         ;                                                            ;
         ;      Function 27H: Random File Block Read, FCB-based       ;
         ;                                                            ;
         ;      int FCB_rblock(oXFCB,nrequest,nactual,start)          ;
         ;          char *oXFCB;                                      ;
         ;          int   nrequest;                                   ;
         ;          int  *nactual;                                    ;
         ;          long  start;                                      ;
         ;                                                            ;
         ;      Returns read status 0, 1, 2, or 3 and sets            ;
         ;      nactual to number of records actually read.           ;
         ;                                                            ;
         ;      If start is -1, the relative-record field is          ;
         ;      not changed, causing the block to be read starting    ;
         ;      at the current record.                                ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_rblock,PUBLIC,<ds,di>
 parmDP  poXFCB
 parmW   nrequest
 parmDP  pnactual
 parmD   start
 cBegin
         loadDP  ds,dx,poXFCB    ; Pointer to opened extended FCB.
         mov     di,dx           ; DI points at FCB, too.
         mov     ax,word ptr (start) ; Get long value of start.
         mov     bx,word ptr (start+2)
         mov     cx,ax           ; Is start = -1?
         and     cx,bx
         inc     cx
         jcxz    rb_skip         ; If so, don't change relative-record
                                 ; field.
         mov     [di+28h],ax     ; Otherwise, seek to start record.
         mov     [di+2ah],bx
 rb_skip:
         mov     cx,nrequest     ; CX = number of records to read.
         mov     ah,27h          ; Get MS-DOS to read CX records,
         int     21h             ; placing them at DTA.
         loadDP  ds,bx,pnactual  ; DS:BX = address of nactual.
         mov     [bx],cx         ; Return number of records read.
         cbw                     ; Clear high byte.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN28H.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 28H: Random File Block Write, FCB-based     ;
         ;                                                            ;
         ;       int FCB_wblock(oXFCB,nrequest,nactual,start)         ;
         ;           char *oXFCB;                                     ;
         ;           int   nrequest;                                  ;
         ;           int  *nactual;                                   ;
         ;           long  start;                                     ;
         ;                                                            ;
         ;       Returns write status of 0, 1, or 2 and sets          ;
         ;       nactual to number of records actually written.       ;
         ;                                                            ;
         ;       If start is -1, the relative-record field is         ;
         ;       not changed, causing the block to be written         ;
         ;       starting at the current record.                      ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_wblock,PUBLIC,<ds,di>
 parmDP  poXFCB
 parmW   nrequest
 parmDP  pnactual
 parmD   start
 cBegin
         loadDP  ds,dx,poXFCB    ; Pointer to opened extended FCB.
         mov     di,dx           ; DI points at FCB, too.
         mov     ax,word ptr (start) ; Get long value of start.
         mov     bx,word ptr (start+2)
         mov     cx,ax           ; Is start = -1?
         and     cx,bx
         inc     cx
         jcxz    wb_skip         ; If so, don't change relative-record
                                 ; field.
         mov     [di+28h],ax     ; Otherwise, seek to start record.
         mov     [di+2ah],bx
 wb_skip:
         mov     cx,nrequest     ; CX = number of records to write.
         mov     ah,28h          ; Get MS-DOS to write CX records
         int     21h             ; from DTA to file.
         loadDP  ds,bx,pnactual  ; DS:BX = address of nactual.
         mov     ds:[bx],cx      ; Return number of records written.
         cbw                     ; Clear high byte.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN29H.ASM

         ;************************************************************;
         ;                                                            ;
         ;            Function 29H: Parse Filename into FCB           ;
         ;                                                            ;
         ;            int FCB_parse(uXFCB,name,ctrl)                  ;
         ;                char *uXFCB;                                ;
         ;                char *name;                                 ;
         ;                int ctrl;                                   ;
         ;                                                            ;
         ;            Returns -1 if error,                            ;
         ;                     0 if no wildcards found,               ;
         ;                     1 if wildcards found.                  ;
         ;                                                            ;
         ;************************************************************;

 cProc   FCB_parse,PUBLIC,<ds,si,di>
 parmDP  puXFCB
 parmDP  pname
 parmB   ctrl
 cBegin
         loadDP  es,di,puXFCB    ; Pointer to unopened extended FCB.
         push    di              ; Save DI.
         xor     ax,ax           ; Fill all 22 (decimal) words of the
                                 ; extended FCB with zeros.
         cld                     ; Make sure direction flag says UP.
         mov     cx,22d
         rep     stosw
         pop     di              ; Recover DI.
         mov     byte ptr [di],0ffh ; Set flag byte to mark this as an
                                    ; extended FCB.
         add     di,7            ; Advance pointer to start of regular
                                 ; FCB.
         loadDP  ds,si,pname     ; Get pointer to filename into DS:SI.
         mov     al,ctrl         ; Get parse control byte.
         mov     ah,29h          ; Parse filename, please.
         int     21h
         cbw                     ; Set return parameter.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN2AH.ASM

         ;************************************************************;
         ;                                                            ;
         ;            Function 2AH: Get Date                          ;
         ;                                                            ;
         ;            long get_date(pdow,pmonth,pday,pyear)           ;
         ;                 char *pdow,*pmonth,*pday;                  ;
         ;                 int *pyear;                                ;
         ;                                                            ;
         ;            Returns the date packed into a long:            ;
         ;                 low byte  = day of month                   ;
         ;                 next byte = month                          ;
         ;                 next word = year.                          ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_date,PUBLIC,ds
 parmDP  pdow
 parmDP  pmonth
 parmDP  pday
 parmDP  pyear
 cBegin
         mov     ah,2ah          ; Set function code.
         int     21h             ; Get date info from MS-DOS.
         loadDP  ds,bx,pdow      ; DS:BX = pointer to dow.
         mov     [bx],al         ; Return dow.
         loadDP  ds,bx,pmonth    ; DS:BX = pointer to month.
         mov     [bx],dh         ; Return month.
         loadDP  ds,bx,pday      ; DS:BX = pointer to day.
         mov     [bx],dl         ; Return day.
         loadDP  ds,bx,pyear     ; DS:BX = pointer to year.
         mov     [bx],cx         ; Return year.
         mov     ax,dx           ; Pack day, month, ...
         mov     dx,cx           ; ... and year into return value.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN2BH.ASM

         ;************************************************************;
         ;                                                            ;
         ;             Function 2BH: Set Date                         ;
         ;                                                            ;
         ;             int set_date(month,day,year)                   ;
         ;                 char month,day;                            ;
         ;                 int year;                                  ;
         ;                                                            ;
         ;             Returns 0 if date was OK, -1 if not.           ;
         ;                                                            ;
         ;************************************************************;

 cProc   set_date,PUBLIC
 parmB   month
 parmB   day
 parmW   year
 cBegin
         mov     dh,month        ; Get new month.
         mov     dl,day          ; Get new day.
         mov     cx,year         ; Get new year.
         mov     ah,2bh          ; Set function code.
         int     21h             ; Ask MS-DOS to change date.
         cbw                     ; Return 0 or -1.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN2CH.ASM

         ;************************************************************;
         ;                                                            ;
         ;             Function 2CH: Get Time                         ;
         ;                                                            ;
         ;             long get_time(phour,pmin,psec,phund)           ;
         ;                  char *phour,*pmin,*psec,*phund;           ;
         ;                                                            ;
         ;             Returns the time packed into a long:           ;
         ;                  low byte  = hundredths                    ;
         ;                  next byte = seconds                       ;
         ;                  next byte = minutes                       ;
         ;                  next byte = hours                         ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_time,PUBLIC,ds
 parmDP  phour
 parmDP  pmin
 parmDP  psec
 parmDP  phund
 cBegin
         mov     ah,2ch          ; Set function code.
         int     21h             ; Get time from MS-DOS.
         loadDP  ds,bx,phour     ; DS:BX = pointer to hour.
         mov     [bx],ch         ; Return hour.
         loadDP  ds,bx,pmin      ; DS:BX = pointer to min.
         mov     [bx],cl         ; Return min.
         loadDP  ds,bx,psec      ; DS:BX = pointer to sec.
         mov     [bx],dh         ; Return sec.
         loadDP  ds,bx,phund     ; DS:BX = pointer to hund.
         mov     [bx],dl         ; Return hund.
         mov     ax,dx           ; Pack seconds, hundredths, ...
         mov     dx,cx           ; ... minutes, and hour into
                                 ; return value.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN2DH.ASM

         ;************************************************************;
         ;                                                            ;
         ;             Function 2DH: Set Time                         ;
         ;                                                            ;
         ;             int set_time(hour,min,sec,hund)                ;
         ;                 char hour,min,sec,hund;                    ;
         ;                                                            ;
         ;             Returns 0 if time was OK, -1 if not.           ;
         ;                                                            ;
         ;************************************************************;

 cProc   set_time,PUBLIC
 parmB   hour
 parmB   min
 parmB   sec
 parmB   hund
 cBegin
         mov     ch,hour         ; Get new hour.
         mov     cl,min          ; Get new minutes.
         mov     dh,sec          ; Get new seconds.
         mov     dl,hund         ; Get new hundredths.
         mov     ah,2dh          ; Set function code.
         int     21h             ; Ask MS-DOS to change time.
         cbw                     ; Return 0 or -1.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN2EH.ASM

         ;************************************************************;
         ;                                                            ;
         ;             Function 2EH: Set/Reset Verify Flag            ;
         ;                                                            ;
         ;             int set_verify(newvflag)                       ;
         ;                 char newvflag;                             ;
         ;                                                            ;
         ;             Returns 0.                                     ;
         ;                                                            ;
         ;************************************************************;

 cProc   set_verify,PUBLIC
 parmB   newvflag
 cBegin
         mov     al,newvflag     ; Get new value of verify flag.
         mov     ah,2eh          ; Set function code.
         int     21h             ; Ask MS-DOS to store flag.
         xor     ax,ax           ; Return 0.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN2FH.ASM

         ;************************************************************;
         ;                                                            ;
         ;           Function 2FH: Get DTA Address                    ;
         ;                                                            ;
         ;           char far *get_DTA()                              ;
         ;                                                            ;
         ;           Returns a far pointer to the DTA buffer.         ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_DTA,PUBLIC
 cBegin
         mov     ah,2fh          ; Set function code.
         int     21h             ; Ask MS-DOS for current DTA address.
         mov     ax,bx           ; Return offset in AX.
         mov     dx,es           ; Return segment in DX.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN30H.ASM

         ;************************************************************;
         ;                                                            ;
         ;           Function 30H: Get MS-DOS Version Number          ;
         ;                                                            ;
         ;           int DOS_version()                                ;
         ;                                                            ;
         ;           Returns number of MS-DOS version, with           ;
         ;              major version in high byte,                   ;
         ;              minor version in low byte.                    ;
         ;                                                            ;
         ;************************************************************;

 cProc   DOS_version,PUBLIC
 cBegin
         mov     ax,3000H        ; Set function code and clear AL.
         int     21h             ; Ask MS-DOS for version number.
         xchg    al,ah           ; Swap major and minor numbers.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN31H.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 31H: Terminate and Stay Resident         ;
         ;                                                            ;
         ;          void keep_process(exit_code,nparas)               ;
         ;               int exit_code,nparas;                        ;
         ;                                                            ;
         ;          Does NOT return!                                  ;
         ;                                                            ;
         ;************************************************************;

 cProc   keep_process,PUBLIC
 parmB   exit_code
 parmW   nparas
 cBegin
         mov     al,exit_code    ; Get return code.
         mov     dx,nparas       ; Set DX to number of paragraphs the
                                 ; program wants to keep.
         mov     ah,31h          ; Set function code.
         int     21h             ; Ask MS-DOS to keep process.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN33H.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 33H: Get/Set Control-C Check Flag        ;
         ;                                                            ;
         ;          int controlC(func,state)                          ;
         ;              int func,state;                               ;
         ;                                                            ;
         ;          Returns current state of Ctrl-C flag.             ;
         ;                                                            ;
         ;************************************************************;

 cProc   controlC,PUBLIC
 parmB   func
 parmB   state
 cBegin
         mov     al,func         ; Get set/reset function.
         mov     dl,state        ; Get new value if present.
         mov     ah,33h          ; MS-DOS ^C check function.
         int     21h             ; Call MS-DOS.
         mov     al,dl           ; Return current state.
         cbw                     ; Clear high byte of return value.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN34H.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 34H: Get Return Address of InDOS Flag       ;
         ;                                                            ;
         ;       char far *inDOS_ptr()                                ;
         ;                                                            ;
         ;       Returns a far pointer to the MS-DOS inDOS flag.      ;
         ;                                                            ;
         ;************************************************************;

 cProc   inDOS_ptr,PUBLIC
 cBegin
         mov     ah,34h          ; InDOS flag function.
         int     21h             ; Call MS-DOS.
         mov     ax,bx           ; Return offset in AX.
         mov     dx,es           ; Return segment in DX.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN35H.ASM

         ;************************************************************;
         ;                                                            ;
         ;           Function 35H: Get Interrupt Vector               ;
         ;                                                            ;
         ;           typedef void (far *FCP)();                       ;
         ;           FCP get_vector(intnum)                           ;
         ;               int intnum;                                  ;
         ;                                                            ;
         ;           Returns a far code pointer that is the           ;
         ;           segment:offset of the interrupt vector.          ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_vector,PUBLIC
 parmB   intnum
 cBegin
         mov     al,intnum       ; Get interrupt number into AL.
         mov     ah,35h          ; Select "get vector" function.
         int     21h             ; Call MS-DOS.
         mov     ax,bx           ; Return vector offset.
         mov     dx,es           ; Return vector segment.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN36H.ASM

         ;************************************************************;
         ;                                                            ;
         ;             Function 36H: Get Disk Free Space              ;
         ;                                                            ;
         ;             long free_space(drive_ltr)                     ;
         ;                 char drive_ltr;                            ;
         ;                                                            ;
         ;             Returns the number of bytes free as            ;
         ;             a long integer.                                ;
         ;                                                            ;
         ;************************************************************;

 cProc   free_space,PUBLIC
 parmB   drive_ltr
 cBegin
         mov     dl,drive_ltr    ; Get drive letter.
         or      dl,dl           ; Leave 0 alone.
         jz      fsp
         and     dl,not 20h      ; Convert letter to uppercase.
         sub     dl,'A'-1        ; Convert to drive number: 'A' = 1,
                                 ; 'B' = 2, etc.
 fsp:
         mov     ah,36h          ; Set function code.
         int     21h             ; Ask MS-DOS to get disk information.
         mul     cx              ; Bytes/sector * sectors/cluster
         mul     bx              ; * free clusters.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN38H.ASM

         ;************************************************************;
         ;                                                            ;
         ;         Function 38H: Get/Set Current Country Data         ;
         ;                                                            ;
         ;         int country_info(country,pbuffer)                  ;
         ;             char country,*pbuffer;                         ;
         ;                                                            ;
         ;         Returns -1 if the "country" code is invalid.       ;
         ;                                                            ;
         ;************************************************************;

 cProc   country_info,PUBLIC,ds
 parmB   country
 parmDP  pbuffer
 cBegin
         mov     al,country      ; Get country code.
         loadDP  ds,dx,pbuffer   ; Get buffer pointer (or -1).
         mov     ah,38h          ; Set function code.
         int     21h             ; Ask MS-DOS to get country
                                 ; information.
         jnb     cc_ok           ; Branch if country code OK.
         mov     ax,-1           ; Else return -1.
 cc_ok:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN39H.ASM

         ;************************************************************;
         ;                                                            ;
         ;              Function 39H: Create Directory                ;
         ;                                                            ;
         ;              int make_dir(pdirpath)                        ;
         ;                  char *pdirpath;                           ;
         ;                                                            ;
         ;              Returns 0 if directory created OK,            ;
         ;              otherwise returns error code.                 ;
         ;                                                            ;
         ;************************************************************;

 cProc   make_dir,PUBLIC,ds
 parmDP  pdirpath
 cBegin
         loadDP  ds,dx,pdirpath  ; Get pointer to pathname.
         mov     ah,39h          ; Set function code.
         int     21h             ; Ask MS-DOS to make new subdirectory.
         jb      md_err          ; Branch on error.
         xor     ax,ax           ; Else return 0.
 md_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN40H.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 40H: Write File or Device                ;
         ;                                                            ;
         ;          int write(handle,pbuffer,nbytes)                  ;
         ;              int handle,nbytes;                            ;
         ;              char *pbuffer;                                ;
         ;                                                            ;
         ;          Returns -1 if there was a write error,            ;
         ;          otherwise returns number of bytes written.        ;
         ;                                                            ;
         ;************************************************************;

 cProc   write,PUBLIC,ds
 parmW   handle
 parmDP  pbuffer
 parmW   nbytes
 cBegin
         mov     bx,handle       ; Get handle.
         loadDP  ds,dx,pbuffer   ; Get pointer to buffer.
         mov     cx,nbytes       ; Get number of bytes to write.
         mov     ah,40h          ; Set function code.
         int     21h             ; Ask MS-DOS to write CX bytes.
         jnb     wr_ok           ; Branch if write successful.
         mov     ax,-1           ; Else return -1.
 wr_ok:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN41H.ASM

         ;************************************************************;
         ;                                                            ;
         ;                Function 41H: Delete File                   ;
         ;                                                            ;
         ;                int delete(pfilepath)                       ;
         ;                    char *pfilepath;                        ;
         ;                                                            ;
         ;                Returns 0 if file deleted,                  ;
         ;                otherwise returns error code.               ;
         ;                                                            ;
         ;************************************************************;

 cProc   delete,PUBLIC,ds
 parmDP  pfilepath
 cBegin
         loadDP  ds,dx,pfilepath ; Get pointer to pathname.
         mov     ah,41h          ; Set function code.
         int     21h             ; Ask MS-DOS to delete file.
         jb       dl_err         ; Branch if MS-DOS could not delete
                                 ; file.
         xor     ax,ax           ; Else return 0.
 dl_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN42H.ASM

         ;************************************************************;
         ;                                                            ;
         ;           Function 42H: Move File Pointer                  ;
         ;                                                            ;
         ;           long seek(handle,distance,mode)                  ;
         ;                int handle,mode;                            ;
         ;                long distance;                              ;
         ;                                                            ;
         ;           Modes:                                           ;
         ;                   0: from beginning of file                ;
         ;                   1: from the current position             ;
         ;                   2: from the end of the file              ;
         ;                                                            ;
         ;           Returns -1 if there was a seek error,            ;
         ;           otherwise returns long pointer position.         ;
         ;                                                            ;
         ;************************************************************;

 cProc   seek,PUBLIC
 parmW   handle
 parmD   distance
 parmB   mode
 cBegin
         mov     bx,handle       ; Get handle.
         les     dx,distance     ; Get distance into ES:DX.
         mov     cx,es           ; Put high word of distance into CX.
         mov     al,mode         ; Get move method code.
         mov     ah,42h          ; Set function code.
         int     21h             ; Ask MS-DOS to move file pointer.
         jnb     sk_ok           ; Branch if seek successful.
         mov     ax,-1           ; Else return -1.
         cwd
 sk_ok:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN43H.ASM

         ;************************************************************;
         ;                                                            ;
         ;            Function 43H: Get/Set File Attributes           ;
         ;                                                            ;
         ;            int file_attr(pfilepath,func,attr)              ;
         ;                char *pfilepath;                            ;
         ;                int func,attr;                              ;
         ;                                                            ;
         ;            Returns -1 for all errors,                      ;
         ;            otherwise returns file attribute.               ;
         ;                                                            ;
         ;************************************************************;

 cProc   file_attr,PUBLIC,ds
 parmDP  pfilepath
 parmB   func
 parmW   attr
 cBegin
         loadDP  ds,dx,pfilepath ; Get pointer to pathname.
         mov     al,func         ; Get/set flag into AL.
         mov     cx,attr         ; Get new attr (if present).
         mov     ah,43h          ; Set code function.
         int     21h             ; Call MS-DOS.
         jnb     fa_ok           ; Branch if no error.
         mov     cx,-1           ; Else return -1.
 fa_ok:
         mov     ax,cx           ; Return this value.

 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4400H.ASM

         ;************************************************************;
         ;                                                            ;
         ;        Function 44H, Subfunctions 00H,01H:                 ;
         ;                      Get/Set IOCTL Device Data             ;
         ;                                                            ;
         ;        int ioctl_char_flags(setflag,handle,newflags)       ;
         ;            int setflag;                                    ;
         ;            int handle;                                     ;
         ;            int newflags;                                   ;
         ;                                                            ;
         ;        Set setflag = 0 to get flags, 1 to set flags.       ;
         ;                                                            ;
         ;        Returns -1 for error, else returns flags.           ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_char_flags,PUBLIC
 parmB   setflag
 parmW   handle
 parmW   newflags
 cBegin
         mov     al,setflag      ; Get setflag.
         and     al,1            ; Save only lsb.
         mov     bx,handle       ; Get handle to character device.
         mov     dx,newflags     ; Get new flags (they are used only
                                 ; by "set" option).
         mov     ah,44h          ; Set function code.
         int     21h             ; Call MS-DOS.
         mov     ax,dx           ; Assume success - prepare to return
                                 ; flags.
         jnc     iocfx           ; Branch if no error.
         mov     ax,-1           ; Else return error flag.
 iocfx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4401H.ASM

         ;************************************************************;
         ;                                                            ;
         ;        Function 44H, Subfunctions 00H,01H:                 ;
         ;                      Get/Set IOCTL Device Data             ;
         ;                                                            ;
         ;        int ioctl_char_flags(setflag,handle,newflags)       ;
         ;            int setflag;                                    ;
         ;            int handle;                                     ;
         ;            int newflags;                                   ;
         ;                                                            ;
         ;        Set setflag = 0 to get flags, 1 to set flags.       ;
         ;                                                            ;
         ;        Returns -1 for error, else returns flags.           ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_char_flags,PUBLIC
 parmB   setflag
 parmW   handle
 parmW   newflags
 cBegin
         mov     al,setflag      ; Get setflag.
         and     al,1            ; Save only lsb.
         mov     bx,handle       ; Get handle to character device.
         mov     dx,newflags     ; Get new flags (they are used only
                                 ; by "set" option).
         mov     ah,44h          ; Set function code.
         int     21h             ; Call MS-DOS.
         mov     ax,dx           ; Assume success - prepare to return
                                 ; flags.
         jnc     iocfx           ; Branch if no error.
         mov     ax,-1           ; Else return error flag.
 iocfx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4402H.ASM

         ;************************************************************;
         ;                                                            ;
         ;     Function 44H, Subfunctions 02H,03H:                    ;
         ;                   IOCTL Character Device Control           ;
         ;                                                            ;
         ;     int ioctl_char_ctrl(recvflag,handle,pbuffer,nbytes)    ;
         ;         int   recvflag;                                    ;
         ;         int   handle;                                      ;
         ;         char *pbuffer;                                     ;
         ;         int   nbytes;                                      ;
         ;                                                            ;
         ;     Set recvflag = 0 to get flags, 1 to set flags.         ;
         ;                                                            ;
         ;     Returns -1 for error, otherwise returns number of      ;
         ;     bytes sent or received.                                ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_char_ctrl,PUBLIC,<ds>
 parmB   recvflag
 parmW   handle
 parmDP  pbuffer
 parmW   nbytes
 cBegin
         mov     al,recvflag     ; Get recvflag.
         and     al,1            ; Keep only lsb.
         add     al,2            ; AL = 02H for receive, 03H for send.
         mov     bx,handle       ; Get character-device handle.
         mov     cx,nbytes       ; Get number of bytes to receive/send.
         loadDP  ds,dx,pbuffer   ; Get pointer to buffer.
         mov     ah,44h          ; Set function code.
         int     21h             ; Call MS-DOS.
         jnc     iccx            ; Branch if no error.
         mov     ax,-1           ; Return -1 for all errors.
 iccx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4403H.ASM

         ;************************************************************;
         ;                                                            ;
         ;     Function 44H, Subfunctions 02H,03H:                    ;
         ;                   IOCTL Character Device Control           ;
         ;                                                            ;
         ;     int ioctl_char_ctrl(recvflag,handle,pbuffer,nbytes)    ;
         ;         int   recvflag;                                    ;
         ;         int   handle;                                      ;
         ;         char *pbuffer;                                     ;
         ;         int   nbytes;                                      ;
         ;                                                            ;
         ;     Set recvflag = 0 to get flags, 1 to set flags.         ;
         ;                                                            ;
         ;     Returns -1 for error, otherwise returns number of      ;
         ;     bytes sent or received.                                ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_char_ctrl,PUBLIC,<ds>
 parmB   recvflag
 parmW   handle
 parmDP  pbuffer
 parmW   nbytes
 cBegin
         mov     al,recvflag     ; Get recvflag.
         and     al,1            ; Keep only lsb.
         add     al,3            ; AL = 02H for receive, 03H for send.
         mov     bx,handle       ; Get character-device handle.
         mov     cx,nbytes       ; Get number of bytes to receive/send.
         loadDP  ds,dx,pbuffer   ; Get pointer to buffer.
         mov     ah,44h          ; Set function code.
         int     21h             ; Call MS-DOS.
         jnc     iccx            ; Branch if no error.
         mov     ax,-1           ; Return -1 for all errors.
 iccx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4404H.ASM

         ;************************************************************;
         ;                                                            ;
         ;   Function 44H, Subfunctions 04H,05H:                      ;
         ;                 IOCTL Block Device Control                 ;
         ;                                                            ;
         ;   int ioctl_block_ctrl(recvflag,drive_ltr,pbuffer,nbytes)  ;
         ;       int   recvflag;                                      ;
         ;       int   drive_ltr;                                     ;
         ;       char *pbuffer;                                       ;
         ;       int   nbytes;                                        ;
         ;                                                            ;
         ;   Set recvflag = 0 to receive info, 1 to send.             ;
         ;                                                            ;
         ;   Returns -1 for error, otherwise returns number of        ;
         ;   bytes sent or received.                                  ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_block_ctrl,PUBLIC,<ds>
 parmB   recvflag
 parmB   drive_ltr
 parmDP  pbuffer
 parmW   nbytes
 cBegin
         mov     al,recvflag     ; Get recvflag.
         and     al,1            ; Keep only lsb.
         add     al,4            ; AL = 04H for receive, 05H for send.
         mov     bl,drive_ltr    ; Get drive letter.
         or      bl,bl           ; Leave 0 alone.
         jz      ibc
         and     bl,not 20h      ; Convert letter to uppercase.
         sub     bl,'A'-1        ; Convert to drive number: 'A' = 1,
                                 ; 'B' = 2, etc.
 ibc:
         mov     cx,nbytes       ; Get number of bytes to recv/send.
         loadDP  ds,dx,pbuffer   ; Get pointer to buffer.
         mov     ah,44h          ; Set function code.
         int     21h             ; Call MS-DOS.
         jnc     ibcx            ; Branch if no error.
         mov     ax,-1           ; Return -1 for all errors.
 ibcx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4405H.ASM

         ;************************************************************;
         ;                                                            ;
         ;   Function 44H, Subfunctions 04H,05H:                      ;
         ;                 IOCTL Block Device Control                 ;
         ;                                                            ;
         ;   int ioctl_block_ctrl(recvflag,drive_ltr,pbuffer,nbytes)  ;
         ;       int   recvflag;                                      ;
         ;       int   drive_ltr;                                     ;
         ;       char *pbuffer;                                       ;
         ;       int   nbytes;                                        ;
         ;                                                            ;
         ;   Set recvflag = 0 to receive info, 1 to send.             ;
         ;                                                            ;
         ;   Returns -1 for error, otherwise returns number of        ;
         ;   bytes sent or received.                                  ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_block_ctrl,PUBLIC,<ds>
 parmB   recvflag
 parmB   drive_ltr
 parmDP  pbuffer
 parmW   nbytes
 cBegin
         mov     al,recvflag     ; Get recvflag.
         and     al,1            ; Keep only lsb.
         add     al,5            ; AL = 04H for receive, 05H for send.
         mov     bl,drive_ltr    ; Get drive letter.
         or      bl,bl           ; Leave 0 alone.
         jz      ibc
         and     bl,not 20h      ; Convert letter to uppercase.
         sub     bl,'A'-1        ; Convert to drive number: 'A' = 1,
                                 ; 'B' = 2, etc.
 ibc:
         mov     cx,nbytes       ; Get number of bytes to recv/send.
         loadDP  ds,dx,pbuffer   ; Get pointer to buffer.
         mov     ah,44h          ; Set function code.
         int     21h             ; Call MS-DOS.
         jnc     ibcx            ; Branch if no error.
         mov     ax,-1           ; Return -1 for all errors.
 ibcx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4406H.ASM

         ;************************************************************;
         ;                                                            ;
         ;   Function 44H, Subfunctions 06H,07H:                      ;
         ;                 IOCTL Input/Output Status                  ;
         ;                                                            ;
         ;   int ioctl_char_status(outputflag,handle)                 ;
         ;       int outputflag;                                      ;
         ;       int handle;                                          ;
         ;                                                            ;
         ;   Set outputflag = 0 for input status, 1 for output status.;
         ;                                                            ;
         ;   Returns -1 for all errors, 0 for not ready,              ;
         ;   and 1 for ready.                                         ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_char_status,PUBLIC
 parmB   outputflag
 parmW   handle
 cBegin
         mov     al,outputflag   ; Get outputflag.
         and     al,1            ; Keep only lsb.
         add     al,6            ; AL = 06H for input status, 07H for output
                                 ; status.
         mov     bx,handle       ; Get handle.
         mov     ah,44h          ; Set function code.
         int     21h             ; Call MS-DOS.
         jnc     isnoerr         ; Branch if no error.
         mov     ax,-1           ; Return error code.
         jmp     short isx
 isnoerr:
         and     ax,1            ; Keep only lsb for return value.
 isx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4407H.ASM

         ;************************************************************;
         ;                                                            ;
         ;   Function 44H, Subfunctions 06H,07H:                      ;
         ;                 IOCTL Input/Output Status                  ;
         ;                                                            ;
         ;   int ioctl_char_status(outputflag,handle)                 ;
         ;       int outputflag;                                      ;
         ;       int handle;                                          ;
         ;                                                            ;
         ;   Set outputflag = 0 for input status, 1 for output status.;
         ;                                                            ;
         ;   Returns -1 for all errors, 0 for not ready,              ;
         ;   and 1 for ready.                                         ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_char_status,PUBLIC
 parmB   outputflag
 parmW   handle
 cBegin
         mov     al,outputflag   ; Get outputflag.
         and     al,1            ; Keep only lsb.
         add     al,7            ; AL = 06H for input status, 07H for output
                                 ; status.
         mov     bx,handle       ; Get handle.
         mov     ah,44h          ; Set function code.
         int     21h             ; Call MS-DOS.
         jnc     isnoerr         ; Branch if no error.
         mov     ax,-1           ; Return error code.
         jmp     short isx
 isnoerr:
         and     ax,1            ; Keep only lsb for return value.
 isx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4408H.ASM

         ;************************************************************;
         ;                                                            ;
         ;      Function 44H, Subfunction 08H:                        ;
         ;                    IOCTL Removable Block Device Query      ;
         ;                                                            ;
         ;      int ioctl_block_fixed(drive_ltr)                      ;
         ;          int drive_ltr;                                    ;
         ;                                                            ;
         ;      Returns -1 for all errors, 1 if disk is fixed (not    ;
         ;      removable), 0 if disk is not fixed.                   ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_block_fixed,PUBLIC
 parmB   drive_ltr
 cBegin
         mov     bl,drive_ltr    ; Get drive letter.
         or      bl,bl           ; Leave 0 alone.
         jz      ibch
         and     bl,not 20h      ; Convert letter to uppercase.
         sub     bl,'A'-1        ; Convert to drive number: 'A' = 1,
                                 ; 'B' = 2, etc.
 ibch:
         mov     ax,4408h        ; Set function code, Subfunction 08H.
         int     21h             ; Call MS-DOS.
         jnc     ibchx           ; Branch if no error, AX = 0 or 1.
         cmp     ax,1            ; Treat error code of 1 as "disk is
                                 ; fixed."
         je      ibchx
         mov     ax,-1           ; Return -1 for other errors.
 ibchx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4409H.ASM

         ;************************************************************;
         ;                                                            ;
         ;        Function 44H, Subfunction 09H:                      ;
         ;                      IOCTL Remote Block Device Query       ;
         ;                                                            ;
         ;        int ioctl_block_redir(drive_ltr)                    ;
         ;            int drive_ltr;                                  ;
         ;                                                            ;
         ;        Returns -1 for all errors, 1 if disk is remote      ;
         ;        (redirected), 0 if disk is local.                   ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_block_redir,PUBLIC
 parmB   drive_ltr
 cBegin
         mov     bl,drive_ltr    ; Get drive letter.
         or      bl,bl           ; Leave 0 alone.
         jz      ibr
         and     bl,not 20h      ; Convert letter to uppercase.
         sub     bl,'A'-1        ; Convert to drive number: 'A' = 1,
                                 ; 'B' = 2, etc.
 ibr:
         mov     ax,4409h        ; Set function code, Subfunction 09H.
         int     21h             ; Call MS-DOS.
         mov     ax,-1           ; Assume error.
         jc      ibrx            ; Branch if error, returning -1.
         inc     ax              ; Set AX = 0.
         test    dh,10h          ; Is bit 12 set?
         jz      ibrx            ; If not, disk is local: Return 0.
         inc     ax              ; Return 1 for remote disk.
 ibrx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN440AH.ASM

         ;************************************************************;
         ;                                                            ;
         ;    Function 44H, Subfunction 0AH:                          ;
         ;                  IOCTL Remote Handle Query                 ;
         ;                                                            ;
         ;    int ioctl_char_redir(handle)                            ;
         ;        int handle;                                         ;
         ;                                                            ;
         ;    Returns -1 for all errors, 1 if device/file is remote   ;
         ;    (redirected), 0 if it is local.                         ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_char_redir,PUBLIC
 parmW   handle
 cBegin
         mov     bx,handle       ; Get handle.
         mov     ax,440ah        ; Set function code, Subfunction 0AH.
         int     21h             ; Call MS-DOS.
         mov     ax,-1           ; Assume error.
         jc      icrx            ; Branch on error, returning -1.
         inc     ax              ; Set AX = 0.
         test    dh,80h          ; Is bit 15 set?
         jz      icrx            ; If not, device/file is local:
                                 ; Return 0.
         inc     ax              ; Return 1 for remote.
 icrx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN440BH.ASM

         ;************************************************************;
         ;                                                            ;
         ;     Function 44H, Subfunction 0BH:                         ;
         ;                   IOCTL Change Sharing Retry Count         ;
         ;                                                            ;
         ;     int ioctl_set_retry(num_retries,wait_time)             ;
         ;         int num_retries;                                   ;
         ;         int wait_time;                                     ;
         ;                                                            ;
         ;     Returns 0 for success, otherwise returns error code.   ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_set_retry,PUBLIC,<ds,si>
 parmW   num_retries
 parmW   wait_time
 cBegin
         mov     dx,num_retries  ; Get parameters.
         mov     cx,wait_time
         mov     ax,440bh        ; Set function code, Subfunction 0BH.
         int     21h             ; Call MS-DOS.
         jc      isrx            ; Branch on error.
         xor     ax,ax
 isrx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN440CH.ASM

         ;************************************************************;
         ;                                                            ;
         ;   Function 44H, Subfunction 0CH:                           ;
         ;                 Generic IOCTL for Handles                  ;
         ;                                                            ;
         ;   int ioctl_char_generic(handle,category,function,pbuffer) ;
         ;       int   handle;                                        ;
         ;       int   category;                                      ;
         ;       int   function;                                      ;
         ;       int  *pbuffer;                                       ;
         ;                                                            ;
         ;   Returns 0 for success, otherwise returns error code.     ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_char_generic,PUBLIC,<ds>
 parmW   handle
 parmB   category
 parmB   function
 parmDP  pbuffer
 cBegin
         mov     bx,handle       ; Get device handle.
         mov     ch,category     ; Get category
         mov     cl,function     ; and function.
         loadDP  ds,dx,pbuffer   ; Get pointer to data buffer.
         mov     ax,440ch        ; Set function code, Subfunction 0CH.
         int     21h             ; Call MS-DOS.
         jc      icgx            ; Branch on error.
         xor     ax,ax
 icgx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN440DH.ASM

         ;************************************************************;
         ;                                                            ;
         ;    Function 44H, Subfunction 0DH:                          ;
         ;                  Generic IOCTL for Block Devices           ;
         ;                                                            ;
         ;    int ioctl_block_generic(drv_ltr,category,func,pbuffer)  ;
         ;        int   drv_ltr;                                      ;
         ;        int   category;                                     ;
         ;        int   func;                                         ;
         ;        char *pbuffer;                                      ;
         ;                                                            ;
         ;    Returns 0 for success, otherwise returns error code.    ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_block_generic,PUBLIC,<ds>
 parmB   drv_ltr
 parmB   category
 parmB   func
 parmDP  pbuffer
 cBegin
         mov     bl,drv_ltr      ; Get drive letter.
         or      bl,bl           ; Leave 0 alone.
         jz      ibg
         and     bl,not 20h      ; Convert letter to uppercase.
         sub     bl,'A'-1        ; Convert to drive number: 'A' = 1,
                                 ; 'B' = 2, etc.
 ibg:
         mov     ch,category     ; Get category
         mov     cl,func         ; and function.
         loadDP  ds,dx,pbuffer   ; Get pointer to data buffer.
         mov     ax,440dh        ; Set function code, Subfunction 0DH.
         int     21h             ; Call MS-DOS.
         jc      ibgx            ; Branch on error.
         xor     ax,ax
 ibgx:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN440EH.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 44H, Subfunctions 0EH, 0FH:                 ;
         ;                     IOCTL Get/Set Logical Drive Map        ;
         ;                                                            ;
         ;       int ioctl_drive_owner(setflag, drv_ltr)              ;
         ;           int setflag;                                     ;
         ;           int drv_ltr;                                     ;
         ;                                                            ;
         ;       Set setflag = 1 to change drive's map, 0 to get      ;
         ;       current map.                                         ;
         ;                                                            ;
         ;       Returns -1 for all errors, otherwise returns         ;
         ;       the block device's current logical drive letter.     ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_drive_owner,PUBLIC
 parmB   setflag
 parmB   drv_ltr
 cBegin
         mov     al,setflag      ; Load setflag.
         and     al,1            ; Keep only lsb.
         add     al,0eh          ; AL = 0EH for get, 0FH for set.
         mov     bl,drv_ltr      ; Get drive letter.
         or      bl,bl           ; Leave 0 alone.
         jz      ido
         and     bl,not 20h      ; Convert letter to uppercase.
         sub     bl,'A'-1        ; Convert to drive number: 'A' = 1,
                                 ; 'B' = 2, etc.
 ido:
         mov     bh,0
         mov     ah,44h          ; Set function code.
         int     21h             ; Call MS-DOS.
         mov     ah,0            ; Clear high byte.
         jnc     idox            ; Branch if no error.
         mov     ax,-1-'A'       ; Return -1 for errors.
 idox:
         add     ax,'A'          ; Return drive letter.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN440FH.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 44H, Subfunctions 0EH, 0FH:                 ;
         ;                     IOCTL Get/Set Logical Drive Map        ;
         ;                                                            ;
         ;       int ioctl_drive_owner(setflag, drv_ ltr)             ;
         ;           int setflag;                                     ;
         ;           int drv_ltr;                                     ;
         ;                                                            ;
         ;       Set setflag = 1 to change drive's map, 0 to get      ;
         ;       current map.                                         ;
         ;                                                            ;
         ;       Returns -1 for all errors, otherwise returns         ;
         ;       the block device's current logical drive letter.     ;
         ;                                                            ;
         ;************************************************************;

 cProc   ioctl_drive_owner,PUBLIC
 parmB   setflag
 parmB   drv_ltr
 cBegin
         mov     al,setflag      ; Load setflag.
         and     al,1            ; Keep only lsb.
         add     al,0fh          ; AL = 0EH for get, 0FH for set.
         mov     bl,drv_ltr      ; Get drive letter.
         or      bl,bl           ; Leave 0 alone.
         jz      ido
         and     bl,not 20h      ; Convert letter to uppercase.
         sub     bl,'A'-1        ; Convert to drive number: 'A' = 1,
                                 ; 'B' = 2, etc.
 ido:
         mov     bh,0
         mov     ah,44h          ; Set function code.
         int     21h             ; Call MS-DOS.
         mov     ah,0            ; Clear high byte.
         jnc     idox            ; Branch if no error.
         mov     ax,-1-'A'       ; Return -1 for errors.
 idox:
         add     ax,'A'          ; Return drive letter.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN45H.ASM

         ;************************************************************;
         ;                                                            ;
         ;             Function 45H: Duplicate File Handle            ;
         ;                                                            ;
         ;             int dup_handle(handle)                         ;
         ;                 int handle;                                ;
         ;                                                            ;
         ;             Returns -1 for errors,                         ;
         ;             otherwise returns new handle.                  ;
         ;                                                            ;
         ;************************************************************;

 cProc   dup_handle,PUBLIC
 parmW   handle
 cBegin
         mov     bx,handle       ; Get handle to copy.
         mov     ah,45h          ; Set function code.
         int     21h             ; Ask MS-DOS to duplicate handle.
         jnb     dup_ok          ; Branch if copy was successful.
         mov     ax,-1           ; Else return -1.
 dup_ok:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN46H.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 46H: Force Duplicate File Handle         ;
         ;                                                            ;
         ;          int dup_handle2(existhandle,newhandle)            ;
         ;              int existhandle,newhandle;                    ;
         ;                                                            ;
         ;          Returns -1 for errors,                            ;
         ;          otherwise returns newhandle unchanged.            ;
         ;                                                            ;
         ;************************************************************;

 cProc   dup_handle2,PUBLIC
 parmW   existhandle
 parmW   newhandle
 cBegin
         mov     bx,existhandle  ; Get handle of existing file.
         mov     cx,newhandle    ; Get handle to copy into.
         mov     ah,46h          ; Close handle CX and then
         int     21h             ; duplicate BX's handle into CX.
         mov     ax,newhandle    ; Prepare return value.
         jnb     dup2_ok         ; Branch if close/copy was successful.
         mov     ax,-1           ; Else return -1.
 dup2_ok:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN47H.ASM

         ;************************************************************;
         ;                                                            ;
         ;            Function 47H: Get Current Directory             ;
         ;                                                            ;
         ;            int get_dir(drive_ltr,pbuffer)                  ;
         ;                int drive_ltr;                              ;
         ;                char *pbuffer;                              ;
         ;                                                            ;
         ;            Returns -1 for bad drive,                       ;
         ;            otherwise returns pointer to pbuffer.           ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_dir,PUBLIC,<ds,si>
 parmB   drive_ltr
 parmDP  pbuffer
 cBegin
         loadDP  ds,si,pbuffer   ; Get pointer to buffer.
         mov     dl,drive_ltr    ; Get drive number.
         or      dl,dl           ; Leave 0 alone.
         jz      gdir
         and     dl,not 20h      ; Convert letter to uppercase
         sub     dl,'A'-1        ; Convert to drive number: 'A' = 1,
                                 ; 'B' = 2, etc.
 gdir:
         mov     ah,47h          ; Set function code.
         int     21h             ; Call MS-DOS.
         mov     ax,si           ; Return pointer to buffer ...
         jnb     gd_ok
         mov     ax,-1           ; ... unless an error occurred.
 gd_ok:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN48H.ASM

         ;************************************************************;
         ;                                                            ;
         ;         Function 48H: Allocate Memory Block                ;
         ;                                                            ;
         ;         int get_block(nparas,pblocksegp,pmaxparas)         ;
         ;             int nparas,*pblockseg,*pmaxparas;              ;
         ;                                                            ;
         ;         Returns 0 if nparas are allocated OK and           ;
         ;         pblockseg has segment address of block,            ;
         ;         otherwise returns error code with pmaxparas        ;
         ;         set to maximum block size available.               ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_block,PUBLIC,ds
 parmW   nparas
 parmDP  pblockseg
 parmDP  pmaxparas
 cBegin
         mov     bx,nparas       ; Get size request.
         mov     ah,48h          ; Set function code.
         int     21h             ; Ask MS-DOS for memory.
         mov     cx,bx           ; Save BX.
         loadDP  ds,bx,pmaxparas
         mov     [bx],cx         ; Return result, assuming failure.
         jb      gb_err          ; Exit if error, leaving error code
                                 ; in AX.
         loadDP  ds,bx,pblockseg
         mov     [bx],ax         ; No error, so store address of block.
         xor     ax,ax           ; Return 0.
 gb_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN49H.ASM

         ;************************************************************;
         ;                                                            ;
         ;               Function 49H: Free Memory Block              ;
         ;                                                            ;
         ;               int free_block(blockseg)                     ;
         ;                   int blockseg;                            ;
         ;                                                            ;
         ;               Returns 0 if block freed OK,                 ;
         ;               otherwise returns error code.                ;
         ;                                                            ;
         ;************************************************************;

 cProc   free_block,PUBLIC
 parmW   blockseg
 cBegin
         mov     es,blockseg     ; Get block address.
         mov     ah,49h          ; Set function code.
         int     21h             ; Ask MS-DOS to free memory.
         jb      fb_err          ; Branch on error.
         xor     ax,ax           ; Return 0 if successful.
 fb_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4AH.ASM

         ;************************************************************;
         ;                                                            ;
         ;         Function 4AH: Resize Memory Block                  ;
         ;                                                            ;
         ;         int modify_block(nparas,blockseg,pmaxparas)        ;
         ;             int nparas,blockseg,*pmaxparas;                ;
         ;                                                            ;
         ;         Returns 0 if modification was a success,           ;
         ;         otherwise returns error code with pmaxparas        ;
         ;         set to max number of paragraphs available.         ;
         ;                                                            ;
         ;************************************************************;

 cProc   modify_block,PUBLIC,ds
 parmW   nparas
 parmW   blockseg
 parmDP  pmaxparas
 cBegin
         mov     es,blockseg     ; Get block address.
         mov     bx,nparas       ; Get nparas.
         mov     ah,4ah          ; Set function code.
         int     21h             ; Ask MS-DOS to change block size.
         mov     cx,bx           ; Save BX.
         loadDP  ds,bx,pmaxparas
         mov     [bx],cx         ; Set pmaxparas, assuming failure.
         jb      mb_exit         ; Branch if size change error.
         xor     ax,ax           ; Return 0 if successful.
 mb_exit:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4BH.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 4BH: Load and Execute Program            ;
         ;                                                            ;
         ;          int execute(pprogname,pcmdtail)                   ;
         ;              char *pprogname,*pcmdtail;                    ;
         ;                                                            ;
         ;          Returns 0 if program loaded, ran, and             ;
         ;          terminated successfully, otherwise returns        ;
         ;          error code.                                       ;
         ;                                                            ;
         ;************************************************************;

 sBegin  data
 $cmdlen =       126
 $cmd    db      $cmdlen+2 dup (?) ; Make space for command line, plus
                                 ; 2 extra bytes for length and
                                 ; carriage return.

 $fcb    db      0               ; Make dummy FCB.
         db      'dummy   fcb'
         db      0,0,0,0

                                 ; Here's the EXEC parameter block:
 $epb    dw      0               ; 0 means inherit environment.
         dw      dataOFFSET $cmd ; Pointer to cmd line.
         dw      seg dgroup
         dw      dataOFFSET $fcb ; Pointer to FCB #1.
         dw      seg dgroup
         dw      dataOFFSET $fcb ; Pointer to FCB #2.
         dw      seg dgroup
 sEnd    data
 sBegin  code

 $sp     dw      ?               ; Allocate space in code seg
 $ss     dw      ?               ; for saving SS and SP.

 Assumes ES,dgroup

 cProc   execute,PUBLIC,<ds,si,di>
 parmDP  pprogname
 parmDP  pcmdtail
 cBegin
         mov     cx,$cmdlen      ; Allow command line this long.
         loadDP  ds,si,pcmdtail  ; DS:SI = pointer to cmdtail string.
         mov     ax,seg dgroup:$cmd    ; Set ES = data segment.
         mov     es,ax
         mov     di,dataOFFSET $cmd+1  ; ES:DI = pointer to 2nd byte of
                                       ; our command-line buffer.
 copycmd:
         lodsb                   ; Get next character.
         or      al,al           ; Found end of command tail?
         jz      endcopy         ; Exit loop if so.
         stosb                   ; Copy to command buffer.
         loop    copycmd
 endcopy:
         mov     al,13
         stosb                   ; Store carriage return at
                                 ; end of command.
         neg     cl
         add     cl,$cmdlen      ; CL = length of command tail.
         mov     es:$cmd,cl      ; Store length in command-tail buffer.

         loadDP  ds,dx,pprogname ; DS:DX = pointer to program name.
         mov     bx,dataOFFSET $epb ; ES:BX = pointer to parameter
                                    ; block.

         mov     cs:$ss,ss       ; Save current stack SS:SP (since
         mov     cs:$sp,sp       ; EXEC function destroys stack).
         mov     ax,4b00h        ; Set function code.
         int     21h             ; Ask MS-DOS to load and execute
                                 ; program.
         cli                     ; Disable interrupts.
         mov     ss,cs:$ss       ; Restore stack.
         mov     sp,cs:$sp
         sti                     ; Enable interrupts.
         jb      ex_err          ; Branch on error.
         xor     ax,ax           ; Return 0 if no error.
 ex_err:
 cEnd
 sEnd    code



         ;************************************************************;
         ;                                                            ;
         ;   Function 4BH: Load an Overlay Program                    ;
         ;                                                            ;
         ;   int load_overlay(pfilename,loadseg)                      ;
         ;       char *pfilename;                                     ;
         ;       int  loadseg;                                        ;
         ;                                                            ;
         ;   Returns 0 if program has been loaded OK,                 ;
         ;   otherwise returns error code.                            ;
         ;                                                            ;
         ;   To call an overlay function after it has been            ;
         ;   loaded by load_overlay(), you can use                    ;
         ;   a far indirect call:                                     ;
         ;                                                            ;
         ;   1. FTYPE (far *ovlptr)();                                ;
         ;   2. *((unsigned *)&ovlptr + 1) = loadseg;                 ;
         ;   3. *((unsigned *)&ovlptr) = offset;                      ;
         ;   4. (*ovlptr)(arg1,arg2,arg3,...);                        ;
         ;                                                            ;
         ;   Line 1 declares a far pointer to a                       ;
         ;   function with return type FTYPE.                         ;
         ;                                                            ;
         ;   Line 2 stores loadseg into the segment                   ;
         ;   portion (high word) of the far pointer.                  ;
         ;                                                            ;
         ;   Line 3 stores offset into the offset                     ;
         ;   portion (low word) of the far pointer.                   ;
         ;                                                            ;
         ;   Line 4 does a far call to offset                         ;
         ;   bytes into the segment loadseg                           ;
         ;   passing the arguments listed.                            ;
         ;                                                            ;
         ;   To return correctly, the overlay  must end with a far    ;
         ;   return instruction.  If the overlay is                   ;
         ;   written in Microsoft C, this can be done by              ;
         ;   declaring the overlay function with the                  ;
         ;   keyword "far".                                           ;
         ;                                                            ;
         ;************************************************************;

 sBegin  data
                                 ; The overlay parameter block:
 $lob    dw      ?               ; space for load segment;
         dw      ?               ; space for fixup segment.
 sEnd    data

 sBegin  code

 cProc   load_overlay,PUBLIC,<ds,si,di>
 parmDP  pfilename
 parmW   loadseg
 cBegin
         loadDP  ds,dx,pfilename ; DS:DX = pointer to program name.
         mov     ax,seg dgroup:$lob ; Set ES = data segment.
         mov     es,ax
         mov     bx,dataOFFSET $lob ; ES:BX = pointer to parameter
                                    ; block.
         mov     ax,loadseg      ; Get load segment parameter.
         mov     es:[bx],ax      ; Set both the load and fixup
         mov     es:[bx+2],ax    ; segments to that segment.

         mov     cs:$ss,ss       ; Save current stack SS:SP (because
         mov     cs:$sp,sp       ; EXEC function destroys stack).
         mov     ax,4b03h        ; Set function code.
         int     21h             ; Ask MS-DOS to load the overlay.
         cli                     ; Disable interrupts.
         mov     ss,cs:$ss       ; Restore stack.
         mov     sp,cs:$sp
         sti                     ; Enable interrupts.
         jb      lo_err          ; Branch on error.
         xor     ax,ax           ; Return 0 if no error.
 lo_err:
 cEnd
 sEnd    code

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4CH.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 4CH: Terminate Process with Return Code     ;
         ;                                                            ;
         ;       int terminate(returncode)                            ;
         ;           int returncode;                                  ;
         ;                                                            ;
         ;       Does NOT return at all!                              ;
         ;                                                            ;
         ;************************************************************;

 cProc   terminate,PUBLIC
 parmB   returncode
 cBegin
         mov     al,returncode   ; Set return code.
         mov     ah,4ch          ; Set function code.
         int     21h             ; Call MS-DOS to terminate process.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4DH.ASM

         ;************************************************************;
         ;                                                            ;
         ;        Function 4DH: Get Return Code of Child Process      ;
         ;                                                            ;
         ;        int child_ret_code()                                ;
         ;                                                            ;
         ;        Returns the return code of the last                 ;
         ;        child process.                                      ;
         ;                                                            ;
         ;************************************************************;

 cProc   child_ret_code,PUBLIC
 cBegin
         mov     ah,4dh          ; Set function code.
         int     21h             ; Ask MS-DOS to return code.
         cbw                     ; Convert AL to a word.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4EH.ASM

         ;************************************************************;
         ;                                                            ;
         ;               Function 4EH: Find First File                ;
         ;                                                            ;
         ;               int find_first(ppathname,attr)               ;
         ;                   char *ppathname;                         ;
         ;                   int  attr;                               ;
         ;                                                            ;
         ;               Returns 0 if a match was found,              ;
         ;               otherwise returns error code.                ;
         ;                                                            ;
         ;************************************************************;

 cProc   find_first,PUBLIC,ds
 parmDP  ppathname
 parmW   attr
 cBegin
         loadDP  ds,dx,ppathname ; Get pointer to pathname.
         mov     cx,attr         ; Get search attributes.
         mov     ah,4eh          ; Set function code.
         int     21h             ; Ask MS-DOS to look for a match.
         jb      ff_err          ; Branch on error.
         xor     ax,ax           ; Return 0 if no error.
 ff_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN4FH.ASM

         ;************************************************************;
         ;                                                            ;
         ;               Function 4FH: Find Next File                 ;
         ;                                                            ;
         ;               int find_next()                              ;
         ;                                                            ;
         ;               Returns 0 if a match was found,              ;
         ;               otherwise returns error code.                ;
         ;                                                            ;
         ;************************************************************;

 cProc   find_next,PUBLIC
 cBegin
         mov     ah,4fh          ; Set function code.
         int     21h             ; Ask MS-DOS to look for the next
                                 ; matching file.
         jb      fn_err          ; Branch on error.
         xor     ax,ax           ; Return 0 if no error.
 fn_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN54H.ASM

         ;************************************************************;
         ;                                                            ;
         ;            Function 54H: Get Verify Flag                   ;
         ;                                                            ;
         ;            int get_verify()                                ;
         ;                                                            ;
         ;            Returns current value of verify flag.           ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_verify,PUBLIC
 cBegin
         mov     ah,54h          ; Set function code.
         int     21h             ; Read flag from MS-DOS.
         cbw                     ; Clear high byte of return value.

 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN56H.ASM

         ;************************************************************;
         ;                                                            ;
         ;                Function 56H: Rename File                   ;
         ;                                                            ;
         ;                int rename(poldpath,pnewpath)               ;
         ;                    char *poldpath,*pnewpath;               ;
         ;                                                            ;
         ;                Returns 0 if file moved OK,                 ;
         ;                otherwise returns error code.               ;
         ;                                                            ;
         ;************************************************************;

 cProc   rename,PUBLIC,<ds,di>
 parmDP  poldpath
 parmDP  pnewpath
 cBegin
         loadDP  es,di,pnewpath  ; ES:DI = pointer to newpath.
         loadDP  ds,dx,poldpath  ; DS:DX = pointer to oldpath.
         mov     ah,56h          ; Set function code.
         int     21h             ; Ask MS-DOS to rename file.
         jb      rn_err          ; Branch on error.
         xor     ax,ax           ; Return 0 if no error.
 rn_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN57H.ASM

         ;************************************************************;
         ;                                                            ;
         ;      Function 57H: Get/Set Date/Time of File               ;
         ;                                                            ;
         ;      long file_date_time(handle,func,packdate,packtime)    ;
         ;           int handle,func,packdate,packtime;               ;
         ;                                                            ;
         ;      Returns a long -1 for all errors, otherwise packs     ;
         ;      date and time into a long integer,                    ;
         ;      date in high word, time in low word.                  ;
         ;                                                            ;
         ;************************************************************;

 cProc   file_date_time,PUBLIC
 parmW   handle
 parmB   func
 parmW   packdate
 parmW   packtime
 cBegin
         mov     bx,handle       ; Get handle.
         mov     al,func         ; Get function: 0 = read, 1 = write.
         mov     dx,packdate     ; Get date (if present).
         mov     cx,packtime     ; Get time (if present).
         mov     ah,57h          ; Set function code.
         int     21h             ; Call MS-DOS.
         mov     ax,cx           ; Set DX:AX = date/time, assuming no
                                 ; error.
         jnb     dt_ok           ; Branch if no error.
         mov     ax,-1           ; Return -1 for errors.
         cwd                     ; Extend the -1 into DX.
 dt_ok:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN58H.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 58H: Get/Set Allocation Strategy         ;
         ;                                                            ;
         ;          int alloc_strategy(func,strategy)                 ;
         ;              int func,strategy;                            ;
         ;                                                            ;
         ;          Strategies:                                       ;
         ;                  0: First fit                              ;
         ;                  1: Best fit                               ;
         ;                  2: Last fit                               ;
         ;                                                            ;
         ;          Returns -1 for all errors, otherwise              ;
         ;          returns the current strategy.                     ;
         ;                                                            ;
         ;************************************************************;

 cProc   alloc_strategy,PUBLIC
 parmB   func
 parmW   strategy
 cBegin
         mov     al,func         ; AL = get/set selector.
         mov     bx,strategy     ; BX = new strategy (for AL = 01H).
         mov     ah,58h          ; Set function code.
         int     21h             ; Call MS-DOS.
         jnb     no_err          ; Branch if no error.
         mov     ax,-1           ; Return -1 for all errors.
 no_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN59H.ASM

         ;************************************************************;
         ;                                                            ;
         ;         Function 59H: Get Extended Error Information       ;
         ;                                                            ;
         ;         int extended_error(err,class,action,locus)         ;
         ;             int *err;                                      ;
         ;             char *class,*action,*locus;                    ;
         ;                                                            ;
         ;         Return value is same as err.                       ;
         ;                                                            ;
         ;************************************************************;

 cProc   extended_error,PUBLIC,<ds,si,di>
 parmDP  perr
 parmDP  pclass
 parmDP  paction
 parmDP  plocus
 cBegin
         push    ds              ; Save DS.
         xor     bx,bx
         mov     ah,59h          ; Set function code.
         int     21h             ; Request error info from MS-DOS.
         pop     ds              ; Restore DS.
         loadDP  ds,si,perr      ; Get pointer to err.
         mov     [si],ax         ; Store err.
         loadDP  ds,si,pclass    ; Get pointer to class.
         mov     [si],bh         ; Store class.
         loadDP  ds,si,paction   ; Get pointer to action.
         mov     [si],bl         ; Store action.
         loadDP  ds,si,plocus    ; Get pointer to locus.
         mov     [si],ch         ; Store locus.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN5AH.ASM

         ;************************************************************;
         ;                                                            ;
         ;             Function 5AH: Create Temporary File            ;
         ;                                                            ;
         ;             int create_temp(ppathname,attr)                ;
         ;                 char *ppathname;                           ;
         ;                 int attr;                                  ;
         ;                                                            ;
         ;             Returns -1 if file was not created,            ;
         ;             otherwise returns file handle.                 ;
         ;                                                            ;
         ;************************************************************;

 cProc   create_temp,PUBLIC,ds
 parmDP  ppathname
 parmW   attr
 cBegin
         loadDP  ds,dx,ppathname ; Get pointer to pathname.
         mov     cx,attr         ; Set function code.
         mov     ah,5ah          ; Ask MS-DOS to make a new file with
                                 ; a unique name.
         int     21h             ; Ask MS-DOS to make a tmp file.
         jnb     ct_ok           ; Branch if MS-DOS returned handle.
         mov     ax,-1           ; Else return -1.
 ct_ok:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN5BH.ASM

         ;************************************************************;
         ;                                                            ;
         ;          Function 5BH: Create New File                     ;
         ;                                                            ;
         ;          int create_new(ppathname,attr)                    ;
         ;              char *ppathname;                              ;
         ;              int attr;                                     ;
         ;                                                            ;
         ;          Returns -2 if file already exists,                ;
         ;                  -1 for all other errors,                  ;
         ;                  otherwise returns file handle.            ;
         ;                                                            ;
         ;************************************************************;

 cProc   create_new,PUBLIC,ds
 parmDP  ppathname
 parmW   attr
 cBegin
         loadDP  ds,dx,ppathname ; Get pointer to pathname.
         mov     cx,attr         ; Get new file's attribute.
         mov     ah,5bh          ; Set function code.
         int     21h             ; Ask MS-DOS to make a new file.
         jnb     cn_ok           ; Branch if MS-DOS returned handle.
         mov     bx,-2
         cmp     al,80           ; Did file already exist?
         jz      ae_err          ; Branch if so.
         inc     bx              ; Change -2 to -1.
 ae_err:
         mov     ax,bx           ; Return error code.
 cn_ok:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN5CH.ASM

         ;************************************************************;
         ;                                                            ;
         ;            Function 5CH: Lock/Unlock File Region           ;
         ;                                                            ;
         ;            int locks(handle,onoff,start,length)            ;
         ;                int handle,onoff;                           ;
         ;                long start,length;                          ;
         ;                                                            ;
         ;            Returns 0 if operation was successful,          ;
         ;            otherwise returns error code.                   ;
         ;                                                            ;
         ;************************************************************;

 cProc   locks,PUBLIC,<si,di>
 parmW   handle
 parmB   onoff
 parmD   start
 parmD   length
 cBegin
         mov     al,onoff        ; Get lock/unlock flag.
         mov     bx,handle       ; Get file handle.
         les     dx,start        ; Get low word of start.
         mov     cx,es           ; Get high word of start.
         les     di,length       ; Get low word of length.
         mov     si,es           ; Get high word of length.
         mov     ah,5ch          ; Set function code.
         int     21h             ; Make lock/unlock request.
         jb      lk_err          ; Branch on error.
         xor     ax,ax           ; Return 0 if no error.
 lk_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN5E02H.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 5EH Subfunction 02H:                        ;
         ;                     Set Printer Setup                      ;
         ;                                                            ;
         ;       int printer_setup(index,pstring,len)                 ;
         ;           int   index;                                     ;
         ;           char *pstring;                                   ;
         ;           int   len;                                       ;
         ;                                                            ;
         ;       Returns 0, otherwise returns -1 for all errors.      ;
         ;                                                            ;
         ;************************************************************;

 cProc   printer_setup,PUBLIC,<ds,si>
 parmW   index
 parmDP  pstring
 parmW   len
 cBegin
         mov     bx,index        ; BX = index of a net printer.
         loadDP  ds,si,pstring   ; DS:SI = pointer to string.
         mov     cx,len          ; CX = length of string.
         mov     ax,5e02h        ; Set function code.
         int     21h             ; Set printer prefix string.
         mov     al,0            ; Assume no error.
         jnb     ps_ok           ; Branch if no error,
         mov     al,-1           ; Else return -1.
 ps_ok:
         cbw
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN5F02H.ASM

         ;************************************************************;
         ;                                                            ;
         ;    Function 5FH Subfunction 02H:                           ;
         ;                  Get Assign-List Entry                     ;
         ;                                                            ;
         ;    int get_alist_entry(index,                              ;
         ;           plocalname,premotename,                          ;
         ;           puservalue,ptype)                                ;
         ;        int  index;                                         ;
         ;        char *plocalname;                                   ;
         ;        char *premotename;                                  ;
         ;        int  *puservalue;                                   ;
         ;        int  *ptype;                                        ;
         ;                                                            ;
         ;    Returns 0 if the requested assign-list entry is found,  ;
         ;    otherwise returns error code.                           ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_alist_entry,PUBLIC,<ds,si,di>
 parmW   index
 parmDP  plocalname
 parmDP  premotename
 parmDP  puservalue
 parmDP  ptype
 cBegin
         mov     bx,index        ; Get list index.
         loadDP  ds,si,plocalname  ; DS:SI = pointer to local name
                                   ; buffer.
         loadDP  es,di,premotename ; ES:DI = pointer to remote name
                                   ; buffer.
         mov     ax,5f02h        ; Set function code.
         int     21h             ; Get assign-list entry.
         jb      ga_err          ; Exit on error.
         xor     ax,ax           ; Else return 0.
         loadDP  ds,si,puservalue ; Get address of uservalue.
         mov     [si],cx         ; Store user value.
         loadDP  ds,si,ptype     ; Get address of type.
         mov     bh,0
         mov     [si],bx         ; Store device type to type.
 ga_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN5F03H.ASM

         ;************************************************************;
         ;                                                            ;
         ;    Function 5FH Subfunction 03H:                           ;
         ;                  Make Assign-List Entry                    ;
         ;    int add_alist_entry(psrcname,pdestname,uservalue,type)  ;
         ;        char *psrcname,*pdestname;                          ;
         ;        int  uservalue,type;                                ;
         ;                                                            ;
         ;    Returns 0 if new assign-list entry is made, otherwise   ;
         ;    returns error code.                                     ;
         ;                                                            ;
         ;************************************************************;

 cProc   add_alist_entry,PUBLIC,<ds,si,di>
 parmDP  psrcname
 parmDP  pdestname
 parmW   uservalue
 parmW   type
 cBegin
         mov     bx,type         ; Get device type.
         mov     cx,uservalue    ; Get uservalue.
         loadDP  ds,si,psrcname  ; DS:SI = pointer to source name.
         loadDP  es,di,pdestname ; ES:DI = pointer to destination name.
         mov     ax,5f03h        ; Set function code.
         int     21h             ; Make assign-list entry.
         jb      aa_err          ; Exit if there was some error.
         xor     ax,ax           ; Else return 0.
 aa_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN5F04H.ASM

         ;************************************************************;
         ;                                                            ;
         ;    Function 5FH Subfunction 04H:                           ;
         ;                  Cancel Assign-List Entry                  ;
         ;                                                            ;
         ;    int cancel_alist_entry(psrcname)                        ;
         ;        char *psrcname;                                     ;
         ;                                                            ;
         ;    Returns 0 if assignment is canceled, otherwise returns  ;
         ;    error code.                                             ;
         ;                                                            ;
         ;************************************************************;

 cProc   cancel_alist_entry,PUBLIC,<ds,si>
 parmDP  psrcname
 cBegin
         loadDP  ds,si,psrcname  ; DS:SI = pointer to source name.
         mov     ax,5f04h        ; Set function code.
         int     21h             ; Cancel assign-list entry.
         jb      ca_err          ; Exit on error.
         xor     ax,ax           ; Else return 0.
 ca_err:
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN62H.ASM

         ;************************************************************;
         ;                                                            ;
         ;       Function 62H: Get Program Segment Prefix Address     ;
         ;                                                            ;
         ;       int get_psp()                                        ;
         ;                                                            ;
         ;       Returns PSP segment.                                 ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_psp,PUBLIC
 cBegin
         mov     ah,62h          ; Set function code.
         int     21h             ; Get PSP address.
         mov     ax,bx           ; Return it in AX.
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\FXN63H.ASM

         ;************************************************************;
         ;                                                            ;
         ;   Function 63H: Get Lead Byte Table                        ;
         ;                                                            ;
         ;   char far *get_lead_byte_table()                          ;
         ;                                                            ;
         ;   Returns far pointer to table of lead bytes for multibyte ;
         ;   characters.  Will work only in MS-DOS 2.25!              ;
         ;                                                            ;
         ;************************************************************;

 cProc   get_lead_byte_table,PUBLIC,<ds,si>
 cBegin
         mov     ax,6300h        ; Set function code.
         int     21h             ; Get lead byte table.
         mov     dx,ds           ; Return far pointer in DX:AX.
         mov     ax,si
 cEnd

 \SAMPCODE\DOS_ENCY\SECTION5\INT20H.ASM

 ;************************************************************;
 ;                                                            ;
 ;                     Perform a final exit.                  ;
 ;                                                            ;
 ;************************************************************;
         int     20H     ; Transfer to MS-DOS.

 \SAMPCODE\DOS_ENCY\SECTION5\INT25H.ASM

 ;***************************************************************;
 ;                                                               ;
 ;      Interrupt 25H: Absolute Disk Read                        ;
 ;                                                               ;
 ;      Read logical sector 1 of drive A into the memory area    ;
 ;      named buff. (On most MS-DOS floppy disks, this sector    ;
 ;      contains the beginning of the file allocation table.)    ;
 ;                                                               ;
 ;***************************************************************;

         mov     al,0            ; Drive A.
         mov     cx,1            ; Number of sectors.
         mov     dx,1            ; Beginning sector number.
         mov     bx,seg buff     ; Address of buffer.
         mov     ds,bx
         mov     bx,offset buff
         int     25h             ; Request disk read.
         jc      error           ; Jump if read failed.
         add     sp, 2           ; Clear stack.
         .
         .
         .
 error:                          ; Error routine goes here.
         .
         .
         .
 buff    db      512 dup (?)

 \SAMPCODE\DOS_ENCY\SECTION5\INT26H.ASM

 ;****************************************************************;
 ;                                                                ;
 ;       Interrupt 26H: Absolute Disk Write                       ;
 ;                                                                ;
 ;       Write the contents of the memory area named buff         ;
 ;       into logical sector 3 of drive C.                        ;
 ;                                                                ;
 ;       WARNING: Verbatim use of this code could damage          ;
 ;       the file structure of the fixed disk. It is meant        ;
 ;       only as a general guide. There is, unfortunately,        ;
 ;       no way to give a really safe example of this interrupt.  ;
 ;                                                                ;
 ;****************************************************************;

         mov     al,2            ; Drive C.
         mov     cx,1            ; Number of sectors.
         mov     dx,3            ; Beginning sector number.
         mov     bx,seg buff     ; Address of buffer.
         mov     ds,bx
         mov     bx,offset buff
         int     26h             ; Request disk write.
         jc      error           ; Jump if write failed.
         add     sp,2            ; Clear stack.
         .
         .
         .
 error:                          ; Error routine goes here.
         .
         .
         .
 buff    db      512 dup (?)     ; Data to be written to disk.

 \SAMPCODE\DOS_ENCY\SECTION5\INT27H.ASM

 ;***************************************************************;
 ;                                                               ;
 ;       Interrupt 27H: Terminate and Stay Resident              ;
 ;                                                               ;
 ;       Exit and stay resident, reserving enough memory         ;
 ;       to protect the program's code and data.                 ;
 ;                                                               ;
 ;***************************************************************;

 Start:  .
         .
         .
         mov     dx,offset pgm_end  ; DX = bytes to reserve.
         int     27h                ; Terminate, stay resident.
         .
         .
         .
 pgm_end equ     $
         end     start

 \SAMPCODE\DOS_ENCY\SECTION5\TEST.ASM

 memS    =       0                ;Small memory model
 ?PLM    =       0                ;C calling conventions
 ?WIN    =       0                ;Disable Windows support

 include cmacros.inc
 include cmacrosx.inc

 sBegin  CODE                     ;Start of code segment
 assumes CS,CODE                  ;Required by MASM

         ;Microsoft C function syntax:
         ;
         ;     int addnums(firstnum, secondnum)
         ;         int firstnum, secondnum;
         ;
         ;Returns firstnum + secondnum

 cProc   addnums,PUBLIC           ;Start of addnums functions
 parmW   firstnum                 ;Declare parameters
 parmW   secondnum
 cBegin
         mov     ax,firstnum
         add     ax,secondnum
 cEnd
 sEnd    CODE
         end

 \SAMPCODE\DOS_ENCY\SECTION5\CMACROSX.INC

 ;       CMACROSX.INC
 ;
 ;       This file includes supplemental macros for two macros included
 ;       in CMACROS.INC: parmCP and parmDP. When these macros are used,
 ;       CMACROS.INC allocates either 1 or 2 words to the variables
 ;       associated with these macros, depending on the memory model in
 ;       use. However, parmCP and parmDP provide no support for automatically
 ;       adjusting for different memory models--additional program code
 ;       needs to be written to compensate for this. The loadCP and loadDP
 ;       macros included in this file can be used to provide additional
 ;       flexibility for overcoming this limit.

 ;       For example, "parmDP pointer" will make space (1 word in small
 ;       and middle models and 2 words in compact, large, and huge models)
 ;       for the data pointer named "pointer". The statement
 ;       "loadDP ds,bx,pointer" can then be used to dynamically place the
 ;       value of "pointer" into DS:BX, depending on the memory model.
 ;       In small-model programs, this macro would generate the instruction
 ;       "mov dx,pointer" (it is assumed that DS already has the right
 ;       segment value); in large-model programs, this macro would generate
 ;       the statements "mov ds,SEG_pointer" and "mov dx,OFF_pointer".


 checkDS macro        segmt
            diffcount = 0
            irp d,<ds,DS,Ds,dS>                  ; Allow for all spellings
               ifdif <segmt>,<d>                 ; of "ds".
                  diffcount = diffcount+1
               endif
            endm
            if diffcount EQ 4
               it_is_DS = 0
            else
               it_is_DS = 1
            endif
         endm

 checkES macro        segmt
            diffcount = 0
            irp d,<es,ES,Es,eS>                  ; Allow for all spellings
               ifdif <segmt>,<d>                 ; of "es".
                  diffcount = diffcount+1
               endif
            endm
            if diffcount EQ 4
               it_is_ES = 0
            else
               it_is_ES = 1
            endif
         endm

 loadDP  macro        segmt,offst,dptr
            checkDS segmt
            if sizeD                             ; <-- Large data model
               if it_is_DS
                  lds  offst,dptr
               else
                  checkES segmt
                  if it_is_ES
                     les  offst,dptr
                  else
                     mov  offst,OFF_&dptr
                     mov  segmt,SEG_&dptr
                  endif
               endif
            else
               mov  offst,dptr                   ; <-- Small data model
               if it_is_DS EQ 0
                  push ds                        ; If "segmt" is not DS,
                  pop  segmt                     ; move ds to segmt.
               endif
            endif
         endm

 loadCP  macro        segmt,offst,cptr
            if sizeC                             ; <-- Large code model
               checkDS segmt
               if it_is_DS
                  lds offst,cptr
               else
                  checkES
                  if it_is_ES
                     les  offst,cptr
                  else
                     mov  segmt,SEG_&cptr
                     mov  offst,OFF_&cptr
                  endif
               endif
            else
               push cs                           ; <-- Small code model
               pop  segmt
               mov  offst,cptr
            endif
         endm
