Metropoli BBS
VIEWER: njmove.asm MODE: TEXT (ASCII)
        page    60,150
        title   NJMOVE - Nifty James' MOVE Utility
        subttl  (C) 1989 Blaszczak

;
; Nifty James' Famous Move Utility
; Version 1.00 of 16 January 1989
; (C) Copyright 1989 By Mike Blaszczak
;

;
; This file is written for the Microsoft Macro Assembler,
; Version 5.10 or higher.  The program will not properly
; operate under any DOS version less than 2.00, but will
; work with any PC-DOS or MS-DOS Versions 2.00 and above.
; This is an .EXE file, and should not be run through
; EXE2BIN.  The executable file should be packed, or the
; LINK option /EXEPACK should be used.
;


; ----------------------------------------------------------------------------
;       ASCII Equates

eos     equ     000h            ; end-of-string character
bell    equ     007h            ; bell beeper
bs      equ     008h            ; back-space
tab     equ     009h            ; tab
lf      equ     00Ah            ; line-feed character
cr      equ     00Dh            ; carriage return
ctrlz   equ     01Ah            ; control zee (aka, EOF)
eof     equ     ctrlz
space   equ     020h            ; blank space


; ----------------------------------------------------------------------------
;       DOS Function Calls and constants

INTDOS  equ     021h

STDIN   equ     0               ; standard output file handle
STDOUT  equ     1               ; standard input file handle

NormalAttrib    equ     000h            ; normal attribute byte (nothing set)
SubDirAttrib    equ     010h            ; attribute for a directory

PutChar equ     002h            ; put character to stdout
GetChar equ     007h            ; get char from stdin, with no echo
GetDefDisk      equ     019h            ; get current default disk drive
SetDTA  equ     01Ah            ; set the DOS DTA address
FreeSpace       equ     036h            ; get disk free space
CreateHandle    equ     03Ch            ; create a file handle
OpenHandle      equ     03Dh            ; open a file handle
CloseHandle     equ     03Eh            ; close a file handle
ReadHandle      equ     03Fh            ; read from a file or device
WriteHandle     equ     040h            ; write to a file or device
DeleteFile      equ     041h            ; delete a file name
GetAttrib       equ     04300h          ; get file attributes
SetAttrib       equ     04301h          ; set file attributes
IOCTL   equ     044h            ; I/O control/status call
GetDefDir       equ     047h            ; get current default directory
Terminate       equ     04Ch            ; terminate this program
FindFirst       equ     04Eh            ; find first file matching wildcard
FindNext        equ     04Fh            ; find next file matching wildcard
RenameFile      equ     056h            ; rename a file
GetDateTime     equ     05700h          ; get date or time
SetDateTime     equ     05701h          ; set date or time
NormalizePath   equ     060h            ; normalize pathname
GetPSP  equ     062h            ; get the programs PSP

; note that NormalizePath is an undocumented call.  DS:SI points to a
; path specification which may contain a drive specifier.  ES:DI points
; to a buffer for the normalized pathname.  The file simply fixes the
; "." and ".." references in the file, making the pathname as short as
; possible.  This expansion is simply used so the user knows *exactly*
; where the file is coming from.  Microsoft, in their infinite (heh)
; wisdom (hah) did not document this very useful function call.


; ----------------------------------------------------------------------------
;       ERRORLEVEL values that we return

ERR_None        equ     0               ; none!
ERR_Usage       equ     1               ; problem on command line
ERR_NotFound    equ     2               ; file not found
ERR_BadFile     equ     3               ; given a device, or an unparsable
ERR_CopyProblem equ     4               ; problem while copying
ERR_Renaming    equ     5               ; problem while renaming


; These are the data structures used in this program.  The first is used
; internally to describe the way a file name is broken down into its
; components.

FileDriveLen    equ     3
FilePathLen     equ     65
FileNameLen     equ     9
FileExtLen      equ     5

FileNameS       struc
drive   db      FileDriveLen dup(?)     ; drive         B:
path    db      FilePathLen  dup(?)     ; path          \bin\booger
fname   db      FileNameLen  dup(?)     ; filename      njmove
fext    db      FileExtLen   dup(?)     ; extension     asm
FileNameS       ends


; This data structure is the one that DOS uses to store the DTA.  When
; we call FINDFIRST and FINDNEXT, this information will be set up to contain
; the rest of the file that we are moving.

DOSDTAS struc
_dta_reserved   db      21 dup (?)      ; reserved space
dta_attribute   db      ?               ; attribute byte
dta_time        dw      ?               ; file modification time
dta_date        dw      ?               ; file modification date
dta_size        dd      ?               ; file size (doubleword!)
dta_fname       db      13 dup (?)      ; file name (ASCIIZ)
DOSDTAS ends


; ============================================================================
        .model  small
        .stack  0100h           ; get a *real* stack


; ============================================================================
; Our data area follows.  This data area contains both initialized and
; uninitialized data.

        .data

Banner  db      "Nifty James' Famous Move Utility",cr,lf
        db      "Version 1.00 of 16 January 1989",cr,lf
        db      "(C)Copyright 1989 by Mike Blaszczak",cr,lf
        db      lf,eos

NoSpace db      "NJMOVE: Destination device ran out of space"
CRLFSet db      cr,lf,eos

NoSelf  db      "NJMOVE: A File can not be moved into itself"
        db      cr,lf,eos

CantWrite       db      "NJMOVE: Couldn't write to destination device"
        db      cr,lf,eos

CantRead        db      "NJMOVE: Couldn't read from source device"
        db      cr,lf,eos

CantSDevice     db      "NJMOVE: Can't move from a device"
        db      cr,lf,eos

CantDDevice     db      "NJMOVE: Can't move to a device"
        db      cr,lf,eos

BadSource       db      "NJMOVE: Bad Source File Specification"
        db      cr,lf,eos

BadDest db      "NJMOVE: Bad Destination File Specification"
        db      cr,lf,eos

CouldntRename   db      "NJMOVE: Could not perform rename operation"
        db      cr,lf,eos

NameCollide     db      "NJMOVE: File naming collision; file already exists"
        db      cr,lf,eos

CouldntDIR      db      "NJMOVE: Couldn't get default directory of drive "
        db      eos

NoMatches       db      "NJMOVE: No files matched ",eos

UAborted        db      cr,lf,"NJMOVE: User Aborted",cr,lf,lf,eos

Usage   db      "Usage:",cr,lf
        db      tab,"NJMOVE <source> <dest> [/C]",cr,lf
        db      lf
        db      tab,"<source>",tab,"source file name",cr,lf
        db      tab,"<dest>",tab,tab,"destination file name",cr,lf
        db      tab,tab," both file names may "
        db      "contain wildcards",cr,lf
        db      tab,"[/C]",tab,"specifies optional confirmation"
        db      cr,lf,lf,eos

MovingMess      db      "Moving ",eos
MoveCMess       db      "Move ",eos

CopyingMess     db      "Copying ",eos
CopyCMess       db      "Copy ",eos

ToMess  db      " to ",eos

BackSlash       db      "\",eos

DotStar db      "."
Star    db      "*",eos

ConfirmPrompt   db      "? [No] ",eos


confirmmode     db      0               ; set if the /C was to be found
copymode        db      0               ; set to one if we must copy
                                ;  from disk to disk instead of
                                ;  just renaming the files
        even

sourcespec      dw      0               ; source file specification
destspec        dw      0               ; destination spec

sourcewildf     db      0               ; set if sourcef has wildcard name
sourcewilde     db      0               ; set if sourcef has wildcard ext
sourcehase      db      0               ; set if sourcef has extension
sourcef FileNameS <>            ; source description
sourcefile      db      64 dup (0)      ; total file name

destwildf       db      0               ; set if destf has wildcard name
destwilde       db      0               ; set if destf has wildcard ext
desthase        db      0               ; set if destf has extension
destf   FileNameS <>            ; destination description
destfile        db      64 dup (0)      ; total file name

mydta   DOSDTAS <>              ; a place for DOS to store its stuff
                                ; during FINDFIRST + FINDNEXT

; These variables are used by the copyfile procedure

sourceh dw      ?               ; handle for source file
desth   dw      ?               ; destination file handle

; ----------------------------------------------------------------------------
; These areas are all data buffers that are not initialized, generally.

        even

argvectors      dw      64 dup (0)      ; pointers to each argumnet
argcount        dw      0               ; count of arguments
argbuff db      128 dup (0)     ; buffer to hold args for DS access

CopyBufferLen   equ     16384
copybuffer      db      CopyBufferLen dup (?)
                                ; this buffer is used while copying

temppath        db      FilePathLen dup(?)
                                ; area used by check4path and
                                ; normalizepath

fixedpath       db      FilePathLen dup(?)
                                ; area used by normalize path, too.

; This area is used by the confirm procedure

confirmreply    db      ?


; ============================================================================
; This is the program code.  The execution of the program starts at the
; very first instruction here.

        .code

        ; first, put the banner of our program on the screen

        mov     ax,@data
        mov     ds,ax
        mov     si,offset Banner
        call    putstr

        ; set the DTA to point to our area

        mov     ah,SetDTA
        mov     dx,offset mydta
        int     INTDOS

        ; then, parse the command line

        call    parseline

        ; convert all of the arguments to upper case

        mov     ax,@data
        mov     ds,ax

        mov     di,offset argvectors

arguploop:      mov     si,[di]         ; get the next arg
        add     di,2

        and     si,si           ; is it 0000?
        je      doneargup

        call    strupr
        jmp     short arguploop

doneargup:
        ; then, find the source file and dest file (maybe),
        ; and check for options, too


        mov     di,offset argvectors
checkargsloop:
        mov     si,[di]         ; get the next arg
        add     di,2

        and     si,si           ; end of list?
        je      donecheckargs

        mov     al,[si]         ; nope!  check it
        cmp     al,'/'
        je      gotoption
        cmp     al,'-'
        jne     notoption

gotoption:      inc     si              ; get the next character of option
        mov     al,[si]
        cmp     al,'C'          ; confirm?
        jne     showusage       ; nope! error!

        mov     al,1
        mov     confirmmode,al  ; confirmation mode
        jmp     short checkargsloop

notoption:      mov     ax,sourcespec   ; do we have a source file?
        and     ax,ax
        jne     gotsource

        mov     sourcespec,si   ; nope!  this is our new source spec
        jmp     short checkargsloop

gotsource:      mov     ax,destspec     ; how about a dest?
        and     ax,ax
        jne     showusage

        mov     destspec,si     ; nope!  this is the dest spec
        jmp     short checkargsloop

        ; now, see if we have enough arugments
donecheckargs:
        mov     ax,argcount     ; did we get any args?
        and     ax,ax
        jne     noshowusage     ; if not, we must

showusage:
        mov     si,offset Usage ; display the usage
        call    putstr
        mov     ah,Terminate    ; and then
        mov     al,ERR_Usage    ; terminate with error status
        call    exit

noshowusage:
        cmp     ax,1
        ja      gotdest         ; was there a destination filespec?

nogotdest:
        mov     si,offset Star  ; nope. asume dest filespec was
        mov     di,offset destf.fname   ; *.*
        call    strcpy
        mov     si,offset DotStar
        mov     di,offset destf.fext
        call    strcpy
        jmp     short destsetup

gotdest:        ; yes!  call splitfilename to handle
        mov     si,destspec     ; the parse of the filename
        mov     di,offset destf ; into the destf area
        call    splitfilename

destsetup:
        ; before we goof with anything, see if the
        ; destination has a file extension

        mov     si,offset destf.fext
        call    strlen
        and     cx,cx           ; something there?
        je      destno

        mov     al,1
        mov     desthase,al     ; yes! flag it!


destno: mov     si,offset destf.drive
        call    strlen          ; was there a drive name given?
        and     cx,cx
        jne     gotdestdrive

        mov     ah,GetDefDisk           ; nope-get default from DOS
        int     INTDOS
        add     al,'A'                  ; make it ASCII
        mov     [si],al
        inc     si                      ; store it
        mov     byte ptr [si],':'       ; add a colon
        inc     si
        mov     byte ptr [si],eos       ; and a zilcher

gotdestdrive:
        mov     si,offset destf.path
        call    strlen          ; was there a file path?
        and     cx,cx
        jne     gotdestpath

        ; put the default file path into the destf area
        ; first, put a backslash in there

        mov     di,offset destf.path
        mov     si,offset BackSlash
        call    strcpy

        ; then ask DOS to put the default path into the
        ; copybuffer area

        mov     si,offset copybuffer
        mov     ah,GetDefDir
        mov     dl,[destf.drive]        ; whats the drive?
        sub     dl,('A'-1)
        int     INTDOS
        jnc     nodestdirproblem

        call    newline                 ; print error message
        mov     si,offset CouldntDIR
        call    putstr
        add     dl,('A'-1)              ; for that drive
        mov     ah,PutChar
        int     INTDOS
        call    newline
        mov     al,ERR_BadFile
        call    exit

nodestdirproblem:       ; now, concatenate it together.  we will end up with
        ; \path\place

        mov     si,offset copybuffer
        mov     di,offset destf.path
        call    strcat

        ; if the path is not just \, add a trailing \ to it

        mov     si,offset copybuffer
        call    strlen
        cmp     cx,1
        jb      gotdestpath

        mov     di,offset destf.path
        mov     si,offset BackSlash
        call    strcat

gotdestpath:
        ; now, we'll see if the filename is there

        mov     si,offset destf.fname
        call    strlen
        mov     bx,cx

        mov     si,offset destf.fext
        call    strlen          ; now, cx = strlen(fext)
                                ;   and bx = strlen(fname)

        and     cx,cx           ; is cx zero?
        je      destnoproblem

        and     bx,bx
        jne     destnoproblem

        ; we've found that there is no filename but there is an extension.
        ; that's not too good.

        mov     si,offset BadDest
        call    putstr
        mov     al,ERR_BadFile
        call    exit

destnoproblem:
        and     bx,bx           ; does filename exist?
        jne     desthasfn

        mov     si,offset Star  ; nope ... copy a star there
        mov     di,offset destf.fname
        call    strcpy

desthasfn:
        and     cx,cx           ; okay, does the extension exist
        jne     destfixed

        mov     si,offset DotStar; nope, copy ".*" into it
        mov     di,offset destf.fext
        call    strcpy

destfixed:
        ; now, check to see if there's a wildcard in the filename
        mov     si,offset destf.fname
        mov     ah,'?'
        call    strchr
        and     ax,ax           ; is it?
        jne     desthaswildf

        mov     si,offset destf.fname
        mov     ah,'*'          ; how about a star?
        call    strchr
        and     ax,ax
        je      destnowildf

desthaswildf:
        mov     al,1
        mov     destwildf,al    ; set the flag

destnowildf:
        ; see if the destination extension has a wildcard
        mov     si,offset destf.fext
        mov     ah,'?'
        call    strchr
        and     ax,ax           ; is it?
        jne     desthaswilde

        mov     si,offset destf.fext
        mov     ah,'*'
        call    strchr
        and     ax,ax
        je      destdone

desthaswilde:
        mov     al,1
        mov     destwilde,al

destdone:       ; normalize the path for the destf

        mov     si,offset destf
        call    normpath

        ; does the filename have a wildcard?

        mov     al,destwildf
        and     al,al
        jne     destcompleted

        ; does it have an extension?

        mov     al,desthase
        and     al,al
        jne     destcompleted

        ; if not, check to see if the filename is a directory

        mov     si,offset destf
        call    check4dir
        and     ax,ax           ; did it work?
        je      destcompleted

        mov     destwilde,al    ; yes! extension and filename
        mov     destwildf,al    ; are wildcards now

destcompleted:  ; the destination file is all set up!
        ; now, we can parse the source file name

        mov     si,sourcespec
        mov     di,offset sourcef
        call    splitfilename

        ; before we touch anything, see if we were given
        ; a file extension

        mov     si,offset sourcef.fext
        call    strlen
        and     cx,cx           ; anything there?
        je      sourceno

        mov     al,1
        mov     sourcehase,al   ; yes! flag it


sourceno:       ; check 'n' see if there is a drive there

        mov     si,offset sourcef.drive
        call    strlen
        and     cx,cx
        jne     gotsourcedrive

        mov     ah,GetDefDisk           ; nope-get default from DOS
        int     INTDOS
        add     al,'A'
        mov     [si],al
        inc     si
        mov     byte ptr [si],':'       ; and store a colon after it
        inc     si
        mov     byte ptr [si],eos       ; and a zilcher around it

gotsourcedrive:
        mov     si,offset sourcef.path
        call    strlen          ; was there a file path?
        and     cx,cx
        jne     gotsourcepath

        ; put the default file path into the sourcef area
        ; first, put a backslash in there

        mov     si,offset BackSlash
        mov     di,offset sourcef.path
        call    strcpy

        ; then ask DOS to put the default path into the
        ; copy buffer area

        mov     si,offset copybuffer
        mov     ah,GetDefDir
        mov     dl,[sourcef.drive]      ; whats the drive?
        sub     dl,('A'-1)      ; make into A=1, B=2 ... for DOS
        int     INTDOS
        jnc     nosourcedirprob

        call    newline                 ; print error message
        mov     si,offset CouldntDIR
        call    putstr
        add     dl,('A'-1)              ; for that drive
        mov     ah,PutChar
        int     INTDOS
        call    newline
        mov     al,ERR_BadFile
        call    exit

nosourcedirprob:        ; now, concatenate it togethre

        mov     si,offset copybuffer
        mov     di,offset sourcef.path
        call    strcat

        ; if the path is not just \, add a trailing \ to it
        mov     si,offset copybuffer
        call    strlen
        cmp     cx,1
        jb      gotsourcepath

        mov     di,offset sourcef.path
        mov     si,offset BackSlash
        call    strcat

gotsourcepath:
        ; make sure there's a filename, even if its a wildcard.
        ; check that it was a valid filename, too.

        mov     si,offset sourcef.fname
        call    strlen
        mov     bx,cx

        mov     si,offset sourcef.fext
        call    strlen          ; now, cx = strlen(fext)
                                ;       and bx = strlen(fname)

        and     cx,cx
        je      sourcenoproblem

        and     bx,bx
        jne     sourcenoproblem

        ; we've found that there is no filename but there is an extension
        ; that's not good

        mov     si,offset BadSource
        call    putstr
        mov     al,ERR_BadFile
        call    exit

sourcenoproblem:
        and     bx,bx           ; does filename exist?
        jne     sourcehasf

        mov     si,offset Star  ; nope ... copy a astar in there
        mov     di,offset sourcef.fname
        call    strcpy

sourcehasf:
        and     cx,cx           ; does extension exist?
        jne     sourcedone
        mov     si,offset DotStar
        mov     di,offset sourcef.fext
        call    strcpy

        ; now, check to see if there's a wildcard in the filename
        mov     si,offset sourcef.fname
        mov     ah,'?'
        call    strchr
        and     ax,ax           ; is it?
        jne     sourcehaswildf

        mov     si,offset sourcef.fname
        mov     ah,'*'          ; how about a star?
        call    strchr
        and     ax,ax
        je      sourcenowildf

sourcehaswildf:
        mov     al,1
        mov     sourcewildf,al  ; set the flag

sourcenowildf:
        ; see if the sourceination extension has a wildcard
        mov     si,offset sourcef.fext
        mov     ah,'?'
        call    strchr
        and     ax,ax           ; is it?
        jne     sourcehaswilde

        mov     si,offset sourcef.fext
        mov     ah,'*'
        call    strchr
        and     ax,ax
        je      sourcedone

sourcehaswilde:
        mov     al,1
        mov     sourcewilde,al

sourcedone:     ; last, normalize the source file path

        mov     si,offset sourcef
        call    normpath

        mov     al,sourcewildf
        and     al,al
        jne     sourcecompleted

        mov     al,sourcehase
        and     al,al
        jne     sourcecompleted

        mov     si,offset sourcef
        call    check4dir
        and     ax,ax           ; did it work?
        je      sourcecompleted

        mov     sourcewildf,al  ; yes! filename and
        mov     sourcewilde,al  ; extension are now wildcards!


sourcecompleted:        ; filenames are all set!
        ; okay, almost there!  check to see if the drives
        ; are the same.  if they are, we're renaming.  if
        ; they're not, we're copying and deleting.

        mov     si,offset sourcef.drive
        mov     al,[si]
        mov     si,offset destf.drive
        cmp     al,[si]
        je      workhorse

        mov     al,1            ; set copying flag
        mov     copymode,al

        ; right! everything is ready.  we'll make the source
        ; file name into something meaningful

workhorse:      mov     si,offset sourcef
        mov     di,offset sourcefile
        call    makefilename

        ; just for kicks, we'll try to open that file.

        mov     ah,OpenHandle
        mov     al,0
        mov     dx,offset sourcefile
        int     INTDOS

        jc      dothefindfirst

        mov     bx,ax
        mov     ah,IOCTL
        mov     al,0            ; see if it is a device
        int     INTDOS

        and     dx,0080h        ; check ISDEV bit
        je      notdevice

deviceblowout:  mov     si,offset CantSDevice
        call    putstr
        mov     ax,ERR_BadFile
        call    exit


notdevice:      mov     ah,CloseHandle
        int     INTDOS


        ; we'll try to find the first matching file

dothefindfirst: mov     ah,FindFirst
        mov     dx,offset sourcefile
        xor     cx,cx
        int     INTDOS

        jnc     foundone

notfound:       mov     si,offset NoMatches
        call    putstr
        mov     si,offset sourcefile
        call    putstr
        call    newline
        mov     ax,ERR_NotFound
        call    exit

        ; this loop is the main thing.  we've called findfirst,
        ; and the dta block is set up with the file we found.
        ; we'll figure out the destination file name, and
        ; then move or copy the file.

        ; at the bottom of the loop, we'll call findnext and
        ; see if there are more matching files.  if its there,
        ; we can loop back here and process that next file.

foundone:       mov     si,offset mydta
        mov     di,offset sourcef
        call    popfound
        mov     si,offset sourcef
        mov     di,offset sourcefile
        call    makefilename

        ; if the source filename has a wildcard *and* the
        ; dest filename has a wild card, copy source filename
        ; into the destination filename

        mov     al,destwildf
        and     al,al
        je      notwildf

        mov     si,offset sourcef.fname
        mov     di,offset destf.fname
        call    strcpy


notwildf:       ; if the source file extension has a wildcard,  and
        ; the destination extension has a wildcard, copy
        ; the source extension into the destination extension

        mov     al,destwilde
        and     al,al
        je      notwilde

        mov     si,offset sourcef.fext
        mov     di,offset destf.fext
        call    strcpy


notwilde:       ; create the destination file into an ASCIIZ string

        mov     si,offset destf
        mov     di,offset destfile
        call    makefilename

        ; print a message for what we are going to do

        mov     al,copymode
        mov     ah,confirmmode

        and     al,al
        je      wearemoving

        mov     si,offset CopyingMess   ; we're copying

        and     ah,ah
        je      printsource             ; without confirmation

        mov     si,offset CopyCMess
        jne     printsource

wearemoving:    mov     si,offset MovingMess    ; we're moving
        and     ah,ah
        je      printsource             ; without confirmation?

        mov     si,offset MoveCMess


printsource:    call    putstr

        ; either
        ;   "Moving sourcefile to destfile"
        ; or
        ;   "Copying sourcefile to destfile"

        mov     si,offset sourcefile
        call    putstr

whereto:        ; print " to "

        mov     si,offset ToMess
        call    putstr

        mov     si,offset destfile
        call    putstr


        ; before we actually do it, call confirmation, if need be

        and     ah,ah
        jne     askfirst        ; no confirmation

        call    newline
        jmp     short goahead

askfirst:       call    getyesno        ; do confirmation
        call    newline

        cmp     ax,0            ; no?
        je      loopbottom      ; skip it, then

        cmp     ax,1            ; yes?
        je      goahead         ; go right ahead

        xor     ax,ax           ; all of them!
        mov     confirmmode,al  ; (forget about confirming)

        ; actually do it

goahead:        mov     al,copymode
        and     al,al
        jne     doacopy

doamove:
        mov     dx,offset sourcefile
        mov     di,offset destfile
        mov     ah,RenameFile
        int     INTDOS
        jnc     loopbottom

        ; decide which error happened.  AX=0005 is bad name,
        ; anything else is different.

        mov     bx,ax
        mov     al,ERR_Renaming

        mov     si,offset NameCollide
        cmp     bx,5
        je      renamingcollision

        mov     si,offset CouldntRename

renamingcollision:      call    newline
        call    putstr
        mov     ah,ERR_Renaming
        call    exit

doacopy:        call    copyfile

loopbottom:     mov     ah,FindNext
        int     INTDOS
        jc      cleanexit       ; relative jnc out of range
        jmp     foundone




cleanexit:
        mov     al,ERR_None     ; return with no error
        call    exit



; ============================================================================
; These subroutines are used by the programs' main routine


; ----------------------------------------------------------------------------
; exit
; On entry, AL contains the error level that the program should return.
;
; This routine will terminate to DOS.  It does no housekeeping functions.

exit    proc    near

        mov     ah,Terminate    ; make a Terminate call
        int     INTDOS

exit    endp


; ----------------------------------------------------------------------------
; strlen
; On entry, DS:SI points to a string.
;
; On return, CX will contain the length of the string.

strlen  proc    near

        push    ax
        push    si

        xor     cx,cx           ; zero the length count

sl_c:   mov     al,[si]         ; get a character
        inc     si
        inc     cx              ; increment pointer and count
        and     al,al           ; is it end of string?
        jne     sl_c            ;  loop back if it isn't

        dec     cx              ; adjust the count down

        pop     si
        pop     ax
        ret

strlen  endp



; ----------------------------------------------------------------------------
; newline
; This routine prints a carriage return and a linefeed.  We will destroy
; no registers.


newline proc    near

        push    si
        push    ds              ; save the DS:SI
        push    ax

        mov     ax,@data
        mov     ds,ax
        mov     si,offset CRLFSet
        call    putstr

        pop     ax              ; restore the registers
        pop     ds
        pop     si
        ret

newline endp


; ----------------------------------------------------------------------------
; putstr
; On entry, DS:SI points to a string.
;
; This routine prints the passed string to STDOUT, and does nothing else.
; The string is not altered in any way.

putstr  proc    near

        push    dx
        push    cx
        push    bx
        push    ax              ; save the regs we use
        push    si

        xor     cx,cx           ; zero the length count

ps_l:   mov     al,[si]         ; get a character
        inc     si
        inc     cx              ; increment pointer and count
        and     al,al           ; is it end of string?
        jne     ps_l            ;  loop back if it isn't

        dec     cx              ; adjust the count down

        mov     ah,WriteHandle  ; ask DOS to write to the stdout
        mov     bx,STDOUT

        pop     si              ; restore the SI
        mov     dx,si           ; and put it into dx for DOS
        int     INTDOS

        pop     ax
        pop     bx
        pop     cx
        pop     dx
        ret

putstr  endp


; ----------------------------------------------------------------------------
; strcat
; On entry, DS:SI points to the concatenant, and ES:DI points to the
; concatenand:
;
;       DS:SI -> "Have Antlers"
;       ES:DI -> "Mooses "
;       call    strcat
;       ES:DI -> "Mooses Have Antlers"


strcat  proc    near

        push    ax
        push    si              ; save the registers
        push    di

        ; first, find the end

sc_next:        mov     al,es:[di]      ; get the char
        inc     di
        and     al,al           ; is it zilcher?
        jne     sc_next         ; nope ... keep looping

        dec     di              ; point to the zilcher

        ; now, we can just call strcpy to move the concatenant
        ; over the end of the concatenand.

        call    strcpy

        pop     di
        pop     si
        pop     ax

        ret
strcat  endp


; ----------------------------------------------------------------------------
; strcpy
; On entry, DS:SI points to a string and ES:DI points to storage space.
;
; This routine will copy the string from DS:SI to ES:DI.

strcpy  proc    near

        push    di              ; save the registers we use
        push    si
        push    ax

sc_nextc:       mov     al,[si]         ; get another character
        inc     si
        mov     es:[di],al      ; store it
        inc     di

        and     al,al           ; if that was the 0 character, quit
        jne     sc_nextc        ; else keep working on the string

        pop     ax              ; restore the registers we used
        pop     si
        pop     di
        ret
strcpy  endp


; ----------------------------------------------------------------------------
; strchr
; On entry, DS:SI points to a string, and ah contains a character to match.
; The routine will work forward through the string until it finds the end
; of the string or matches the character.  On return, AX will be 0000 if
; there was no match, or DS:AX will point to the character in the string
; if the character wasn't found.

strchr  proc    near

        push    si              ; save si

strchrnc:       mov     al,[si]         ; get a character
        inc     si              ; and increment pointer
        cmp     al,ah           ; is it a match?
        je      strchrhit

        and     al,al           ; nope.  was it '\0'?
        jne     strchrnc        ; if not, go do more

        xor     ax,ax           ; yes, the string ended
        je      strchrexit      ; so make AX null for failure

strchrhit:      mov     ax,si           ; got the character!
        dec     ax              ; put pointer into ax

strchrexit:     pop     si              ; restore si
        ret                     ; and go home

strchr  endp


; ----------------------------------------------------------------------------
; strrchr
; On entry, DS:SI points to a string, and ah contains a character to match.
; The routine will step backward, starting at the end of the string, and
; work until it finds a match.  If the program makes it through the whole
; string without finding the character, it will return with AX set to 0000.
; Otherwise, DS:AX points the the last occurrence of the character in the
; string.


strrchr proc    near

        push    cx              ; save the registers we use
        push    si
        xor     cx,cx           ; zero cx since we use it to count

strrchrfe:      mov     al,[si]         ; get the next character
        inc     si              ; increment the pointer
        inc     cx              ; increment the count of chars
        and     al,al           ; is this the end of the string?
        jne     strrchrfe

        cmp     cx,0            ; is the string empty?
        je      strrchrfail     ; if so, automatic failure

strrchrloop:    dec     si              ; point to the null
        mov     al,[si]         ; is that the character?
        cmp     ah,al
        je      strrchrhit      ; if so, we've hit!
        dec     cx
        jne     strrchrloop     ; if not, loop more

strrchrfail:    xor     ax,ax
        je      strrchrexit     ; return AX as 0000

strrchrhit:     mov     ax,si           ; got the character! move pointer
                                ; into ax

strrchrexit:    pop     si
        pop     cx
        ret

strrchr endp


; ----------------------------------------------------------------------------
; strupr
; On entry, DS:SI points to a string and ES:DI points to storage space.
;
; This routine will convert the entire content of the string to uppercase.
; It won't alter any registers, nor will it change non-alphabetic characters
; in the str.


strupr  proc    near

        cld
        push    ax
        push    si

struprloop:     lodsb                   ; get the next character
        and     al,al           ; is it zilcho?
        je      struprdone

        cmp     al,'a'          ; is it less than 'a'?
        jl      struprloop      ; yep ... keep looping

        cmp     al,'z'          ; is it bigger than 'z'?
        jg      struprloop      ; yes; don't modify it

        sub     al,('a' - 'A')
        mov     [si-1],al       ; restore that character
        jmp     short struprloop

struprdone:     pop     si
        pop     ax
        ret

strupr  endp


; ----------------------------------------------------------------------------
; parseline
; This routine tries to parse the command line.  It sets up the variables
; in the data segment (argcount and arglist), as well as copying the
; information into a local area in the data segment.  The routine will
; not preserve any of the registers it uses.

parseline       proc    near

        mov     ah,GetPSP       ; get the PSP from DOS
        int     INTDOS

        mov     ds,bx           ; move the PSP into ds
        mov     si,0081h        ; address the command line

        xor     dx,dx
        mov     dl,[si-1]       ; get the length of the line
        add     dl,081h

        mov     ax,@data        ; now, set up ES to point to our
        mov     es,ax           ; own data areas
        mov     di,offset argbuff


nextarg:
eatwhite:
        mov     al,[si]
        inc     si              ; get the next character

        cmp     al,space        ; is it a space?
        je      eatwhite
        cmp     al,tab          ; or a tab?
        je      eatwhite        ; yes ... skip over it

        dec     si              ; point si at the first non-white
        push    si              ; and save a copy of it for later

eatarg: mov     al,[si]         ; get the last character
        inc     si
        cmp     al,space        ; is it a whitespace?
        jle     gotarg
        jmp     short eatarg    ; nope... keep skipping

gotarg:
        dec     si
        mov     byte ptr [si],0 ; make the argument a string

        pop     ax              ; get the pointer to this arg

        xchg    ax,si           ; put si into DS:SI

        call    strcpy          ; store in our data segment

        call    strlen          ; get the length of the string
        push    cx              ; save it

        xchg    si,ax

        and     cx,cx           ; if this string has no length,
        je      added           ;  don't add it to the table

        ; here, DS:AX points to the end of the string and
        ; ES:DI points to the place where the string was stored.
        ; DS:SI still points to the beginning of the string.

add2table:      mov     ax,es:argcount
        shl     ax,1            ; get offset into array
        add     ax,offset argvectors
        xchg    ax,si
        mov     es:[si],di      ; store the offset of this arg
        xchg    si,ax

        inc     es:argcount     ; increment the count of args

added:
        ; adjust ES:DI

        pop     cx
        add     di,cx           ; get the length and fix it in
        inc     di              ; make DS:SI point to the next
        inc     si              ; free spot

        ; see if we are done

        cmp     dx,si
        ja      nextarg

wearedone:
        ret
parseline       endp


; ----------------------------------------------------------------------------
; splitfilename
; this procedure accepts ds:si as a pointer to a full file name.  it also
; expects that es:di points to a FileNameS.  The routine will copy
; information from the full file name into the FileNameS structure.

splitfilename   proc    near

        push    cx
        push    si              ; save the regs we use
        push    di

        ; to get started, check to see if there's a drivespec

        mov     al,[si+1]       ; is there a colon for the
        cmp     al,':'          ; second character?
        jne     sfn_nodrive     ; nope! don't worry about it

sfn_gotdrive:   mov     es:[di+1].drive,al      ; store the colon
        mov     al,[si]                 ; get the drive letter
        mov     es:[di].drive,al
        add     di,2            ; make es:di point after it
        add     si,2            ; and make si point after drive


sfn_nodrive:    ; since there's no drive spec, just zero the drive.

        xor     ax,ax
        mov     es:[di].drive,al ; stick it


sfn_drivedone:  ; now that drive is done, point es:di to the path

        pop     di              ; point to the path area
        push    di
        add     di,path

        ; see if there was a path in the filename we got

        mov     al,[si]         ; get the first char
        cmp     al,'.'
        jne     sfn_notrelpath
        je      sfn_hasrelpath


sfn_notrelpath: mov     ah,'\'          ; the path character
        call    strchr

        and     ax,ax           ; is AX == 0000?
        jne     sfn_haspath

sfn_hasrelpath: mov     ax,si           ; yes -- point to the start of
                                ; the string

sfn_haspath:    ; there's a path!  now, try to find the *last*
        ; character of the path.

        mov     dx,ax           ; save AX for now
        mov     ah,'\'
        call    strrchr         ; find the last '\'
        xchg    ax,dx

        and     dx,dx
        jne     sfn_hasend

        ; hmph!  there is no '\', either way you look
        ; does the path begin with '.'?

        cmp     byte ptr [si],'.'
        jne     sfn_nopath      ; nope!  no path name
                                ; yes!  relative path name!


sfn_relpath:    call    strlen          ; if no '\', point to end of
        mov     dx,si           ;   string
        add     dx,cx

        cmp     ax,dx           ; is our end and start equal?
        je      sfn_nopath      ; no path here, mon!

        ; now, copy the string from DS:AX to DS:DX into ES:DI.

sfn_hasend:     mov     si,ax           ; start with ax
        mov     cx,FilePathLen-2

sfn_movepath:   mov     al,[si]
        inc     si              ; get this character
        mov     es:[di],al      ; and store it in path
        inc     di

        dec     cx              ; decrement max char count.
        jne     sfn_movingpath  ; if too many, truncate

        mov     al,'\'
        mov     es:[di],al      ; truncate with a backslash
        inc     di
        mov     si,dx           ; update the pointer so's it
        inc     si              ; points to the filename now
        jmp     short sfn_movedpath

sfn_movingpath:
        cmp     dx,si           ; is si>dx?  (work to dx)
        jae     sfn_movepath    ; no ... keep looping

sfn_movedpath:
sfn_nopath:     xor     ax,ax           ; set up the ax so we can
        mov     es:[di],al      ; store a null in .path area

        ; now, we have the path done.  mess with moving
        ; the file name and the extension. DS:SI already
        ; points to the file name.

sfn_pathdone:   pop     di              ; make es:di point to filename
        push    di
        add     di,fname

        mov     cx,FileNameLen

sfn_movefn:     mov     al,[si]         ; get the next character
        cmp     al,'.'          ; end of filename?
        je      sfn_gotext

        dec     cx              ; too many characters?
        jne     sfn_moveingfn   ; yep!  truncate.

        mov     al,0
        mov     es:[di],al      ; terminate the filename
sfn_blowoff:    mov     al,[si]         ; skip ahead until we find eos or
        cmp     al,'.'          ; a period
        je      sfn_gotext
        inc     si
        and     al,al           ; is it a zilcher?
        jne     sfn_blowoff
        je      sfn_movedfn

sfn_moveingfn:
        mov     es:[di],al      ; store this character
        inc     si
        inc     di
        and     al,al           ; did we just write an eos?
        jne     sfn_movefn

sfn_movedfn:    xor     ax,ax
        pop     di              ; make addressing to strucutre
        push    di
        mov     es:[di].fext,al
        jmp     short sfn_exit  ; and split this joint

sfn_gotext:     mov     ah,0            ; write an end-of-string for
        mov     es:[di],ah      ; the filename, first

        mov     cx,FileExtLen

        pop     di
        push    di
        add     di,fext         ; make es:di point to filename

sfn_moveext:    mov     es:[di],al      ; store the character
        inc     si
        inc     di              ; increment the pointers
        and     al,al           ; did we just write a '\0'?
        je      sfn_exit

        cmp     cx,0            ; is it zero yet?
        jne     snf_movingext

        mov     al,0
        dec     di
        mov     es:[di],al
        jmp     short sfn_exit

snf_movingext:  dec     cx
        mov     al,[si]         ; get next character
        jmp     short sfn_moveext

sfn_exit:
        ; on the way out, we will check to see if the path
        ; name was a relative file.  if so, we will make
        ; sure a slash is appended to it.

        pop     di
        push    di
        add     di,path

        mov     al,[di]
        cmp     al,'.'          ; not a relative, no problem
        jne     sfn_nofix

        ; fix relative path

        mov     si,di
        call    strlen
        add     si,cx
        dec     si              ; point to last character
        mov     al,[si]         ; get that character
        cmp     al,'\'          ; slash?
        je      sfn_nofix       ;   no problem

        inc     si              ; make it a slash
        mov     byte ptr [si],'\'
        inc     si
        mov     byte ptr [si],eos

sfn_nofix:      ; restore the regs we saved
        pop     di
        pop     si
        pop     cx
        ret
splitfilename   endp


; ----------------------------------------------------------------------------
; makefilename
; On entry, DS:SI points to a FileNameS, and ES:DI points to a storage
; buffer for a file name.  The routine will string all of the elements
; of the strucutre together to make a real full file name.

makefilename    proc    near

        push    ax
        push    si
        push    di
        mov     ax,si           ; put base pointer into ax

        ; first copy the drive specifier

        mov     si,ax
        add     si,drive        ; copy the drive
        call    strcpy

        ; then concatenate the path

        mov     si,ax
        add     si,path
        call    strcat

        ; then concatenate the filename

        mov     si,ax
        add     si,fname
        call    strcat

        ; now concatenate the extension

        mov     si,ax
        add     si,fext
        call    strcat

        ; that's all!

        pop     di
        pop     si      ; restore the registers and return home
        pop     ax
        ret
makefilename    endp


; ----------------------------------------------------------------------------
; popfound
; This routine accepts a pointer to a DOSDTAS in DS:DI, and a pointer to
; a FileNameS in ES:DI.  It takes the filename and extension fields from
; the DOSDTAS and puts them into the fname and fext portions of the
; FileNameS structure.

popfound        proc

        push    ax
        push    cx
        push    dx
        push    si      ; save the regs we use
        push    di

        mov     dx,si           ; and make a copy of the parms
        mov     cx,di           ; to easily address the structs

        ; first, work on moving the filename

        mov     si,dx
        add     si,dta_fname

        mov     di,cx
        add     di,fname

popfoundf:      mov     al,[si]         ; get this char
        cmp     al,'.'          ; period yet?
        je      popfounde
        mov     es:[di],al      ; store it

        and     al,al           ; is it a zilcher?
        jne     popfoundfing

        mov     di,cx           ; yep!  zilch out extension too
        add     di,fext
        mov     es:[di],al
        jmp     short popfounddone


popfoundfing:   inc     di
        inc     si
        jmp     short popfoundf ; loop for more

popfounde:      mov     ah,0
        mov     es:[di],ah      ; terminate the filename with 0
        mov     di,cx

        add     di,fext         ; point to the extension

popfoundw:      mov     es:[di],al
        inc     di
        inc     si
        and     al,al           ; is it zero?
        je      popfounddone    ; yes, quit!
        mov     al,[si]         ;(get next char)
        jne     popfoundw       ; nope ... keep working

popfounddone:
        pop     di
        pop     si              ; restore registers
        pop     dx
        pop     cx
        pop     ax
        ret
popfound        endp


; ----------------------------------------------------------------------------
; copyfile
; this procedure completely copies a file.  it copies the file in
; sourcefile to the file in destfile.  If there is an error during
; the copy, the destfile is automatically deleted and the program
; terminates via exit.  If the copy is successful, the source file
; is deleted.  The dest file will inherit the same file time and
; date stamp as the source file.

copyfile        proc    near

        ; first, try to open the dest file for reading

        mov     ah,OpenHandle           ; open the
        mov     dx,offset destfile      ; dest for
        mov     al,0                    ; reading
        int     INTDOS
        jc      cf_nameokay

        ; we could read the source file!  the file already
        ; exists, so we must abort.

        mov     bx,ax
        mov     ah,CloseHandle          ; close the handle and
        call    newline
        mov     si,offset NameCollide   ; print an error!
        call    putstr
        mov     ah,ERR_CopyProblem
        call    exit

cf_nameokay:    ; try to open the source file

        mov     ah,OpenHandle           ; open the
        mov     dx,offset sourcefile    ; source for
        mov     al,0                    ; for reading
        int     INTDOS

        ; was there an error?

        jnc     cf_noopenerr

cf_cantread:    call    newline                 ; couldn't open; print
        mov     si,offset CantRead      ; an error message
        call    putstr
        mov     ah,ERR_CopyProblem
        call    exit                    ; and terminate

cf_noopenerr:   mov     sourceh,ax              ; save the handle

        mov     ah,CreateHandle         ; open
        mov     dx,offset destfile      ; desination
        xor     cx,cx                   ; no attribute
        int     INTDOS

        jnc     cf_nowriteopen

cf_cantwrite:   call    newline                 ; couldn't write; print
        mov     si,offset CantWrite     ; an error message
        call    putstr
        mov     ah,ERR_CopyProblem
        call    exit                    ; and terminate!

cf_nowriteopen: mov     desth,ax

cf_nextbunch:   mov     ah,ReadHandle           ; read
        mov     cx,CopyBufferLen        ; all we can
        mov     bx,sourceh              ; from the source
        mov     dx,offset copybuffer    ; into the buffer
        int     INTDOS

        jnc     cf_canread              ; no read error?

        mov     dx,offset destfile      ; delete the dest file
        mov     ah,DeleteFile
        int     INTDOS
        jmp     short cf_cantread       ; print error and quit

cf_canread:
        and     ax,ax                   ; (Was it zero?)
        je      cf_closeup              ; yes! go close the files

        mov     cx,ax                   ; take what we read
        mov     ah,WriteHandle          ; and write it
        mov     dx,offset copybuffer    ; from the buffer
        mov     bx,desth                ; to the destination
        int     INTDOS

        jnc     cf_canwrite             ; write error!

        mov     dx,offset destfile      ; delete the dest file
        mov     ah,DeleteFile
        int     INTDOS
        jmp     short cf_cantwrite      ; print error and quit

cf_canwrite:
        cmp     cx,ax                   ; did we write 'em all?
        jne     cf_cantwrite            ; no?  that's a problem!
        je      cf_nextbunch

cf_closeup:
        mov     ax,GetDateTime          ; get the date
        mov     bx,sourceh              ; from the source file
        int     INTDOS

        mov     ax,SetDateTime          ; set the date
        mov     bx,desth                ; to the dest file
        int     INTDOS

        mov     bx,desth                ; close dest file
        mov     ah,CloseHandle
        int     INTDOS

        mov     bx,sourceh              ; close source file
        mov     ah,CloseHandle
        int     INTDOS

        ;       get the source file's attribute byte

        mov     dx,offset sourcefile
        mov     ax,GetAttrib
        int     INTDOS

        ;       set the dest file's attribute byte

        mov     dx,offset destfile
        mov     ax,SetAttrib            ; CX is already set
        or      cx,020h                 ; but set archive bit
        int     INTDOS

        ;       set the source file's attribute byte to normal

        mov     dx,offset sourcefile
        mov     ax,SetAttrib
        mov     cx,NormalAttrib         ; reset source file
                                        ; so we can now
        mov     dx,offset sourcefile    ; delete the source file
        mov     ah,DeleteFile
        int     INTDOS

        ret
copyfile        endp


; ----------------------------------------------------------------------------
; getyesno
; This procedure prints a yes/no prompt on the screen and gets an answer.
; the routine accepts "y", "1", and "t" as affirmative responses.  the
; routine accepts "n", "0", and "f" as negative responses.  If the routine
; is given an affirmative response, it will return AX as 0001.  If it is
; given a negative reply, it will send back 0000 in AX.  If the routine
; receives "a" in reply to its prompt, it will return a 0002 in AX.

getyesno        proc    near

        mov     si,offset ConfirmPrompt ; print prompt
        call    putstr

gyn_delete:     xor     ax,ax
        mov     confirmreply,al

gyn_loop:       mov     ah,GetChar      ; get one character
        int     INTDOS
        cmp     al,'a'
        jl      gyn_noup
        cmp     al,'z'
        jg      gyn_noup

        sub     al,('a'-'A')    ; make it uppercase

gyn_noup:       mov     ah,confirmreply
        and     ah,ah
        jne     gyn_checkcr     ; we got a letter, is this one
                                ; a carriage return?

        cmp     al,cr           ; carriage return?
        je      gyn_negative    ; yep, default is no!

        cmp     al,ctrlz        ; control zee?
        jne     gyn_notzee

        ; if CTRL+Z is pressed, we must quit here.

        mov     si,offset UAborted
        call    putstr          ; make a message about it
        mov     al,ERR_None     ; exit with no errors
        call    exit

gyn_notzee:     cmp     al,space        ; is this character lower than ' '?
        jle     gyn_loop        ; yep, ignore it

        cmp     al,'Y'          ; filter out unwanted chars
        je      gyn_okay        ;

        cmp     al,'N'
        je      gyn_okay

        cmp     al,'A'
        je      gyn_okay

        cmp     al,'0'
        je      gyn_okay

        cmp     al,'1'
        je      gyn_okay

        cmp     al,'T'
        je      gyn_okay

        cmp     al,'F'
        jne     gyn_loop

gyn_okay:
        mov     confirmreply,al ; store the character

        mov     ah,PutChar      ; and echo it to the screen
        mov     dl,al
        int     INTDOS
        jmp     short gyn_loop  ; go loop back for more

gyn_checkcr:    cmp     al,cr           ; is this one a carriage return?
        je      gyn_replied     ; yes! process the reply

        cmp     al,bs           ; is this one a backspace?
        jne     gyn_loop        ; no! bad char, ignore it

        mov     ah,PutChar      ; put a backspace
        mov     dl,bs
        int     INTDOS

        mov     ah,PutChar      ; put a space to whiteout
        mov     dl,space        ; the character typed
        int     INTDOS

        mov     ah,PutChar      ; and another BS to fix cursor
        mov     dl,bs
        int     INTDOS

        jmp     gyn_delete

gyn_replied:    mov     al,confirmreply ; get the reply, and decide what
                                ;       to do

        cmp     al,'A'
        je      gyn_everything

        cmp     al,'0'
        je      gyn_negative
        cmp     al,'F'
        je      gyn_negative
        cmp     al,'N'
        je      gyn_negative

        cmp     al,'Y'
        je      gyn_affirm
        cmp     al,'1'
        je      gyn_affirm
        cmp     al,'T'
        je      gyn_affirm
        jmp     gyn_loop        ; somehow a bad char got in


gyn_everything: mov     ax,2            ; reply "all"
        ret

gyn_negative:   mov     ax,0            ; negative reply
        ret

gyn_affirm:     mov     ax,1            ; affirmative reply
        ret

getyesno        endp


; ----------------------------------------------------------------------------
; check4dir
; this routine accepts DI as a pointer to a FileNameS.  It will check
; to see if the file name section of the file name is actually a directory.
; if it is, it will move the file name to the end of the path field and
; make the file name null.  if not, it simply returns without changing
; the structure.  We have to do this because DOS won't understand us if
; we specify a subdirectory without tacking \*.* onto the end of it.
; If we end up changing anything, AX is set to one.  If not, AX is zilch.
;

check4dir       proc    near

        push    si              ; save the pointer

        mov     di,offset temppath
        call    strcpy          ; move drive to temppath

        mov     di,offset temppath
        add     si,path         ; get the path
        call    strcat

        mov     di,offset temppath
        pop     si
        push    si
        add     si,fname
        call    strcat          ; and finally add the filename

        mov     ah,FindFirst
        mov     dx,offset temppath
        mov     cx,SubDirAttrib ; get directories only
        int     INTDOS
        jnc     c4d_worked

        ; return with al set to zero to indicate that
        ; we didn't change the filename

        xor     ax,ax           ; make a zilcher out of ax
        pop     si
        ret

        ; it *is* a subdir!
        ; concatenate the file to the path


c4d_worked:     pop     si
        push    si
        mov     di,si
        add     di,path
        add     si,fname
        call    strcat          ; add filename to path

        mov     si,offset BackSlash
        call    strcat          ; add backslash to path

        mov     si,offset Star  ; put a star into the filename
        pop     di
        push    di
        add     di,fname
        call    strcpy

        mov     si,offset DotStar
        pop     di
        push    di
        add     di,fext         ; and a ".*" into extension
        call    strcpy

c4d_end:        mov     ax,1            ; ax=1, success!
        pop     si              ; pop register and return!
        ret
check4dir       endp


; ----------------------------------------------------------------------------
; normpath
; this proc accepts a pointer to a FileNameS in DS:SI.  The proc will
; call DOS to have it normalize the path -- this simply means that the
; procedure will resolve any relative (ie, including "." or "..")
; file path references.  Note that the function call used here is
; undocumented.

normpath        proc    near

        push    di              ; save the registers
        push    si


        mov     di,offset temppath
        call    strcpy          ; copy drive and path to
        add     si,path         ; a temporary area
        call    strcat

        mov     ah,NormalizePath
        mov     si,offset temppath
        mov     di,offset fixedpath
        int     INTDOS

        pop     si
        mov     di,si           ; copy the path back
        add     di,path
        mov     si,(offset fixedpath)+2
        call    strcpy

        pop     di              ; restore
        ret                     ; and return
normpath        endp


; ----------------------------------------------------------------------------
; That's a wrap.  Written under the influence of Tom Petty, Rush, and REM.

        end


[ RETURN TO DIRECTORY ]