          TITLE   GET

; Program SDL_GET.ASM (or just GET)
; Purpose Batch file enhancer for use with MASDIR
; Input	  Command line consisting of:
;	     Command letter (with optional E for extended)
;	     Optional prompt enclosed in double	quotes
;	     Optional argument
; Output  Number in DOS ERRORLEVEL and string in GET environment variable
BIOS_DATA       SEGMENT AT 40H
        ORG     84H
CRT_ROWS DB     ?
        ORG     87H
EGA_INFO DB     ?
BIOS_DATA       ENDS


	  DOSSEG
	  .MODEL  small
	  PAGE	  60,120
          INCLUDE DOS.INC

	  .STACK  100h

	  .DATA

S1      SIZESTR @Filename               ; If the name of this file is GET
S2      INSTR @Filename,<get>           ; then the $ option will be omitted.
S3      INSTR @Filename,<GET>           ; Any other file name will cause the
IF S1 eq 3 AND S2 eq 1 OR S3 eq 1       ; $ option to be assembled in.
GET equ <TRUE>
ENDIF
cr       equ      "}"                     ; Surrogate for CR
esc_s    equ      "~"                     ; Surrogate for Esc
ext      equ      "`"  ; grave accent indicates that extended keys can be used
MaxSize  equ      1000h                 ; MaxSize of moving text prompt.

help1	  LABEL	  BYTE
DB 13,10
DB "  GET -- Bob Stephan's Batch file enhancer -- a public domain utility",13,10
DB "         Syntax:  GET command argument",13,10
DB "Purpose             Command       Argument   Environment Errorlevel  Extended",13,10
DB "---------           -------       --------   --(GET=)--- --(Exit)--  --[E]---",13,10
DB 'Get character       C[E] ["prompt"] [chars]  Character   ASCII dec.  No Echo',13,10
DB 'Get yes/no aNswer   N[E] ["prompt"]  None    Character   ASCII dec.  No Echo',13,10
DB 'Get string          S[E] ["prompt"]  None    String      Length      No Echo',13,10
DB 'Moving Text(GetaKey)T[E] "Filespec" [chars]  Character   ASCII dec.  No Echo',13,10
DB "Get DOS version     D[E]             None    Major Vers. MajorMinor  MinorEnv",13,10
DB "Get environment left    E[E]         None    Bytes left  Bytes left  Bytes/10",13,10
DB "Get file size (DIR=0)   F[E]  Filespec       Hex bytes   Kilobytes   Kb/10",13,10
DB "Get disk space          K[E] [driveletter]   Kilobytes   Kilobytes   Kb/10",13,10
DB "Get memory free         M[E]         None    Kilobytes   Kilobytes   Kb/10",13,10
DB "Clear (Blank) Screen    B[E] [New attribute] Current or last attr.   HexAttr",13,10
DB "Check for printer       P            None    1=yes,0=no  1=yes,0=no  None",13,10
DB "Get/set video mode      V   [New mode]       Current or last mode    None",13,10
DB "Check coprocessor       7            None    1=yes,0=no  1=yes,0=no  None",13,10
DB "Check for ANSI.SYS      A            None    1=yes,0=no  1=yes,0=no  None",13,10
DB "Get current directory   Y[E]         None    Directory   Level/Drive Drive",13,10
;DB "Hex clock since 1/1/80  $            None    Days        Days/255    None",13,10
DB "Surrogate [char] args: ",cr," is Carriage Return, "
DB esc_s," is Escape, ",ext," is Extended.$",13,10


guess     DB      80             ; Prompt for string
actual    DB      0
string    DB      80 DUP (0)

extend	  DB	  0		 ; Flag	for extended command
vid	  DB	  3		 ; Video mode
ans	  DB	  27,"[6n$"	 ; ANSI	string to get current position
overwrt	  DB	  8,8,8,8,"    $"; Overwrite ANSI characters
delete	  DB	  8,8,8,"$"	 ; Delete ANSI characters

what      DB      "GET="        ; Variable name
lwhat     EQU     $-what

prompt	  DW	  0		 ; Pointer to prompt
lprompt	  DW	  0		 ; Length of prompt
arg	  DW	  0		 ; Pointer to argument
larg	  DW	  0		 ; Length of argument
EGA_L     DB      0
clock     DB      'CLOCK$',00H
; Command table
IFNDEF GET
cmds      DB      "BCSVDMEKFP7AY$NT" ; Command character list
ELSE
cmds      DB      "BCSVDMEKFP7AYNT" ; Command character list (omit $ for GET)
ENDIF
lcmds	  EQU	  $-cmds	 ;   and length	of list
	  EVEN
table     DW      BlankScreen
          DW      GetChar        ; Command procedure table
	  DW	  GetStr
	  DW	  DoVid
	  DW	  GetDOS
	  DW	  GetMem
	  DW	  GetEnvSz
	  DW	  GetDskSz
	  DW	  GetFilSz
	  DW	  VeriPrint
	  DW	  VeriCop
	  DW	  VerAnsi
          DW      GetDir
IFNDEF GET
          DW      Clock$                ; This calls the $ option.
ENDIF
          DW      aNswer                ; Yes/No answer--CR=Yes,Esc gives 126
          DW      Moving
          DW      Help
          ;DW      NoCmd

err1	  DB	  "Invalid command",7,13,10,"$"
err2	  DB	  "Out of environment space",7,13,10,"$"
err3	  DB	  "Must have DOS Version 2.0 or higher",7,13,10,"$"

	  .CODE
start:	  mov	  ax,@DATA		; Starting execution address
	  mov	  ds,ax			; Initialize data segment

	  @GetVer			; Get DOS version
	  or	  al,al			; Is it	0?
	  jne	  DOSOK			; No? Continue
	  @DispStr err3			; Yes? Quit for	1.x
	  int	  20h

DOSOK:	  mov	  si,83h		; Starting point in PSP
	  mov	  ax,WORD PTR es:[5Dh]	; Load command characters
	  cmp	  al,' '		; Is it	space?
	  jne	  isarg			; If no	argument, show help
	  call	  Help

isarg:	  cmp	  ah,'E'		; Extend flag?
	  jne	  noextend		; No? Continue
	  inc	  extend		; Yes? Turn on flag and	adjust pointer
	  inc	  si

noextend: call	  GetArg		; Get argument from command line

	  push	  es			; Save and load	DS into	ES
	  mov	  bx,ds
	  mov	  es,bx
	  mov	  di,OFFSET cmds	; Load pointer to command table
	  mov	  cx,lcmds+1		; Load length
          repne   scasb                 ; Find position of command character
	  pop	  es
	  sub	  di,(OFFSET cmds)+1	; Point	to procedure
	  shl	  di,1			; Adjust for word addresses
	  call	  table[di]		; Call the selected procedure

	  push	  ax			; Save
	  push	  es
	  call	  DoEnviron		; Put result in	environment string
	  or	  ax,ax			; Test for 0 before
	  pop	  es			;   restore
	  pop	  ax
	  jz	  done

	  cmp	  BYTE PTR es:[5Dh],'E'	; Is it	Environment command
	  je	  done			; Yes? Skip message

error2:   @DispStr err2 		; Error message
	  mov	al,0
done:	  @Exit				; Quit with AL as return code

; End of program - Start of procedures

; Procedure Blank Screen without changing color or attribute or number of lines

BlankScreen PROC
        PUSH    DS                      ; See if the number of active rows
	MOV	AX,BIOS_DATA		; is available in the BIOS data area.
	MOV	DS,AX			; This is the address of the segment.
	ASSUME	DS:BIOS_DATA		; Tell the assembler
	MOV	DH,CRT_ROWS		; Good only in later versions of DOS,
	POP	DS			; else it will be 0 (I hope).
        ASSUME  DS:@data               ; Tell MASM we are back home.
	CMP	DH,18H			; 25 rows?
	JLE	ROWS_OK 		; If less, don't use it.
	SUB	DH,18H			; Else get the difference from 25,
	MOV	BYTE PTR EGA_L,DH	; and save it for later use.
ROWS_OK:
        MOV     BH,0                    ; Page number for setting cursor
        MOV     AH,8                   ; Function to read attribute
        INT     10H                     ; Read attribute in AH (char in al)
          xor   al,al                   ; This code borrowed from video proc
          push    ax                    ; Save last attribute
          ;cmp     larg,1                ; How many digits in mode?
          ;jl      gotattr               ; None? Get out
          MOV   CX,LARG
          JCXZ  GOTATTR
          mov     bx,arg                ; Load address of argument string
          mov     ax,es:WORD PTR [bx]   ; Get address of mode string
          DEC   CX
          JCXZ  ONEDIG
          DEC   CX
          JCXZ  TWODIG
          ;je      onedig                ; One digit - skip the reverse
          INC     BX                    ; Load address of argument string
          mov     ax,es:WORD PTR [bx]   ; Get address of mode string
TWODIG:   xchg    ah,al                 ; Reverse for two digits
          sub     ax,'00'               ; Adjust from ASCII to numeric
          cmp   ah,09
          jle   InHex
          sub   ah,07
          and   ah,0Fh
InHex:  cmp     al,09
        jle     HexOk
        sub     al,07
        and     al,0Fh
HexOk:  cmp     extend,0
        je      NotHex
        mov     cl,4
        shl     ah,cl
        add     ah,al
        jmp     SHORT gotattr
NotHex: aad                           ; Convert to binary
        JCXZ  setattr
        ;SUB     CX,CX
        MOV     CL,ES:BYTE PTR [BX-1]
        MOV     AH,CL
        SUB     CL,'0'
        MOV     CH,2
        SUB     CH,CL
        ADD     AH,CH
        SHL     AH,CL
        ADD     AH,AL
         jmp     SHORT GOTattr
onedig:   sub     al,'0'                ; Convert to numeric
        cmp     al,09
        jle     setattr
        sub     al,7                    ; Must be Hex
        and   al,0Fh
setattr:
          xchg    ah,al                 ; Attribute in AH
gotattr:SUB     DX,DX                   ; Position for cursor
        MOV     CX,AX                   ; Attribute in AH, save in CX
        MOV     AH,2                    ; Function for setting cursor, BH=page
        INT     10H                     ; Set cursor position
        MOV     BX,CX                   ; Need attribute in BH
        SUB     CX,CX                   ; Position for scroll
        MOV     DX,184FH                ; Lower right row, col to scroll
        ADD     DH,EGA_L                ; Are there extra lines?
        MOV     AX,600H                 ; Function to clear(scroll) screen
        INT     10H                     ; Bios call
        pop     ax                      ; Last or current attribute
        xchg    al,ah                   ; Set return to attribute
        cmp     extend,0
        jne     IsHex
        call    bintodec                ; Put attribute in enviroment
        jmp     SHORT BlankEnd
IsHex:  call    BinToHex
BlankEnd:RET
BlankScreen     ENDP

IFNDEF GET

; Procedure Clock$                      ; Assembled only if filename not GET.
; Purpose   Get days since 1-1-80 from CLOCK$ device (DOS).
; Input     None

Clock$      PROC

            mov   ax,3d00h              ; Call DOS to open a file
            mov   dx,offset Clock      ; ASCIIZ name of CLOCK$ device
            int   21h                   ; Call DOS
            jc    QuietExit             ; If error, exit...else...
            mov   bx,ax                 ; Handle in AX, need in BX
            mov   dx,offset string      ; Location of output
            mov   cx,0001               ; Length of output expected
            mov   ax,3f00h              ; Function to read device
            int   21h                   ; Call DOS
            jc    QuietExit             ; If error, exit...else...
            mov   ax,word ptr string    ; Number of days since 1-1-80
            cmp   ax,300                ; Take all dates except 300 in 1980.
            ;cmp   ax,0D00h             ; 1989 is early enough, else clock is
            jle   QuietExit             ; not set, so don't use.
            xchg  al,ah                 ; Get digits in proper order, and
            call  BinToHex              ; Convert number in AX to hex digits.
            xor   bx,bx             ; Now have hex in string, length in actual
            mov   bl,byte ptr actual    ; Number of hex digits, no leading 0's?
            mov   actual,5              ; If more than 2 bytes we want to split
            mov   si,bx                 ; it into two hex numbers for use by
            mov   bx,4
ByteLoop:
            dec   si                    ; DEBUG.  We will make 5 bytes out of
            mov   ah,string[si]         ; it, 2 and 2 separated by a space.
            mov   string[bx],ah         ; We are now moving the 4th to the
            cmp   si,0                  ; fifth place, and the third to the 4th.
            jz    DoneLoop
            dec   bx
            cmp   bx,2                  ; Need to put space in 3rd positon
            jne   ByteLoop
            mov   string[bx],' '
            dec   si
DoneLoop:                               ; si is either 0 or 1.
            dec   bx                    ; if si is 0, bx is 3, 2, or 1 after dec
            cmp   bx,si                 ; if bx is 1, si is either 0 or 1
            jz    ClockEnd              ; if bx is 2 or 3, si must be 0
            cmp   bx,2                  ;
            jne   ZeroLoop              ; either bx=3,si=0 or bx=1,si=0
            mov   string[bx],' '
Zero_1:     dec   bx                    ; We know that bx is 2
ZeroLoop:                               ; Now have to fill with leading zeros
            mov   string[bx],'0'
            cmp   bx,3                  ; If 3, we still have to do ' '
            jz    DoneLoop              ; so go back up there.
            cmp   bx,0                  ; If not yet 0, do it again.
            jg    Zero_1

ClockEnd:   ret
QuietExit:  mov   ax,4c00h              ; Quietly exit with a zero return code.
            int 21h
Clock$          ENDP

ENDIF

; Procedure aNswer
; Purpose   get and answer to a yes/no type question.
; Input	    Allowable characters pointed to by "arg" (optional)
;	    Prompt pointed to by "prompt" (optional)
; Output    Character in AX and in "string"

aNswer  PROC
        ;mov     bx,offset prompt        ; Simulate the valid characters
        ;add     bx,lprompt              ; Length of prompt to find end.
        MOV     BX,60H                  ; Use DOS PSP FCB area for this???
        mov     arg,bx              ; Tell GetChar where the args are,
        mov     larg,4              ; and how many...
        mov     word ptr es:[bx],'NY' ; Set up Y(es), N(o) characters
        mov     word ptr es:[bx+2],"}~" ; Set up CR and Esc surrogates
        call    GetChar                 ; Now use existing code
        ret
aNswer  ENDP

; Procedure GetChar
; Purpose   Get	a character from user
; Input	    Allowable characters pointed to by "arg" (optional)
;	    Prompt pointed to by "prompt" (optional)
; Output    Character in AX and	in "string"

GetChar	  PROC

	  call	  ShowPrmpt		; Display prompt if there is one

readkey:  @GetKey 0,1                   ; Get a key
        CALL    CheckChar
        cmp     al,0
        jz      readkey

charend:  push	  AX
          @DispCh 13,10                 ; Better put in cr,lf...
	  pop	  AX
	  ret
GetChar	  ENDP

; Procedure CheckChar
; Purpose   Check character from user
; Input	    Allowable characters pointed to by "arg" (optional)
;           Character to check in AL
; Output    Character in AX(al) and in "string"
;           If AL is 0, bad character.

CheckChar  PROC
	  cmp	  al,13			; Is it	carriage return?
	  jne	  notcr			; Yes? Continue
          mov     al,cr                ; Call it whatever is in cr
                                        ;   (use surrogate in character list
					;    if	you want to accept CR)
notcr:	  cmp	  al,27 		; Is it Escape?
	  jne	  notesc		; Yes? Continue
          mov     al,Esc_s                ; Call it tilde

notesc:   or	  al,al 		; Is it 0 for extended key?
	  je	  exkey			; Special case

	  mov	  bl,al			; Save a copy and swap
	  xchg	  ah,al

	  call	  UpCase		; Uppercase it

	  xchg	  ah,al			; Swap back
	  mov	  si,arg		; Load pointer and length of argument
	  mov	  cx,larg
	  jcxz	  gotchar		; If no	argument, quit early

; Compare character to argument	to see if it's valid

argcheck: mov	  ah,BYTE PTR es:[si]	; Get character
	  inc	  si			; Increment index

	  call	  UpCase		; Convert to uppercase

	  cmp	  ah,al			; Is it	in argument?
	  je	  gotchar		; Yes? We're done
	  loop	  argcheck		;   else check another
          jmp     SHORT BadChar         ;   and get another character

gotchar:  push	  ax
	  cmp	  extend,0		; Is extend flag set?
	  jne	  noecho		; Yes? Don't echo
          cmp     bl,cr                 ; Don't echo ~ (alias for CR)
	  je	  noecho
          cmp     bl,Esc_s              ; Don't echo ~ (alias for Esc)
	  je	  noecho
	  @DispCh bl			; Display valid	character
noecho:	  pop	  ax
	  mov	  string,al		; Put the character in string
          mov     actual,1              ; Length is one
          jmp    SHORT EndCheck

exkey:	  @GetKey 0,1			; Get second key in AL
	  mov	  si,arg		; Load pointer to argument
          cmp     BYTE PTR es:[si],Ext  ; Is argument grave accent?
					;   (use grave in character list if
					;    you want to accept	extended keys)
	  je	  gotext		; Yes? Extended	character
BadChar:  @DispCh 7                     ; No? Illegal, so ring bell
          mov     al,0                  ;   and get another
          jmp   short EndCheck
gotext:   mov     string[0],'`'         ; Extended flag value is "`<char>"
	  mov	  string[1],al
	  mov	  actual,2		; Length is 2
EndCheck: ret
CheckChar   ENDP

; Procedure GetStr
; Purpose   Get	a string
; Input	    Prompt pointed to by prompt	(optional)
; Output    String in "string";	length to AX

GetStr	  PROC

	  call	  ShowPrmpt		; Display prompt if there is one

	  cmp	  extend,1		; Extend flag true?
	  je	  password		; Yes? Then don't echo

	  @GetStr guess,0		; Get string (null-terminated)
	  jmp	  SHORT	gotstr		; Done

password: mov	  bx,OFFSET string	; Load offset of string	buffer
	  mov	  cx,80			; Maximum count
nextkey:  @GetKey 0,1			; Get key, no echo
	  cmp	  al,13			; Is it	carriage return
	  je	  gotpass		; Yes? Done
	  mov	  [bx],al		; No? Put key in buffer
	  inc	  bx			; Point	to next
	  loop	  nextkey

gotpass:  sub	  bx,OFFSET string	; Adjust pointer to get	count
	  mov	  actual,bl		; Save count

gotstr:	  @DispCh 13,10
	  mov	  ax,bx			; Save string length
	  ret
GetStr	  ENDP

; Procedure GetDOS
; Purpose   Get	DOS version
; Input	    None
; Output    Major or minor version in "string";	(major *10)+minor in AX

GetDOS	  PROC
	  @GetVer			; Get DOS version
	  mov	  string,al		; Put major in string
	  mov	  bh,al			; Save copy
	  mov	  al,ah			; Divide minor to get one digit
	  sub	  ah,ah			; Clear	top
	  mov	  cl,10			; Divide by 10
	  div	  cl
	  xchg	  al,bh			; Exchange major and minor
	  mul	  cl			; Multiply major by 10
	  add	  al,bh			; (Major*10)+Minor - 3.2 is now	32
	  cmp	  extend,1		; Extend?
	  jne	  gotver		; No? Already got it
	  mov	  string,bh		; Save number
gotver:	  mov	  actual,1		; Save length 1
	  add	  string,30h		; Convert to ASCII
	  ret
GetDOS	  ENDP

; Procedure GetEnvSz
; Purpose   Get	environment bytes available
; Input	    None
; Output    Environment	bytes available

GetEnvSz  PROC
	  push	  es			; Save ES
	  call	  GetEnv		; Get the environment size
	  pop	  es			; Restore
	  sub	  ax,cx			; Subtract length used from total
					;   length to get length remaining
	  call	  BinToDec		; Convert to string
	  call	  Byticize		; Handle values	too large for byte
	  ret
GetEnvSz  ENDP


; Procedure GetFilSz
; Purpose   Get	the size of a specified	file
; Input	    Filespec pointed to	by "arg"
; Output    File size in AX and String(Hex)

GetFilSz  PROC
          mov     di,arg                ; Point to start and end of arg
          mov     bx,larg
          Call    Open_Size             ; Returns size in DX:AX, handle in BX
          JC      gotsize
          ;mov     actual,0             ; Initial def is 0
          PUSH    DX
          or      dx,dx
          jz      just_ax
          push    ax
          mov     ax,dx
          call    BinToHex
          pop     ax
just_ax:  call    BinToHex
          POP     DX
          or      dx,dx
          jnz     @F
          or      ax,ax
          jz      gotsize2
@@:       mov     cX,10                ; Convert to kilobytes
floop:    rcr     dx,1
          rcr     ax,1
          loop    floop
          ;shr     ax,cl
          ;or      dx,dx
          ;jz      gotsize2
	  inc	  ax			; Round	up
          jmp     SHORT gotsize2

gotsize:  call    BinToDec              ; Convert to string (decimal)
gotsize2:
          call    Byticize              ; Handle large values
	  ret
GetFilSz  ENDP

; Procedure Open_Size
; Purpose   Get the size of a specified file and open for reading
;           Called by GetFilSz and Moving
; Input	    Filespec pointed to	by "arg"
; Output    File size in DX:AX, Handle in BX

Open_Size PROC
          ;cmp     arg,0                 ; File name argument?
          cmp     di,0                 ; File name argument?
	  jne	  isfile
          call    NoCmd
                        ; Must be called with these args!
isfile:  ; mov     di,arg                ; Point to start and end of arg
         ; mov     bx,larg
	  mov	  BYTE PTR es:[bx+di],0	; Make null-terminated

	  push	  ds
          ;@OpenFil arg,0,es             ; Open file for reading
          @OpenFil di,0,es             ; Open file for reading
          pop     ds                    ; Handle now in AX unless error
          jc      ferror                ; Error if carry


;notdir:
          mov   si,ax                   ; Save handle in SI for Moving
          @GetFilSz ax                  ; Size in DX:AX
          jc      ferror                ; Error if carry
          ret
ferror:   ;cmp     ax,5                  ; Access denied? Probably a directory
          ;jne     nofile                ; No file or some other error
          ;mov     ax,0FFh               ; Call directory size 255
          ;jmp     SHORT gotsize        ; DIR and error return size 0!
;nofile:
          sub     ax,ax                 ; Size of nothing is 0
          stc
          ret
Open_Size ENDP

; Procedure GetDskSz
; Purpose   Get	K remaining on specified disk
; Input	    Drive letter pointed to by "arg"
; Output    Disk space remaining

GetDskSz  PROC
          ;sub     ax,ax                 ; Assume default drive
          ;cmp     arg,0                 ; Was there an argument?
          MOV   AX,ARG
          OR    AX,AX
          je      defdrive              ; No? Got drive
          MOV   BX,AX
          MOV   AL,ES:BYTE PTR [BX ]
          AND   AL,5FH
          ;mov     al,BYTE PTR es:6Dh    ; Yes? Get drive letter
	  sub	  al,'A'-1		; Convert to binary
defdrive: @ChkDrv al			; Get disk space
	  cmp	  ax,0FFFFh		; Is drive valid?
	  jne	  valid
	  call	  NoCmd
valid:	  mul	  bx			; Sectors = sectors/cluster * clusters
          shr     ax,1
          ;mul     cx                    ; Bytes = bytes/sector * sectors
          ;mov     cl,10                 ; Convert to kilobytes
          ;shr     ax,cl
          ;inc     ax                    ; Round up
	  call	  BinToDec		; Convert to string
	  call	  Byticize		; Handle large values

	  ret
GetDskSz  ENDP

; Procedure GetMem
; Purpose   Get	memory available
; Input	    None
; Output    Available memory

GetMem	  PROC
	  int	  12h			; Get memory available in K
	  mov	  bx,es			; Get memory used
	  mov	  cx,6			; Convert to K
	  shr	  bx,cl
          sub     ax,bx                 ; Calculate how much is left
          DEC     AX                    ; Seems like we need 1 less???
          ;sub     dx,dx                 ; Clear DX
          ;mov     cx,1024               ; Multiply to get bytes
          ;mul     cx
          ;mov     cx,1000               ; Divide to get thousands (not K)
          ;div     cx
	  call	  BinToDec		; Convert to string
	  call	  Byticize		; Handle large values
	  ret
GetMem	  ENDP

; Procedure VeriPrint
; Purpose   See	if LPT1	(PRN) is available
; Input	    None
; Output    1 for yes or 0 for no

VeriPrint PROC
	  mov	  ax,200h		; Check	printer	status
	  sub	  dx,dx			;   for	main parallel printer (port 0)
	  int	  17h
	  xchg	  dx,ax			; Put 0	(for error) in AX
	  test	  dh,00101001b		; Are any error	bits on?
	  jne	  printerr		; Yes? Leave 0
	  test	  dh,10010000b		; Are both operation bits on?
	  jz	  printerr		; No? Leave 0
	  inc	  ax			; Yes? Return 1
printerr: call	  BinToDec		; Convert to string
	  ret
VeriPrint ENDP

; Procedure DoVid
; Purpose   Get	current	video mode and optionally set a	new mode
; Input	    New	video mode pointed to by "arg" (optional)
; Output    Current video mode (before change)

DoVid	  PROC
	  mov	  ah,0Fh		; Get video mode
	  int	  10h

	  cmp	  larg,1		; How many digits in mode?
	  jl	  gotmode		; None?	Get out
	  push	  ax			; Some?	Save mode
	  mov	  bx,arg		; Load address of argument string
	  mov	  ax,es:WORD PTR [bx]	; Get address of mode string
	  je	  one			; One digit - skip the reverse
	  xchg	  ah,al			; Reverse for two digits
	  sub	  ax,'00'		; Adjust from ASCII to numeric
	  aad				; Convert to binary
	  jmp	  SHORT	setmode
one:	  sub	  al,'0'		; Convert to numeric
setmode:  sub	  ah,ah			; Set mode
	  int	  10h
	  pop	  ax			; Restore
gotmode:  cbw				; Extend to AX
	  call	  BinToDec		; Convert to string
	  ret
DoVid	  ENDP

; Procedure VeriCop
; Purpose   Check for coprocessor
; Input	    None
; Output    1 for yes or 0 for no

VeriCop	  PROC
	  int	  11h			; Check	peripherals
	  test	  al,10b		; Coprocessor
	  mov	  ax,0			; Assume no (don't change flags)
	  jz	  no87			; No? Done
	  inc	  ax			; Yes? Set to 1
no87:	  call	  BinToDec		; Convert to string
	  ret
VeriCop	  ENDP

; Procedure VerAnsi
; Purpose   Check for ANSI driver
; Input	    None
; Output    1 for yes or 0 for no

VerAnsi	  PROC

	  @DispStr ans			; Print	ANSI string to get
					;   cursor position
	  mov	  ah,6			; Check	for key
	  mov	  dl,0FFh		;   in buffer
	  int	  21h
	  jnz	  ansi			; Done if ANSI
	  @DispStr overwrt		; Overwrite ANSI string
	  sub	  ax,ax			; 0 if not ANSI
	  jmp	  SHORT	gotansi
ansi:	  mov	  ax,0C06h		; Clear	returned ANSI keys
	  mov	  dl,0FFh		;   out	of buffer
	  int	  21h
	  @DispStr delete		; Delete ANSI string
	  mov	  ax,1			; Set 1	for true
gotansi:  call	  BinToDec		; Convert to string
	  ret
VerAnsi	  ENDP

; Procedure GetDir
; Purpose   Get	the current directory or drive
; Input	    None
; Output    Current directory or drive in "string"; level or drive number in AX

GetDir	  PROC	  NEAR
	  cmp	  extend,1		; Extend flag true?
	  jne	  directry		; No? Get directory

	  @GetDrv			; Yes? Get drive
	  mov	  ah,al			; Copy
	  add	  ah,'A'		; Convert to drive letter
	  mov	  string,ah		; Put character	in string
	  inc	  actual		; Length 1
	  cbw
	  ret

directry: mov	  si,OFFSET string	; Load address for string
	  mov	  BYTE PTR [si],"\"	; Insert backslash (DOS	doesn't)
	  inc	  si			; Point	to next
	  @GetDir si
	  sub	  cx,cx			; Count	is zero
	  dec	  si			; Move pointer back to start
findback: lodsb				; Load
	  cmp	  al,"\"		; Is it	backslash?
	  jne	  notslash		; No? Continue
	  inc	  dx			; Yes? Increment level counter
notslash: or	  al,al			; Is it	0?
	  loopne  findback		; No? Repeat

	  neg	  cx			; Negate to get	positive count
	  mov	  actual,cl		; Put level in variable
	  xchg	  ax,dx
	  ret
GetDir	  ENDP

; Procedure NoCmd
; Purpose   Return error for invalid command
; Input	    None
; Output    None

NoCmd	  PROC
	  @DispStr err1			; Display error	and quit
	  @Exit	  0
NoCmd	  ENDP

; Procedure Byticize
; Purpose   Adjust word	values to fit in a byte
; Input	    Value in AX
; Output    255	for word values, else value (no	extend)	or value*10 (extend)

Byticize  PROC
	  cmp	  extend,0		; Is extend flag set?
	  je	  sizechk		; No? Check size
	  sub	  dx,dx			; Yes? Clear DX
	  mov	  bx,10			; Divide by 10 to get 10-unit chunks
	  div	  bx

sizechk:  or	  ah,ah			; Is it	255 or less?
	  je	  byteOK
	  mov	  al,0FFh		; No? Call it 255
byteok:	  ret
Byticize  ENDP

; Procedure GetArg
; Purpose   Parse command line for argument and	prompt strings
; Input	    Command line
; Output    Pointer to argument	in "arg"; length (or 0 if none)	in "larg"
;	    Pointer to prompt in "prompt"; length (or 0	if none) in "lprompt"

GetArg	  PROC
	  push	  ax
	  push	  es			; Swap ES and DS
	  push	  ds
	  pop	  es
	  pop	  ds

white:	  lodsb				; Load while white space
	  cmp	  al,' '		; Is it	space?
	  je	  white			; Throw	away
	  cmp	  al,9			; Is it	tab?
	  je	  white			; Throw	away
	  cmp	  al,'"'                ; Is it quote?
	  je	  promptit		; Process
	  cmp	  al,13			; Is it	carriage return?
	  je	  gotarg		; Done

	  sub	  cx,cx
qdone:	  dec	  si			; Adjust
	  mov	  es:arg,si		; Save pointer to argument start

chars:	  lodsb				; Load while not white
	  cmp	  al,' '		; Is it	space?
	  je	  nomore		; Done
	  cmp	  al,9			; Is it	tab?
	  je	  nomore		; Done
	  cmp	  al,13			; Is it	carriage return?
	  loopne  chars			; Throw	away
nomore:	  not	  cx			; Adjust count
	  mov	  es:larg,cx		; Save length
	  jmp	  SHORT	gotarg

promptit: mov	  di,si			; Save pointer to start
	  sub	  cx,cx			; Clear	count
inprompt: lodsb				; Another
	  cmp	  al,13			; Is it	carriage return?
	  je	  oneq			; Yes? Treat one quote like character
	  cmp	  al,'"'                ; Is it quote?
	  loopne  inprompt		; No? Throw away
	  mov	  es:prompt,di		; Save prompt pointer
	  not	  cx
	  mov	  es:lprompt,cx		;   and	length
	  jmp	  SHORT	white		; Get the argument

oneq:	  mov	  si,di			; Restore
	  mov	  cx,-1			; Set count to -1
	  jmp	  SHORT	qdone

gotarg:	  push	  es			; Swap ES and DS back
	  push	  ds
	  pop	  es
	  pop	  ds

	  pop	  ax
	  ret
GetArg	  ENDP

; Procedure ShowPrmpt
; Purpose   If prompt, display it
; Input	    Pointer to prompt
; Output    Prompt to screen

ShowPrmpt PROC
	  cmp	  prompt,0		; Is there a prompt?
	  je	  noshow		; If not, continue
	  push	  ds			; Save and restore DS
	  @Write  prompt,lprompt,,es	; DOS Write function
	  pop	  ds

noshow:	  ret
ShowPrmpt ENDP

; Procedure DoEnviron
; Purpose   Convert a string to	an environment variable
; Input     String in "string", length in "actual"
; Output    String in "GET" environment variable;
;	      AX has 0 for success, nonzero for	failure

DoEnviron PROC
	  call	  GetEnv		; Get environment size,	length,	address
	  mov	  dx,ax			; Save size and	length
	  mov	  bx,cx

; Find "GET="

	  sub	  di,di			; Point	to start
	  sub	  al,al			; Search for zero
          mov     si, OFFSET what       ; Point source at "GET="
findwh:	  repne	  scasb			; Search
	  cmp	  BYTE PTR es:[di],0	; If double null, end of environment
	  je	  gotend
	  jcxz	  noroom		; Error	if not found
	  push	  di			; Save
	  push	  cx
	  mov	  si,OFFSET what	; Load address and length of "what"
	  mov	  cx,lwhat		;   for	comparison
	  repe	  cmpsb			; Compare
	  mov	  si,di			; Make copy
	  pop	  cx			; Restore
	  pop	  di
	  jnz	  findwh

; Find end of "GET" variable

	  xchg	  di,si
	  repne	  scasb			; Find end of environment variable
	  xchg	  si,di			; Point	source to next variable

; Calculate characters left to write

	  mov	  cx,bx			; Load total characters
	  sub	  cx,si			; Subtract finished to get left

; Move everything back to overwrite "GET="

movenv:	  push	  ds			; Save DS
	  mov	  ax,es			; Copy to ES
	  mov	  ds,ax
	  rep	  movsb			; Copy
	  mov	  BYTE PTR es:[di],0	; Put null at end in case of error
	  pop	  ds			; Restore

; Check	environment space

gotend:	  mov	  al,actual		; Load length of string
	  sub	  ah,ah			; Clear	top
	  add	  ax,lwhat		; Add length of	name
	  add	  ax,di			; Add position to get final length
	  cmp	  ax,dx			; Is it	longer than environment?
	  jge	  noroom		; Yes? Quit

; Put GET= at end

	  mov	  si,OFFSET what	; Load address and length of what
	  mov	  cx,lwhat
	  rep	  movsb

; Put new string at end

	  mov	  si,OFFSET string	; Load address and length of string
	  mov	  cl,actual
	  rep	  movsb
	  mov	  WORD PTR es:[di],0	; Put double null at end
	  sub	  ax,ax			; Return 0 for success
	  ret

noroom:	  inc	  ax			; Return nonzero for fail
	  ret
DoEnviron ENDP

; Procedure GetEnv
; Purpose   Find and measure the environment
; Input	    None
; Output    Segment of environment in ES, size in AX, length in	CX

GetEnv	  PROC
	  mov	  dx,es:10h		; Load segment of COMMAND.COM
	  mov	  es,dx			;   into ES
	  mov	  ax,es:2Ch		; Load COMMAND.COM's environment
	  or	  ax,ax			; Is it	0?
	  jnz	  secondry		; No? This is a	secondary command
					;   and	we have	its environment	in AX
	  dec	  dx			; Yes? This is original	COMMAND.COM
	  mov	  es,dx			;   so point ES	to paragraph before PSP
	  add	  dx,es:03		; Offset of environment	is 3 bytes in
	  add	  dx,2			; Adjust it back to PSP
	  mov	  ax,dx			; Put it in AX
secondry:

; Note:
; CodeView cannot debug	the previous section of	code, because the PSP
; addresses checked by the code	are those passed from DOS to CodeView,
; not addresses	passed from DOS	to the program.	To debug with CodeView,
; find the actual address of the environment:

;     S	500:0 L	FFFF "COMSPEC="

; When you find	the actual address, hard code it into your program:

;	  mov	  ax,110Ch		; Debug	line

; Comment the line out for final assembly after	debugging.

	  mov	  si,ax			; Save a copy
	  sub	  dx,dx			; Clear	DX for multiply
	  dec	  ax			; Get paragaraph before	environment
	  mov	  es,ax			; Load into DS
	  mov	  ax,es:03		; Size in paragraphs is	at byte	4
	  mov	  cx,16			; Multiply by 16
	  mul	  cx
	  mov	  es,si			; Restore environment address
	  sub	  di,di			; Point	to start
	  mov	  cx,ax			; Load maximum count (size of
	  mov	  bx,ax			;   environment) and save a copy
	  sub	  ax,ax			; Search for double null
null2:	  repne	  scasb			; Look for null
	  jz	  noerr			; If not out of	space, continue
	  sub	  ax,ax			;   else error (return 0)
	  jmp	  error2
noerr:	  cmp	  BYTE PTR es:[di],0	; Is it	double null?
	  jne	  null2			; No? Look again
	  mov	  cx,di			; Yes? Save length in CX
	  mov	  ax,bx			; Reload size to AX

	  ret
GetEnv	  ENDP

; Procedure BinToDec
; Purpose   Convert binary number in AX	to string
; Input	    Value in AX
; Output    Value string in "string"; length of	string in "actual"

; AX contains number to be converted

BinToDec  PROC
	  push	  ax
	  push	  es
	  sub	  cx,cx			; Clear	counter
	  mov	  bx,10			; Get ready to divide by 10

getdigit: sub	  dx,dx			; Clear	top
	  div	  bx			; Remainder is last digit
	  add	  dl,'0'		; Convert to ASCII
	  push	  dx			; Put on stack
	  inc	  cx			; Count	character
	  or	  ax,ax			; Is quotient 0?
	  jnz	  getdigit		; No? Get another

	  mov	  actual,cl		; Save number of digits
	  mov	  ax,ds			; Load DS to ES
	  mov	  es,ax
	  mov	  di,OFFSET string	; Load source

putdigit: pop	  ax			; Get a	digit off stack
	  stosb				; Store	it to string
	  loop	  putdigit

	  pop	  es
	  pop	  ax
	  ret
BinToDec  ENDP

; Procedure BinToHex
; Purpose   Convert binary number in AX to string in HEX
; Input	    Value in AX
; Output    Value string in "string"; length of	string in "actual"

; AX contains number to be converted

BinToHex PROC
	  push	  ax
	  push	  es
	  sub	  cx,cx			; Clear	counter
	  mov	  bx,16 		; Get ready to divide by 16

hexdigit: sub	  dx,dx 		; Clear top
	  div	  bx			; Remainder is last digit
	  add	  dl,'0'		; Convert to ASCII
	  cmp	  dl,58 		; May be ABCDEF
	  jb	  isnum
	  add	  dl,7
isnum:	  push	  dx			; Put on stack
	  inc	  cx			; Count	character
	  or	  ax,ax			; Is quotient 0?
          jnz     hexdigit              ; No? Get another
          xor     dh,dh
          mov     dl,actual             ; Save number in string already, if any
          add     actual,cl             ; Save number of digits
	  mov	  ax,ds			; Load DS to ES
	  mov	  es,ax
	  mov	  di,OFFSET string	; Load source
          add     di,dx
putdigith: pop	   ax			 ; Get a digit off stack
	  stosb				; Store	it to string
	  loop	  putdigith

	  pop	  es
	  pop	  ax
	  ret
BinToHex  ENDP

; Procedure UpCase
; Purpose   Convert a character	to uppercase
; Input	    Character in AH
; Output    Converted character	in AH

UpCase	  PROC

	  cmp	  ah,"a"		; Is character below lowercase?
	  jl	  ok			; If so, continue
					;   else
	  cmp	  ah,"z"		; Is character above lowercase?
	  jg	  ok			; If so, continue
					;   else
          sub     ah,20h                ; Make it uppercase
ok:	  ret
UpCase	  ENDP

; Procedure Help
; Purpose   Display syntax screens
; Input	    None
; Output    Help to screen

Help      PROC
          mov   larg,0
          call BlankScreen
	  @DispStr help1		; First	screen
	  ;@GetKey			 ; Pause
	  ;@DispStr help2		 ; Second screen
	  @Exit	  0
Help	  ENDP

; Procedure Moving
; Purpose   Display text in file in a moving display
; Input	    Filespec pointed to	by "arg"
; Output    User input character

Moving    PROC
          ;cmp     prompt,0              ; Is there a prompt?
          ;je      BadFile               ; If not, continue
          mov     di,prompt             ; Point to start and end of arg
          mov     bx,lprompt
          Call    Open_Size             ; Leaves handle in bx
        or      dx,dx                   ; Size in DX:AX
        jz      @f
        mov     ax,MaxSize                ; Max size
@@:     mov     dx,offset moving_buffer ;
        mov     cx,ax                   ; File size from AX (<64k)
        cmp     cx,MaxSize
        jle     @f
        mov     cx,MaxSize
@@:     mov     ax,3f00h                ; Read file bx, (cx chars)
        int     21h
        jc      BadFile
        cmp     ax,0
        jz      BadFile
        cmp     ax,80
        jb      BadFile
        mov     bp,offset moving_buffer
        add     bp,ax
        sub     bp,79                   ; offset of last line.
again:  CALL    GET_A_KEY               ; This puts up moving message.
        mov     actual,0
        call    CheckChar               ; Char in AL
        sub     ah,ah
        CMP     AL,0                    ; Is there a key in AL?
        JZ      again                  ; If so, we are done here...
        ;jmp     short got_a_key
        ret
badfile:  sub     ax,ax                 ; Size of nothing is 0

;got_a_key:  ;call    BinToDec              ; Convert to string
	  ret
Moving    ENDP

; Procedure get_a_key
; Purpose   Display moving message until a key is pressed
; Input	    None
; Output    None

GET_A_KEY PROC
        MOV     DL,0FFH                 ; Indicat console input.
        MOV     AX,0C06H                ; Clear keyboard buffer
        INT     21H                     ; Executes function 06, console input
        MOV     DX,1800H                ; Fixed cursor position 24,0
        ;SUB     DH,1                    ; Move the cursor up 1 row
        ;SUB     DL,DL                   ; and to the left margin
        PUSH    CX
        PUSH    DX

SHARE_AGAIN:
        MOV     SI,OFFSET moving_buffer ; Output a line of message
SHARE_LOOP:                             ; starting at this location
        POP     DX                      ; DX has cursor position2
        PUSH    DX                      ; so save it each time.

        MOV     AH,02                   ; using BIOS function 2
        INT     10H                     ; Call BIOS
        MOV     DX,SI
        MOV     CX,79
        mov     bx,2
        MOV     AH,40H                  ; This does the writing.
        INT     21H                     ; Call DOS standard error
        PUSH    DX
        MOV     DL,0FFH                 ; This checks for a key every char.
        MOV     AH,06H                  ; Now check the input status
        INT     21H                     ; Is there a key pressed?
        POP     DX                      ; If so, AL will have key and
        JNZ     THATS_ALL               ; zero flag will be clear.
        INC     DX                      ; else, show another line...
        MOV     SI,DX                   ; Save the location pointer
        MOV     AH,2CH                  ; Start timing loop...
        INT     21H                     ; Hundredths of seconds in DL,
XCHG_LOOP:
        XCHG    DX,DI                   ; So save it for comparison.
TIME_LOOP:
        MOV     AH,2CH                  ; and get the time again.
        INT     21H
        CMP     DX,DI
        JB      XCHG_LOOP
        SUB     DX,DI
        CMP     DX,15
        JB      TIME_LOOP
        CMP     SI,bp                   ; bp has the offset of the last segment
        JB      SHARE_LOOP              ; of text, so start over if there.
                                        ; This is the slow way to get a key.
;        MOV     AH,0BH                  ; Now check the input status
;        INT     21H                     ; Is there a key pressed?
;        OR      AL,AL                   ; If so, AL will be FFh
;        JNZ     THATS_ALL               ; so we are done,
                                        ; But this doesn't actually get the key.
        JMP SHORT SHARE_AGAIN
THATS_ALL:                              ; Function to get key (INKEY)
        POP     DX                      ; using DOS interrupt will be
        POP     CX                      ; handled by calling function.
KEYOK:  RET
GET_A_KEY ENDP
        .DATA
;guess     DB      80             ; Prompt for string
;actual    DB      0
;string    DB      0,0
Moving_buffer db  MaxSize dup(0)
          END    start                  ; End assembly
