		   PAGE   61,132

		   TITLE  DataPath 1.6 - Not Copyrighted (nc) 1987 - GDC Software

		   COMMENT *

   DATAPATH.COM -  This program attaches itself to MS-DOS or PC-DOS for
		   automatic search of specified subdirectories or disks for
		   a program's support ( help, overlay, etc.) files.  It will
		   support CP/M style calls ( CALL 0005h ) as well as normal
		   MS-DOS calls.  Detailed information on use and technical
		   information is in the file DATAPATH.DOC.


    ASSEMBLY	-  MASM DATAPATH;
		   LINK DATAPATH;
		   EXE2BIN DATAPATH.EXE DATAPATH.COM

    AUTHOR	-  Bruce Dubbs
		   GDC Software
		   122 Valencia Dr.
		   Universal City, TX 78148

		   THIS PROGRAM IS NOT COPYRIGHTED 1987.  IT IS RELEASED INTO
		   THE PUBLIC DOMAIN FOR WHATEVER USE YOU WANT.  IF YOU WANT
		   TO DISTRIBUTE THIS PROGRAM OR USE IT WITH ANY OTHER WAY,
		   AN ACKNOWLEDGEMENT WOULD BE NICE.			    *


		   SUBTTL Basic Definitions and Setup
		   PAGE

NUL		   EQU	  00H
BEL		   EQU	  07H
LF		   EQU	  0AH
CR		   EQU	  0DH

TRUE		   EQU	  0FFFFH
FALSE		   EQU	  NOT TRUE

		   .SALL		       ; Supress MACRO listings

SUBTITLE	   MACRO  STRING	       ; Change the subtitle but
					       ; don't display SUBTTL command
		   SUBTTL STRING
		   .LIST
		   PAGE
		   ENDM

PRINT		   MACRO  STRING	       ; Print a string
		   MOV	  DX,OFFSET STRING
		   MOV	  AH,9
		   INT	  21H
		   ENDM


CSEG		   SEGMENT

		   ASSUME CS:CSEG,DS:CSEG,SS:CSEG,ES:NOTHING

		   ORG	  0
XFER_ADDRESS	   LABEL  BYTE

		   ORG	  80H
DP_LENGTH	   LABEL  BYTE

		   ORG	  81H
DATAPATH	   LABEL  BYTE

		   ORG	  100H

START:		   JMP	  INSTALL	       ; Jump to installation
		   DW	  64 DUP (?)
STACK		   LABEL  WORD
WORK_AREA	   DB	  128 DUP (?)
ID		   DB	  'DataPath 1.6'       ;                         1.3

;------------------------------------------------------------------------------
		   .XLIST
		   SUBTITLE <BDOS redirection module>

BDOS		   PROC   FAR

; In a program designed to run under CP/M, a CALL to location 5h would call
; BDOS.  In MS-DOS, CP/M compatability is attempted by inserting a FAR CALL
; at location 5h.  From there the first instruction found is a far jump.  The
; function request was passed in register CL.

; This routine rearranges the stack, and sets up for a proper INT 21h function
; request.

; Note:  CL = 0 (TERMINATE) must be handled specially because the CS segment
;	 is not compatable with a memory resident DataPath.

		   CMP	  CL,0		       ; Terminate?
		   JNE	  BDOS_2
		   JMP	  CS:BDOS_ADDR
BDOS_2: 	   POP	  AX		       ; Throw away 0Ah return addr
		   POP	  AX		       ; Now swap the top two words on
		   POP	  CS:BDOS_TEMP	       ;   the stack
		   PUSH   AX
		   PUSH   CS:BDOS_TEMP
		   MOV	  AH,CL 	       ; Set the function
		   INT	  21H		       ;   and go do it
		   RET

BDOS_TEMP	   DW	  ?
BDOS_ADDR	   LABEL  DWORD
BDOS_OFFSET	   DW	  ?
BDOS_SEGMENT	   DW	  ?

BDOS		   ENDP

;------------------------------------------------------------------------------
		   .XLIST
		   SUBTITLE <Interrupt 24h (Abort) routine>
ABORT		   PROC   FAR

;  This is the fatal error abort routine to check for open drive doors, etc.
;  It will be used only when we are searching for a file.  We will ignore
;  any error and return to the program with carry set.

		   POP	  AX		       ; Discard the INT 24H return
		   POP	  AX		       ;   address and flags
		   POP	  AX

		   POP	  AX		       ; Restore user registers
		   POP	  BX
		   POP	  CX
		   POP	  DX
		   POP	  SI
		   POP	  DI
		   POP	  BP
		   POP	  DS
		   POP	  ES

		   XCHG   BP,SP 	       ; Set the carry flag on the
		   OR	  BYTE PTR 4[BP],1     ;   stack so it will be set
		   XCHG   BP,SP 	       ;   after the IRET
		   IRET

ABORT		   ENDP

;------------------------------------------------------------------------------
		   PAGE

; The routines below set and restore the INT 24H fatal error handler.
; They use an INT 21H call that reenters DataPath.  The stack will grow a
; little, but the call is just passed to MS-DOS.

SET_ABORT:	   PUSH   AX		       ; Save registers
		   PUSH   BX
		   PUSH   ES
		   MOV	  AX,3524H	       ; Get INT 24H address
		   INT	  21H
		   MOV	  ABORT_OFFSET,BX      ;   and save it
		   MOV	  ABORT_SEGMENT,ES
		   MOV	  DX,OFFSET ABORT
		   MOV	  AX,2524H	       ; Set the interrupt address
		   INT	  21H
		   POP	  ES		       ; Restore registers
		   POP	  BX
		   POP	  AX
		   RET

CLEAR_ABORT:	   PUSH   AX		       ; Save registers
		   PUSH   DX
		   PUSH   DS
		   LDS	  DX,ABORT_ADDRESS     ; Reset address
		   MOV	  AX,2524H	       ; Set the interrupt address
		   INT	  21H
		   POP	  DS		       ; Restore registers
		   POP	  DX
		   POP	  AX
		   RET

ABORT_ADDRESS	   LABEL  DWORD
ABORT_OFFSET	   DW	  ?
ABORT_SEGMENT	   DW	  ?

;------------------------------------------------------------------------------
		   .XLIST
		   SUBTITLE <Interrupt 21H intercept>
; When installed, DataPath intercepts all INT 21H call and works on three
; of them.

NEW_21: 	   PUSHF		       ; Save flags
		   CMP	  CS:DATAPATH,'$'      ; If null datapath, skip to DOS
		   JE	  OLD		       ;			    1.6
		   CMP	  AH,0FH	       ; Is the call 'OPEN' ?
		   JE	  OPEN
		   CMP	  AH,23H	       ; Is the call 'FILE SIZE' ?
		   JE	  OPEN
		   CMP	  AH,0BDH	       ; Is this 'our' function?
		   JE	  OUR_FUNCTION
		   CMP	  AH,3DH	       ; Is it 'OPEN FILE HANDLE' ?
		   JNE	  OLD
		   JMP	  HANDLE

OLD:		   POPF 		       ; Restore flags we called with
		   JMP	  CS:OLD_21	       ; Otherwise don't mess with the
					       ;   interrupt

; Check if caller is looking for DataPath

OUR_FUNCTION:	   PUSH   ES		       ; Save registers 	1.2
		   PUSH   CX		       ;			1.2
		   PUSH   SI		       ;			1.2
		   PUSH   DI		       ;			1.2

		   PUSH   CS		       ; Set destination to us
		   POP	  ES		       ;
		   MOV	  DI,OFFSET ID	       ; Point to our ID	1.2
		   MOV	  CX,12 	       ;			1.2
		   REPE   CMPSB 	       ; See if ID is us	1.2

		   POP	  DI		       ; Restore registers	1.2
		   POP	  SI		       ;			1.2
		   JCXZ   ID_OK 	       ; If zero, ID matches	1.2
		   POP	  CX		       ; Reset registers	1.2
		   POP	  ES		       ;			1.2
		   JMP	  OLD		       ; And continue to DOS	1.2

ID_OK:		   POP	  CX		       ; Reset CX		1.2
		   ADD	  SP,2		       ; ES stays the same	1.2
		   NOT	  BX		       ; Invert BX to say we're here
		   POPF
		   IRET


SAVE_SS 	   DW	  ?		       ; Stack Segment storage
SAVE_SP 	   DW	  ?		       ; Stack Pointer storage
OLD_21		   LABEL  DWORD 	       ; Real INT 21H address
OLD_21_OFFSET	   DW	  ?
OLD_21_SEGMENT	   DW	  ?
VERBOSE 	   DB	  0FFH		       ; TRUE
WRITE_ENABLE	   DB	  0		       ; FALSE
		   .XLIST
		   SUBTITLE <INT 21H Functions 'OPEN' and 'FILE SIZE'>

; This procedure handles the old CP/M 'OPEN' routine.  If the call does not
; directly, a call to the routine 'FIND_FILE' checks any alternate paths.
; If the file is then found, the directory is changed to the found file's
; directory, the file is opened, and the directory is then changed back to
; the original directory.

FUNCTION	   DB	  ?		       ; Calling function
DRIVE_SPECIFIED    DB	  ?
CURRENT_DRIVE	   DB	  ?
RESULT		   DB	  ?
FCB_OFFSET	   DW	  ?
FCB_SEGMENT	   DW	  ?
FILENAME	   DB	  13 DUP (?)
					       ; Flags are already pushed
OPEN:		   STI			       ; Reenable interrupts
		   MOV	  CS:FUNCTION,AH       ; Save calling function
		   CALL   CS:OLD_21	       ; Try to open the file
		   CMP	  AL,0		       ; Successful ?
		   JNE	  OPEN_2	       ; If not, try DataPath
		   IRET 		       ; Otherwise return

; Search the DataPath

OPEN_2: 	   MOV	  CS:SAVE_SS,SS        ; Save the stack
		   MOV	  CS:SAVE_SP,SP
		   MOV	  AX,CS 	       ; Set the stack
		   MOV	  SS,AX
		   MOV	  SP,OFFSET STACK
		   PUSH   BX		       ; Save the registers
		   PUSH   CX
		   PUSH   DX
		   PUSH   SI
		   PUSH   DI
		   PUSH   DS
		   PUSH   ES
		   PAGE

; Set up defaults and save FCB data.

		   MOV	  ES,AX 	       ; Set string destination seg
		   MOV	  SI,DX
		   MOV	  CS:ATTRIBUTES,0      ; Set default attributes
		   MOV	  CS:FCB_OFFSET,DX     ; Save FCB address
		   MOV	  CS:FCB_SEGMENT,DS

		   CLD			       ; String increase
		   LODSB		       ; Get 1st FCB char (bump SI)
		   CMP	  AL,0FFH	       ; Extended FCB ?
		   JNE	  OPEN_4
		   MOV	  AL,5[SI]	       ; Get attributes
		   MOV	  CS:ATTRIBUTES,AL     ;   and save
		   MOV	  AL,6[SI]	       ; Get drive specification
		   ADD	  SI,7		       ; Adjust to point to filename

; Copy filename to a work area as an ASCII string.  Also check for wildcards.
; If a wildcard is found, don't search DataPath.

OPEN_4: 	   MOV	  CS:DRIVE_SPECIFIED,AL; Save drive specification

		   MOV	  CX,8
		   MOV	  DI,OFFSET FILENAME   ; Point to work area

OPEN_5: 	   LODSB		       ; Transfer filename
		   CMP	  AL,'?'
		   JE	  OPEN_8A
		   CMP	  AL,' '               ; Blanks terminate the string
		   JE	  OPEN_6
		   STOSB
OPEN_6: 	   LOOP   OPEN_5

		   MOV	  AL,'.'               ; Add a '.'
		   STOSB
		   MOV	  CL,3

OPEN_7: 	   LODSB		       ; Transfer extension to work
		   CMP	  AL,'?'               ;    area
		   JE	  OPEN_8A
		   CMP	  AL,' '
		   JE	  OPEN_8
		   STOSB
		   LOOP   OPEN_7

OPEN_8: 	   MOV	  AL,NUL	       ; Terminate ASCIIZ string
		   STOSB
		   PAGE

		   PUSH   CS
		   POP	  DS		       ; DS points to us
		   MOV	  RESULT,0FFH	       ; Set default to not found
		   MOV	  DX,OFFSET FILENAME   ; ES:DX points to filename
		   MOV	  DI,DX
		   CALL   GET_LENGTH
		   CMP	  CL,0
		   JE	  OPEN_8A
		   CALL   FIND_PATH
		   JNC	  OPEN_9	       ; File not found

OPEN_8A:	   JMP	  OPEN_20

; We found the file. We will now:
;  1.  Get the found drive.
;  2.  Get the current directory on the found drive.
;  3.  Set the found directory on the found drive.
;  4.  Set found drive in FCB.
;  5.  Open the file.
;  6.  Reset the current directory on the found drive.
;  7.  If the sspecified FCB drive was not zero, reset it.

OPEN_9: 	   MOV	  AH,19H	       ; Get current drive
		   INT	  21H
		   INC	  AL		       ; Set 1=A, 2=B, etc
		   MOV	  CURRENT_DRIVE,AL

		   MOV	  DI,OFFSET XFER_ADDRESS ; Get found drive
		   MOV	  BX,DX
		   MOV	  AL,1[BX]	       ; Check if 2nd char is a ':'
		   CMP	  AL,':'               ; If drive not specified,
		   JE	  OPEN_10	       ;   then get current drive

		   MOV	  AL,CURRENT_DRIVE
		   ADD	  AL,'@'               ; Make ASCII
		   JMP	  SHORT OPEN_11

OPEN_10:	   MOV	  AL,[BX]	       ; Get found drive
OPEN_11:	   MOV	  CL,AL 	       ; Save in CL and
		   STOSB		       ;   memory
		   MOV	  AX,'/:'              ; Add ':/' (stored backward)
		   STOSW

		   MOV	  SI,DI 	       ; Set up for 'Get Directory'
		   SUB	  CL,'@'               ; Make drive numeric
		   MOV	  DL,CL 	       ; Save in CL
		   MOV	  AH,47H	       ; Get current directory
		   INT	  21H
		   JC	  OPEN_20	       ; Shouldn't happen
		   PAGE

		   MOV	  DI,END_PATH	       ; Point to filename
		   DEC	  DI		       ; Point to end of path
		   MOV	  AL,NUL	       ; Make ASCIIZ string
		   STOSB

		   MOV	  DX,BX 	       ; Point to directory
		   MOV	  AH,3BH
		   INT	  21H		       ; Set directory Path

		   PUSH   DS
		   MOV	  DX,FCB_OFFSET        ; Point to FCB
		   MOV	  DS,FCB_SEGMENT
		   MOV	  BX,DX
		   MOV	  AL,[BX]	       ; Get 1st char
		   CMP	  AL,0FFH	       ; Check for extended FCB
		   JNE	  OPEN_14
		   ADD	  BX,7		       ; Point to drive specifier
OPEN_14:	   MOV	  [BX],CL	       ; Set drive specifier

		   MOV	  AH,CS:FUNCTION       ; Now open the file
		   PUSHF
		   CALL   CS:OLD_21
		   POP	  DS
		   MOV	  RESULT,AL	       ; Save result

		   MOV	  DX,OFFSET XFER_ADDRESS
		   MOV	  AH,3BH
		   INT	  21H		       ; Reset found directory

		   MOV	  AL,DRIVE_SPECIFIED   ; If drive was specified,
		   CMP	  AL,0		       ;   reset it
		   JNE	  OPEN_16
		   MOV	  AL,CURRENT_DRIVE     ; otherwise, use current drive
OPEN_16:	   MOV	  BX,FCB_OFFSET        ; Point to FCB
		   MOV	  DS,FCB_SEGMENT
		   MOV	  AH,[BX]	       ; Get 1st character
		   CMP	  AH,0FFH	       ; Check for extended FCB
		   JNE	  OPEN_18
		   ADD	  BX,7		       ; Point to drive
OPEN_18:	   MOV	  [BX],AL	       ; Set drive
		   PAGE

OPEN_20:	   POP	  ES		       ; Restore all registers
		   POP	  DS
		   POP	  DI
		   POP	  SI
		   POP	  DX
		   POP	  CX
		   POP	  BX

; Note -- interrupts are automatically disabled for one instruction after
;	  updating a segment register

		   MOV	  SS,CS:SAVE_SS
		   MOV	  SP,CS:SAVE_SP
		   MOV	  AH,CS:FUNCTION
		   MOV	  AL,CS:RESULT	       ; Return result of OPEN
		   IRET

		   .XLIST
		   SUBTITLE <INT 21H Function to 'OPEN FILE HANDLE'>

; This procedure handles the 'OPEN FILE HANDLE' routine.  If the call does
; not work directly, a call to the routine 'FIND_FILE' checks any alternate
; paths.  If the file is found, the file is then opened.

HANDLE: 	   CLI			       ; Enable hardware interrupts
		   MOV	  CS:CALLER,AX	       ; Save calling function
		   CALL   CS:OLD_21	       ; Try the open as sent

		   JC	  HANDLE_0
		   JMP	  HANDLE_4	       ; Success

; Try data paths
HANDLE_0:	   MOV	  CS:SAVE_AX,AX        ; Save original error code
		   MOV	  CS:SAVE_SS,SS        ; Save old SS:SP
		   MOV	  CS:SAVE_SP,SP
		   MOV	  AX,CS
		   MOV	  SS,AX 	       ; SS <- CS
		   MOV	  SP,OFFSET STACK      ; Set stack pointer
		   PUSH   BX		       ; Save CPU state
		   PUSH   CX
		   PUSH   DX
		   PUSH   SI
		   PUSH   DI
		   PUSH   DS
		   PUSH   ES

		   MOV	  BX,DS
		   MOV	  DS,AX 	       ; DS <- CS
		   MOV	  ES,BX 	       ; ES <- OLD DS

		   PAGE

;  See if the caller:
;    1. Asked for a open to write, or
;    2. Has a path length not in the range of 1..80 bytes, or
;    3. Had any wildcards in the file name, or
;    4. Specified a directory or drive.
;  If any of the above are true, do not search extra paths.


		   CMP	  WRITE_ENABLE,TRUE    ; See if read only
		   JE	  HANDLE_1	       ; Skip next if so

		   MOV	  AX,CALLER	       ; Get calling function
		   CMP	  AL,0		       ; Open for read?
		   STC			       ; Set if error
		   JNE	  HANDLE_3	       ; If not, skip DataPath

HANDLE_1:	   PUSH   BX
		   MOV	  BX,DX
		   CMP	  ES:BYTE PTR[BX+1],':'; if drive is specified,
		   JNE	  HANDLE_2	       ;    ignore it
		   ADD	  DX,2
HANDLE_2:	   MOV	  DI,DX 	       ; Set DI for SCASB instructions
		   POP	  BX

		   CALL   GET_LENGTH	       ; Get length of filename
		   CMP	  CL,0		       ; If zero, return	1.6
		   STC			       ; Assume error		1.6
		   JE	  HANDLE_3

		   CALL   CHECK_WILD	       ; Check for wildcard characters
		   JC	  HANDLE_3	       ; If present, return

		   MOV	  ATTRIBUTES,0111B     ; Match any attribute
		   CALL   FIND_PATH	       ; Search for the file
		   JC	  HANDLE_3

		   MOV	  AX,CALLER	       ; No error, open the file
		   PUSHF
		   CALL   OLD_21
		   JC	  HANDLE_3	       ; Jump if we have an error
		   MOV	  HANDLE_NUM,AX

HANDLE_3:	   POP	  ES		       ; Restore all registers
		   POP	  DS
		   POP	  DI
		   POP	  SI
		   POP	  DX
		   POP	  CX
		   POP	  BX
		   MOV	  AX,CS:SAVE_AX        ; This is original error code
		   MOV	  SS,CS:SAVE_SS
		   MOV	  SP,CS:SAVE_SP
		   JC	  HANDLE_4
		   MOV	  AX,CS:HANDLE_NUM     ; If no error, return the handle
		   PAGE

; Insure the flags on the stack are set properly.

HANDLE_4:	   XCHG   BP,SP 	       ; We want to index on SP
		   JC	  HANDLE_6
		   AND	  BYTE PTR 4[BP],0FEH  ; Clear carry on the stack
		   JMP	  SHORT HANDLE_8       ;    or
HANDLE_6:	   OR	  BYTE PTR 4[BP],1     ; Set carry on stack
HANDLE_8:	   XCHG   BP,SP 	       ; Reset registers
		   IRET 		       ; OK to return

CALLER		   DW	  ?
HANDLE_NUM	   DW	  ?
SAVE_AX 	   DW	  ?
;------------------------------------------------------------------------------
		   .XLIST
		   SUBTITLE <Subroutines>

;  Find the length of the path

GET_LENGTH:	   CLD			       ; Strings increase
		   PUSH   DI		       ; Save start location
		   MOV	  AL,NUL
		   MOV	  CX,80 	       ; Set max length of path
		   REPNE  SCASB 	       ; Find a NUL
		   SUB	  CX,79 	       ; Get length of path
		   NEG	  CX
		   MOV	  CS:NAME_LENGTH,CX    ; Save the filename length
		   POP	  DI
		   RET
;------------------------------------------------------------------------------

;  Check for Drive, Directory, or wildcards ( ':', '\', '*', or '?' )

CHECK_WILD:	   MOV	  BX,OFFSET WILDCARDS  ; Point to exceptions
		   MOV	  CX,4		       ; There are 4 of them

C1:		   PUSH   CX		       ; Save the count
		   PUSH   DI		       ; Save the start location
		   MOV	  CX,NAME_LENGTH       ; Length to scan
		   MOV	  AL,[BX]	       ; Get a character
		   REPNE  SCASB 	       ; See if its there
		   CMP	  CL,0		       ; Set the flags
		   POP	  DI		       ; Reset start location
		   POP	  CX		       ; Reset count
		   JNE	  C2		       ; Found exception, no DataPath
		   INC	  BX		       ;  else, point to next char
		   LOOP   C1		       ;	and check for it
		   CLC
		   RET			       ; Return OK

C2:		   STC			       ; Error return
		   RET

NAME_LENGTH	   DW	  ?
WILDCARDS	   DB	  ':\*?'
;------------------------------------------------------------------------------
		   PAGE

; The subroutine FIND_PATH checks for the file in question along the
; supplementary DataPath.  The logic of the routine is as follows:

;	 1.  If the DataPath is exhausted, exit with carry set.
;	 2.  Copy the next DataPath, delimited by a ';' to the working area.
;	     a.  Insure it ends with a backslash.
;	 3.  Execute DOS call to 'Find First Matching File'.
;	     a.  If file is found, return with DS:DX pointing to the
;		 path/filename for a subsequent Open call.
;	     b.  Otherwise, goto step 1 above.

;	 Upon entry, ES:DX points to filename.

ATTRIBUTES	   DB	  ?
END_PATH	   DW	  ?
FILE_OFFSET	   DW	  ?
FILE_SEGMENT	   DW	  ?
LAST_PATH	   DB	  ?

FIND_PATH	   PROC   NEAR
		   MOV	  FILE_OFFSET,DX       ; Save file pointers
		   MOV	  FILE_SEGMENT,ES

		   CALL   SET_ABORT	       ; Set our INT 24H handler
		   CALL   SET_DTA	       ; Set Disk Transfer Address

		   MOV	  SI,OFFSET DATAPATH   ; Point to DataPath

; Move the path to our work area

		   MOV	  AX,CS 	       ; Reset destination to us
		   MOV	  ES,AX
		   MOV	  LAST_PATH,FALSE      ; Not at last path yet

F1:		   CMP	  LAST_PATH,TRUE       ; Are we done?
		   JNE	  F2		       ; Continue if done
		   CALL   CLEAR_ABORT	       ; All done, reset system
		   CALL   RESET_DTA
		   STC			       ; Set carry means not found
		   RET

F2:		   MOV	  CX,LENGTH WORK_AREA  ; Point to work area
		   MOV	  DI,OFFSET WORK_AREA
		   MOV	  AX,NUL	       ; Start AX as two nuls

		   PAGE

F3:		   LODSB		       ; Get a character
		   CMP	  AL,';'               ; End of path?
		   JE	  F6
		   CMP	  AL,'$'               ; End of path and DataPath?
		   JE	  F5
		   STOSB		       ; Save the character in memory
		   MOV	  AH,AL 	       ;   and here to check for '\'
		   LOOP   F3		       ; Get next character

F5:		   MOV	  LAST_PATH,TRUE       ; Set last path flag
F6:		   CMP	  AH,NUL	       ; Check for zero length DataPath
		   JE	  F7
		   MOV	  AL,'\'               ; Is last character a backslash
		   CMP	  AH,AL
		   JE	  F7
		   STOSB		       ; If not, make it a backslash

; Add the filename to the path

F7:		   MOV	  END_PATH,DI	       ; Save pointer to path end
		   PUSH   DS
		   PUSH   SI
		   MOV	  SI,FILE_OFFSET
		   MOV	  CX,NAME_LENGTH       ; Set the length
		   MOV	  DS,FILE_SEGMENT      ; Point to original filename
		   REP	  MOVSB 	       ; Transfer filename
		   MOVSB		       ; Including the NUL
		   POP	  SI
		   POP	  DS

; Find First Matching File

		   MOV	  DX,OFFSET WORK_AREA
		   MOV	  AH,4EH	       ; Find first
		   MOV	  CH,0
		   MOV	  CL,ATTRIBUTES        ; Match given attributes
		   INT	  21H		       ; Find the file
		   JC	  F1		       ; If not found, check next path

		   CALL   CLEAR_ABORT	       ; Successful return
		   CALL   RESET_DTA
		   CLC
		   RET
FIND_PATH	   ENDP
;------------------------------------------------------------------------------

		   PAGE

; Set Disk Transfer Address

SET_DTA:	   MOV	  AH,2FH	       ; Save the old Disk Xfer Addr
		   INT	  21H
		   MOV	  DTA_OFFSET,BX
		   MOV	  DTA_SEGMENT,ES
		   MOV	  AH,1AH	       ; Set our Disk Xfer Addr
		   MOV	  DX,OFFSET XFER_ADDRESS
		   INT	  21H
		   RET

; Reset Disk Transfer Address

RESET_DTA:	   PUSH   DS
		   PUSH   DX
		   LDS	  DX,DTA	       ; Point to old Disk Xfer Addr
		   MOV	  AH,1AH
		   INT	  21H		       ; Reset it
		   POP	  DX
		   POP	  DS
		   RET

DTA		   LABEL  DWORD
DTA_OFFSET	   DW	  ?
DTA_SEGMENT	   DW	  ?
;------------------------------------------------------------------------------
		   .XLIST
		   SUBTITLE <DataPath installation procedure>

; Start of DataPath installation procedure

TRANSIENT	   LABEL  WORD		       ; Start of transient load

BAD_DOS_MSG	   DB	  'DataPath requires DOS 2.0 or above',CR,LF
		   DB	  'DataPath NOT installed',CR,LF,'$'
RESIDENT_FLAG	   DB	  0		       ; False			1.2
HELP		   DB	  0		       ; False			1.2
CLEAR_FLAG	   DB	  0		       ; False			1.5
		   PAGE

INSTALL:	   MOV	  AX,CS
		   MOV	  DS,AX 	       ; INSURE DS IS SET

; Check for DOS 2.0 or above

		   MOV	  AH,30H	       ; Get DOS version
		   INT	  21H
		   CMP	  AL,2		       ; Is it 2.0 or more?
		   JGE	  INSTALL_1	       ; Continue if OK

		   PRINT  BAD_DOS_MSG
		   INT	  20H		       ; TERMINATE

INSTALL_1:	   MOV	  AX,3521H	       ; GET OLD INT 21 ADDR
		   INT	  21H
		   MOV	  OLD_21_OFFSET,BX     ; SAVE IT
		   MOV	  OLD_21_SEGMENT,ES

; Check if DataPath is already installed by calling a special (our own)
; function of INT 21H.

		   MOV	  AH,0BDH	       ; Our interrupt
		   MOV	  SI,OFFSET ID	       ; Point to name		 1.2
		   XOR	  BX,BX 	       ; Insure BX is zero
		   INT	  21H		       ; See if DataPath is there
		   INC	  BX		       ; If so, BX will now be zero
		   JNZ	  INSTALL_3

; DataPath is already there

		   MOV	  RESIDENT_FLAG,TRUE
		   JMP	  SHORT INSTALL_4

; Set new interrupt 21 address

INSTALL_3:	   MOV	  DX,OFFSET NEW_21     ; POINT TO OFFSET
		   MOV	  AX,2521H	       ; UPDATE THE INTERRUPT
		   INT	  21H
		   PAGE
; Set the CP/M BDOS Entry Point for compatability

		   MOV	  BX,6
		   LES	  BX,[BX]	       ; GET ADDRESS OF CALL TO CP/M
		   MOV	  AX,ES:1[BX]	       ;   AND SAVE IT
		   MOV	  BDOS_OFFSET,AX
		   MOV	  AX,ES:3[BX]
		   MOV	  BDOS_SEGMENT,AX

		   MOV	  DX,OFFSET BDOS
		   MOV	  ES:1[BX],DX	       ; SET OUR ADDRESS INTO MS-DOS
		   MOV	  ES:3[BX],DS

; If DataPath program is not resident OR a new DataPath is passed, then
;   transfer the new Datapath

INSTALL_4:	   CALL   CHECK_SWITCHES       ; Set up switches
		   CALL   SQUEEZE_PATH	       ; Remove illegal characters  1.5

		   CMP	  RESIDENT_FLAG,FALSE
		   JE	  INSTALL_5
		   CMP	  CLEAR_FLAG,TRUE
		   JE	  INSTALL_5
		   CMP	  DP_LENGTH,0
		   JE	  INSTALL_6
INSTALL_5:	   CALL   SET_DATAPATH

; If a new DataPath is not passed, or we are in VERBOSE mode, display the
;   current DataPath

		   CMP	  HELP,TRUE	       ; Always display if help    1.2
		   JE	  INSTALL_6	       ;   is set		   1.2
		   CMP	  ES:VERBOSE,TRUE
		   JE	  INSTALL_6
		   CMP	  DP_LENGTH,0
		   JNE	  INSTALL_8
INSTALL_6:	   CALL   DISPLAY

; Just terminate if we are already resident

INSTALL_8:	   CMP	  CS:RESIDENT_FLAG,TRUE
		   JNE	  INSTALL_9
		   MOV	  AX,4C00H	       ; OK RETURN
		   INT	  21H		       ; TERMINATE

; Exit but stay resident - set paragraphs to save

PARAS		   =	  (TRANSIENT-XFER_ADDRESS+15) SHR 4 ;	      1.5

INSTALL_9:	   MOV	  DX,PARAS	       ; SET LENGTH TO RESERVE	 1.2
		   MOV	  AX,3100H
		   INT	  21H		       ; TERMINATE & STAY RESIDENT

		   PAGE

; Transfer the DataPath but do some pre-processing

SET_DATAPATH	   PROC   NEAR
		   CLD			       ; INCREMENT STRINGS
		   MOV	  SI,OFFSET DATAPATH   ; POINT TO SOURCE CS:81
		   MOV	  DI,SI 	       ; POINT TO DEST	 ES:81
		   MOV	  CL,DP_LENGTH	       ; GET LENGTH OF PASSED STRING
		   MOV	  CH,0

		   CMP	  CLEAR_FLAG,TRUE      ; IF CLEAR_FLAG IS SET	   1.5
		   JE	  SET_9 	       ;   SET NULL DATAPATH	   1.5

		   CMP	  RESIDENT_FLAG,TRUE   ; IF RESIDENT THEN ES WAS SET
		   JE	  SET_2 	       ;   BY INT 21H FUNCTION 'BD'h
		   PUSH   CS		       ; ELSE SET TO CS
		   POP	  ES
		   CMP	  HELP,TRUE	       ; IF HELP AND NOT RESIDENT, 1.2
		   JE	  SET_9 	       ;   SET NULL DATAPATH	   1.2

SET_2:		   CMP	  HELP,TRUE	       ; IF HELP SET, DON'T RESET  1.2
		   JE	  SET_10	       ;   DATAPATH		   1.2

SET_4:		   LODSB		       ; GET A CHARACTER
		   CMP	  AL,' '               ; THROW AWAY NON PRINTING
		   JBE	  SET_8 	       ;    CHARACTERS
		   CMP	  AL,'z'               ; AND OTHER WEIRD CHARS
		   JA	  SET_8
		   CMP	  AL,'a'               ; CONVERT CHARS TO UPPER CASE
		   JB	  SET_7
		   AND	  AL,0DFH
SET_7:		   STOSB		       ; SAVE THE CHARACTER
SET_8:		   LOOP   SET_4
SET_9:		   MOV	  AL,'$'               ; STORE A TERMINATING $
		   STOSB
SET_10: 	   RET

SET_DATAPATH	   ENDP

CHECK_ILLEGAL	   PROC   NEAR		       ;			  1.3
		   PUSH   CX		       ; SAVE REGISTERS 	  1.3
		   PUSH   DI		       ;			  1.3
		   PUSH   ES		       ;			  1.3
		   PUSH   CS		       ; SET ES=CS		  1.3
		   POP	  ES		       ;			  1.3
		   MOV	  CX,10 	       ; CHECK 10 CHARACTERS	  1.3
		   MOV	  DI,OFFSET ILLEGAL_CHARS ;			  1.3
		   REPNE SCASB		       ; SEE IF WE HAVE ILLEGAL   1.3
		   CLC			       ; ASSUME CHAR OK 	  1.3
		   JNE	  CHECK_ILLEGAL_1      ; JUMP IF NO MATCHES	  1.3
		   STC			       ; CHAR IS ILLEGAL	  1.3
CHECK_ILLEGAL_1:   POP	  ES		       ; RESTORE REGISTERS	  1.3
		   POP	  DI		       ;			  1.3
		   POP	  CX		       ;			  1.3
		   RET			       ;			  1.3
CHECK_ILLEGAL	   ENDP 		       ;			  1.3

ILLEGAL_CHARS	   DB	  '$"[]*?+=<>'         ;                          1.3

		   .XLIST
		   SUBTITLE <Display Subroutine>

; This procedure outputs DataPath info to the screen

DISPLAY 	   PROC   NEAR

		   PRINT  INFO		       ; Print heading
		   CMP	  RESIDENT_FLAG,TRUE   ; If first time say installed
		   JE	  DISPLAY_1
		   PRINT  INSTALLED

DISPLAY_1:	   PRINT  CRLF
		   PRINT  NC		       ; Print our name
		   PRINT  CRLF2 	       ; Skip a line
		   PRINT  VERBOSE_MSG	       ; Print verbose status
		   CMP	  ES:VERBOSE,TRUE
		   JE	  DISPLAY_2
		   PRINT  OFF
		   JMP	  SHORT DISPLAY_3
DISPLAY_2:	   PRINT  ON

DISPLAY_3:	   PRINT  WRITE_ACCESS	       ; Print open for write status
		   CMP	  ES:WRITE_ENABLE,TRUE
		   JE	  DISPLAY_4
		   PRINT  OFF
		   JMP	  SHORT DISPLAY_5
DISPLAY_4:	   PRINT  ON

DISPLAY_5:	   PRINT  CRLF		       ; Skip a line

		   PRINT  DATAPATH_MSG	       ; Tell the current DataPath

		   PUSH   DS
		   PUSH   ES
		   POP	  DS		       ; POINT TO DATAPATH
		   PRINT  DATAPATH
		   POP	  DS

		   PRINT  CRLF2
		   CMP	  HELP,TRUE	       ;			  1.2
		   JNE	  DISPLAY_6	       ;			  1.2
		   PRINT  USAGE 	       ;			  1.2

DISPLAY_6:	   RET

DISPLAY 	   ENDP
		   PAGE

INFO		   DB	  'DataPath version 1.6  $'                      ; 1.5
INSTALLED	   DB	  'Installed$'
NC		   DB	  '(nc) 1987 by GDC Software$'
VERBOSE_MSG	   DB	  'Verbose mode is $'
WRITE_ACCESS	   DB	  'Write access is $'
ON		   DB	  'on',CR,LF,'$'                                 ; 1.2
OFF		   DB	  'off',CR,LF,'$'                                ; 1.2
DATAPATH_MSG	   DB	  'Current DataPath=$'
CRLF2		   DB	  CR,LF
CRLF		   DB	  CR,LF,'$'
USAGE		   DB	  'DataPath usage:',CR,LF
		   DB	  '  DataPath path1 [;path2...] [/V] [/Q] [/R] [/W]'
		   DB	  ' [/C] [/H]',CR,LF                              ; 1.5
		   DB	  '    where path1, path2... are the paths to '
		   DB	  'search for files',CR,LF
		   DB	  '          /V sets verbose mode [default]',CR,LF
		   DB	  '          /Q sets quiet mode',CR,LF
		   DB	  '          /R sets read only mode [default]',CR,LF
		   DB	  '          /W allows opens for writing',CR,LF
		   DB	  '          /C clears the DataPath',CR,LF        ; 1.5
		   DB	  '          /H writes this message',CR,LF,'$'

		   .XLIST
		   SUBTITLE <Check Switches Subroutine>
; This procedure checks for a /W, /R, /V, /C, and /Q and sets the VERBOSE,
; WRITE_ENABLE, and CLEAR_FLAG flags accordingly.

CHECK_SWITCHES	   PROC   NEAR

		   CMP	  RESIDENT_FLAG,TRUE   ; If resident ES is set to
		   JE	  SWITCH_1	       ; resident portion of DataPath
		   PUSH   CS		       ; otherwise use CS
		   POP	  ES

SWITCH_1:	   CLD			       ; Scan for a '/'
		   MOV	  AL,'/'
		   XOR	  CX,CX
		   MOV	  CL,DP_LENGTH
		   MOV	  DI,OFFSET DATAPATH
		   PUSH   ES
		   PUSH   DS		       ; Set ES to point to us
		   POP	  ES
		   REPNZ  SCASB
		   POP	  ES		       ; Restore ES
		   JCXZ   SWITCH_9	       ; All done -- return

		   MOV	  AL,[DI]	       ; Get the character after the /
		   AND	  AL,0DFH	       ; Make upper case
		   MOV	  WORD PTR[DI-1],'  '  ; Blank out /x

		   CMP	  AL,'V'               ; /V --> set VERBOSE
		   JNE	  SWITCH_2
		   MOV	  ES:VERBOSE,TRUE
		   JMP	  SHORT SWITCH_1

SWITCH_2:	   CMP	  AL,'Q'               ; /Q --> clear VERBOSE
		   JNE	  SWITCH_3
		   MOV	  ES:VERBOSE,FALSE
		   JMP	  SHORT SWITCH_1

SWITCH_3:	   CMP	  AL,'R'               ; /R --> Only work on files
		   JNE	  SWITCH_4	       ;	opened to read
		   MOV	  ES:WRITE_ENABLE,FALSE
		   JMP	  SHORT SWITCH_1

SWITCH_4:	   CMP	  AL,'W'               ; /W --> Work on files for
		   JNE	  SWITCH_5	       ;	read or write
		   MOV	  ES:WRITE_ENABLE,TRUE
		   JMP	  SHORT SWITCH_1

SWITCH_5:	   CMP	  AL,'H'               ; /H --> Type usage message 1.2
		   JNE	  SWITCH_6
		   MOV	  HELP,TRUE
		   JMP	  SHORT SWITCH_1

SWITCH_6:	   CMP	  AL,'C'               ; /C --> Clear DataPath     1.5
		   JNE	  SWITCH_8
		   MOV	  CLEAR_FLAG,TRUE
		   JMP	  SHORT SWITCH_1

SWITCH_8:	   PRINT  OPTION_ERROR	       ; Bad switch.  Say so and give
		   MOV	  HELP,TRUE	       ;   help

SWITCH_9:	   RET

OPTION_ERROR	   DB	  BEL,'SWITCH ERROR',CR,LF,'$'

CHECK_SWITCHES	   ENDP

		   .XLIST
		   SUBTITLE <Squeeze Path Subroutine>
; This procedure removes non-printing and illegal characters from the passed
; DataPath

SQUEEZE_PATH	   PROC   NEAR
		   PUSH   ES		       ; Save pointer
		   PUSH   DS		       ; Make sure we point to local
		   POP	  ES		       ;    DataPath
		   MOV	  AH,0
		   MOV	  CL,DP_LENGTH	       ; Set length of command tail
		   MOV	  CH,AH
		   MOV	  SI,OFFSET DATAPATH   ; Point to command tail
		   MOV	  DI,SI
		   JCXZ   SQZ_3 	       ; If length is zero, return

SQZ_1:		   LODSB		       ; Get a character
		   CMP	  AL,' '               ; Check if non-printing
		   JLE	  SQZ_2 	       ; Skip if it is
		   CALL   CHECK_ILLEGAL        ; Check for illegal pathname chars
		   JC	  SQZ_2 	       ; Skip those too
		   INC	  AH		       ; Char OK, count it
		   STOSB		       ; Save it
SQZ_2:		   LOOP   SQZ_1 	       ; Work through the string
		   MOV	  DP_LENGTH,AH	       ; Save the new length

SQZ_3:		   POP	  ES		       ; Restore ES
		   RET

SQUEEZE_PATH	   ENDP

CSEG		   ENDS
		   END	  START
