BAC     segment para public 'code'
        assume  cs:BAC, ds:BAC, es:BAC, ss:NOTHING
        org     100h            ; .COM format
BEGIN:
        jmp     CODE_START      ; Jump around data declarations
DECLARE:                        ; Messages, Storage Areas, Equates
        COPYRIGHT       db      'BACopy (C) 1985, Dickinson Associates Inc.'
                        db      13,10,'$'
        PATH_FILE_LEN   equ     77  ;Length = 1, Path = 63, FileName = 12, 0 = 1
        SOURCE_FILE     db      PATH_FILE_LEN dup (0)
        TARGET_PATH     db      PATH_FILE_LEN dup (0)
        SOURCE_END      dw      0
        TARGET_END      dw      0
        SOURCE_HANDLE   dw      0
        TARGET_HANDLE   dw      0
        SOURCE_DTA      db      44 dup(0)
        TARGET_DTA      db      44 dup(0)
        VALID_IN        db      'abcdefghijklmnopqrstuvwxyz,;=',9
        VALID_OUT       db      'ABCDEFGHIJKLMNOPQRSTUVWXYZ',4 dup(32)
        VALID_NUM       equ     $ - VALID_OUT + 1
        BLKSIZE         dw      0
        LAST_BLOCK      db      0
        EVENT_FLAG      db      0
        ERR_HEAD        db      10,13,'BACopy Error - $'
        NO_PARMS        db      'Correct Syntax is:',13,10,10
        db   'BACopy [d:][source_path]source_filename[.ext] [d:][target_path]$'
        FILE_NOT_FOUND  db      'File Not Found$'
        SOURCE_ERROR    db      'Opening Source File$'
        CREATE_ERROR    db      'Creating Target File$'
        TARGET_FULL     db      '!!',10,10,13,'Target Disk is Full',13,10,10
        db      'Insert New Disk and Press [Enter]',7,'$'
        ERR_TAIL        db      10,10,13,' . . . Aborting',10,13,13,'$'
        CONFIRM_MSG_1   db      ' . . $'
        CONFIRM_MSG_2   db      'BACopied to . . $'
        END_LINE        db      10,13,'$'
        NOTHING_TO_DO   db      13,10,'No Files Needed to be BACopied',13,10,'$'
;
CODE_START:     ; Parse command line into source & target parameters
        mov     dx,offset COPYRIGHT     ; Display copyright notice
        mov     ah,9h
        int     21h
        mov     si,80h                  ; PSP parameter byte count pointer
        mov     cl,[si]                 ; Move byte count to CL
        xor     ch,ch                   ; Zero CH
        jcxz    NO_PARMS_PASSED         ; If CX is zero, there are no parameters
        mov     dx,cx                   ; Save byte count in dx
        inc     si                      ; Point to parameter area
        mov     di,si                   ; Copy SI to DI for cleanup routine
        cld                             ; Set direction flag to forward
CLEAN_PARMS:    ; Change valid delimiters to blanks, lower to upper case
        lodsb                           ; Load each character to AL
        push    di                      ; Save DI on stack
        mov     di,offset VALID_IN      ; Point to table of valid inputs
        push    cx                      ; Save CX on stack
        mov     cx,VALID_NUM            ; Set CX to number of inputs to look for
repne   scasb                           ; See if any are in AL
        jcxz    CLEAN_END               ; If not, change nothing
        mov     bx,VALID_NUM            ; Set up BX to point to valid output
        sub     bx,cx                   ; This will leave BX one off
        mov     al,VALID_OUT [bx - 1]   ; Load the valid output to AL
CLEAN_END:
        pop     cx                      ; Restore CX
        pop     di                      ; Restore DI
        stosb                           ; Store modified AL back to PSP
loop    CLEAN_PARMS                     ; Loop until CX is zero
;
        mov     cx,dx                   ; Restore number of bytes in PSP to CX
        mov     dx,2                    ; Set DX to look for up to 2 parameters
        mov     bx,offset SOURCE_FILE   ; Set BX to address of 1st parameter
        mov     al,' '                  ; Set up to scan for first non-blank
        mov     di,81h                  ; Set DI to PC-DOS parameter pointer
FIND_PARMS:     ; Start looking for parameters, load to program storage
repe    scasb                           ; Scan while blanks
        mov     si,di                   ; Set SI to second non-blank byte
        dec     si                      ; Adjust it to first non-blank byte
        inc     cx                      ; Adjust CX to compensate
        jcxz    PARMS_LOADED            ; If CX is zero, no parameters left
        mov     di,bx                   ; Set DI to parameter hold area
        mov     ax,cx                   ; Store CX to first byte of hold area
        stosb                           ; DI is adjusted to second byte here
STORE:  lodsb                           ; Load each byte to AL
        cmp     al,' '                  ; Is it a blank?
        jz      END_STORE               ; Yes, end of this parameter
        stosb                           ; No, store the byte to hold area
END_STORE:
        loopnz  STORE                   ; Keep looking
        sub     [bx],cx                 ; Store number of bytes in each
        jcxz    PARMS_LOADED            ; If CX is zero, no more parameters
        dec     byte ptr [bx]           ; parameter to first byte of hold area
        mov     di,si                   ; Set up to scan for next non-blank
        dec     di                      ; Adjust DI to point to the blank
        inc     cx                      ; Adjust CX to compensate
        dec     dx                      ; Decrement DX counter
        cmp     dx,0                    ; Is DX zero?
        jz      PARMS_LOADED            ; Yes, all expected parameters loaded
        add     bx,PATH_FILE_LEN        ; No, point to next part of hold area
        jmp     FIND_PARMS              ; Go back and look for more
PARMS_LOADED:                           ; All parameters are loaded
        cmp     SOURCE_FILE[0],0        ; If there are no bytes in the
        ja      FIX_UP                  ; SOURCE_FILE, no parameters present
NO_PARMS_PASSED:                        ; Exit with an error if there
        mov     dx,offset NO_PARMS      ; are no parameters passed
        jmp     ERROR_EXIT
FIX_UP:                                 ; Fix SOURCE_FILE and TARGET_PATH
        mov     si,offset SOURCE_FILE   ; For Search calls
        lodsb                           ; Get Number of bytes
        xor     ah,ah                   ; Zero high byte of AX
        mov     di,si                   ; Move SI to DI for scan
        add     di,ax                   ; Start scan at end of parameter
        dec     di                      ; Adjust DI
        mov     cx,ax                   ; Set CX to number of bytes
        mov     al,'\'                  ; Scan for the last '\'
        std                             ; Set direction flag to reverse
repnz   scasb                           ; Scan while not '\'
        jnz     NO_SOURCE_DIR           ; If Zero Flag not set, '\' not found
        add     di,2                    ; Add 2 to DI to point to file name
        jmp     SOURCE_FIXED            ; position
NO_SOURCE_DIR:                          ; No source directory was specified
        add     di,1                    ; Adjust DI
        cmp     SOURCE_FILE[2],':'      ; Check for specified disk drive
        jne     SOURCE_FIXED            ; None present, we're done
        mov     di,offset SOURCE_FILE[3]; Yes, set DI to point to first byte
SOURCE_FIXED:                           ; after ':'
        mov     SOURCE_END,di           ; Move DI to SOURCE_END pointer
;
        cld                             ; Set direction flag to forward
        mov     si,offset TARGET_PATH   ; Set up to look for '\' present
        lodsb                           ; Get number of bytes
        cmp     al,0                    ; If it's zero, no target specified
        je      NO_TARGET
        xor     ah,ah                   ; Zero high byte of AX
        add     si,ax                   ; Add it to SI to point to end
        dec     si                      ; Decrement SI to adjust
        lodsb                           ; Look at last byte
        mov     di,si                   ; Copy SI to DI
        cmp     al,'\'                  ; Is last byte a '\'?
        je      TARGET_FIXED            ; Yes, everything's fine
        cmp     TARGET_PATH[0],2        ; If TARGET_PATH is 2 bytes long and
        jne     STORE_SLASH             ; is a disk drive specification,
        cmp     TARGET_PATH[2],':'      ; let it default to the current
        je      TARGET_FIXED            ; directory.
STORE_SLASH:                            ; Place a '\' at the end of
        mov     al,'\'                  ; TARGET_PATH if user did
        stosb                           ; not
TARGET_FIXED:
        mov     TARGET_END,di           ; Move DI to TARGET_END pointer
        jmp     BUFFER_SIZE
NO_TARGET:                              ; Set up to allow target path default
        mov     TARGET_END,offset TARGET_PATH + 1      ; to current path
BUFFER_SIZE:                            ; Compute size of file buffer
        mov     ax,0fdffh               ; Leave plenty of room in segment
        mov     dx,offset FILE_BUFFER   ; for stack & set DX to end of code
        sub     ax,dx                   ; Subtract
        mov     BLKSIZE,ax              ; Save result in BLKSIZE
FIND_FILE:                              ; Find first source file
        xor     ax,ax                   ; Request to use SOURCE_DTA
        mov     ah,1ah                  ; to house FCB for SOURCE_FILE
        mov     dx,offset SOURCE_DTA
        int     21h                     ; Call PC-DOS
        mov     dx,offset SOURCE_FILE + 1       ; DX points to SOURCE_FILE
        mov     ah,4eh                  ; Request function 4EH (find 1st file)
        mov     cx,0                    ; Set CX to zero for normal files only
        int     21h                     ; Call PC-DOS
        jnc     FOUND_FILE              ; If no error, first file found
        mov     dx,offset FILE_NOT_FOUND; If no files found, exit
        jmp     ERROR_EXIT              ; program with error message
FOUND_FILE:
        mov     LAST_BLOCK,0            ; Initalize last block read flag
        mov     si,offset SOURCE_DTA+30 ; SI points to source file name in DTA
        mov     di,SOURCE_END           ; DI points to end of source path
        push    si                      ; Save pointer to source file name
        mov     cx,13                   ; DTA will have 13 bytes
rep     movsb                           ; Move name bytes to SOURCE_FILE
        mov     di,TARGET_END           ; DI points to end of target path
        pop     si                      ; Recover pointer to source file name
        mov     cx,13                   ; DTA will have 13 bytes
rep     movsb                           ; Move file name bytes to TARGET_PATH
FIND_TARGET:                            ; Find matching target file
        mov     ah,1ah                  ; Request to use TARGET_DTA
        xor     al,al                   ; to house FCB for TARGET_PATH
        mov     dx,offset TARGET_DTA
        int     21h                     ; Call PC-DOS
        mov     ah,4eh                  ; Request find 1st file for target
        mov     dx,offset TARGET_PATH+1
        mov     cx,0                    ; Set CX to zero for normal files only
        int     21h                     ; Call PC-DOS
        jc      OPEN_SOURCE             ; If not found, bypass date & time check
CHECK_TIME_DATE:                        ; Check time & date stamps in DTAs
        mov     si,offset SOURCE_DTA+24 ; Load source file date stamp to AX
        lodsw
        mov     dx,ax                   ; Save in DX
        mov     si,offset TARGET_DTA+24 ; Load target file date stamp to AX
        lodsw
        cmp     dx,ax                   ; If Source file newer, jump
        ja      OPEN_SOURCE             ; to OPEN_SOURCE
        jne     DONT_COPY               ; If Source file older, don't copy it
        mov     si,offset SOURCE_DTA+22 ; Otherwise,
        lodsw                           ; load source time stamp to AX
        mov     dx,ax                   ; Save in DX
        mov     si,offset TARGET_DTA+22 ; Load target time stamp to AX
        lodsw
        cmp     dx,ax                   ; If Source file newer, jump
        ja      OPEN_SOURCE             ; to OPEN_SOURCE
        jmp     DONT_COPY
DONT_COPY:                              ; Otherwise,
        call    CLOSE_ALL               ; Close all files
        jmp     NEXT_FILE               ; Check for next file
OPEN_SOURCE:
        mov     ah,3dh                  ; Request Open Source File
        mov     dx,offset SOURCE_FILE+1 ; DX points to source file path name
        mov     al,0                    ; with read permission only
        int     21h                     ; Call PC-DOS
        mov     SOURCE_HANDLE,ax        ; Save handle in memory
        jnc     CREATE_TARGET           ; If no carry, open was good
        mov     dx,offset SOURCE_ERROR  ; Otherwise, exit with error
        mov     SOURCE_HANDLE,0         ; Make sure CLOSE_ALL ignores handle
        jmp     ERROR_EXIT
CREATE_TARGET:
        xor     ax,ax
        mov     ah,3ch                  ; Request create & open a file
        mov     dx,offset TARGET_PATH+1 ; named the target file
        xor     cx,cx                   ; with normal attribute
        int     21h                     ; Call PC-DOS
        mov     TARGET_HANDLE,ax        ; Save target handle
        jnc     PROCEED_TO_COPY         ; If no carry, create / open is ok
        mov     dx,offset CREATE_ERROR  ; Otherwise, exit with an error
        mov     TARGET_HANDLE,0         ; Make sure CLOSE_ALL ignores target
        jmp     ERROR_EXIT
PROCEED_TO_COPY:                        ; The heart of the matter
        mov     si,offset SOURCE_FILE+1 ; Point to source file
START1: lodsb                           ; Load each byte to AL
        cmp     al,0                    ; If ASCII 0, end of field
        je      DOTS
        mov     dl,al                   ; Copy byte to DL for funciton 2H
        mov     ah,2h                   ; Request function 2H
        int     21h                     ; Call PC-DOS
        jmp     START1                  ; Get next character
DOTS:   mov     ah,9h                   ; Confirm start of task
        mov     dx,offset CONFIRM_MSG_1
        int     21h
KEEP_COPYING:
        mov     ah,3fh                  ; Request read block of data
        mov     cx,BLKSIZE              ; BLKSIZE bytes long
        mov     bx,SOURCE_HANDLE        ; from source file
        mov     dx,offset FILE_BUFFER   ; into buffer
        int     21h                     ; Call PC-DOS
        cmp     ax,0                    ; If AX is 0, no bytes were
        je      FINISH                  ; read, and we're done
        mov     cx,ax                   ; Move AX to CX for write call (below)
        cmp     cx,BLKSIZE              ; Check number of bytes read against
        je      MORE_TO_COME            ; request.  If equal, we got them all,
        mov     LAST_BLOCK,1            ; otherwise, it's the last block of file
MORE_TO_COME:                           ;
        push    cx                      ; Save requested write count on stack
        mov     ah,40h                  ; Request write block of data
        mov     bx,TARGET_HANDLE        ; to target file
        mov     dx,offset FILE_BUFFER   ; from file buffer
        int     21h                     ; Call PC-DOS
        pop     cx                      ; Recover requested write count
        cmp     ax,cx                   ; If CX equals AX,
        je      WRITE_OK                ; write was successful,
DISK_FULL:
        call    CLOSE_ALL               ; Otherwise disk is full -- close files
        mov     ah,41h                  ; Request erase file
        mov     dx,offset TARGET_PATH+1 ; for incomplete target.
        int     21h                     ; Call PC-DOS
        mov     dx,offset TARGET_FULL
        mov     ah,9h
        int     21h
READ_KEYBOARD:                          ; Prompt requested [Enter] key
        mov     ah,8h                   ; Make sure [Ctrl]-[Break] is detected
        int     21h                     ; Call PC-DOS for key
        cmp     al,13                   ; Check for [Enter]
        jne     READ_KEYBOARD           ; (no extended codes are 13)
        mov     cx,2
END_FULL:
        mov     dx,offset END_LINE      ; Send a new line to screen
        mov     ah,9h
        int     21h
        loop    END_FULL
        jmp     FOUND_FILE              ; Re-start from FOUND_FILE:
WRITE_OK:
        cmp     LAST_BLOCK,1            ; If this is the last block,
        je      FINISH                  ; we're done
        jmp     KEEP_COPYING            ; Otherwise, keep going.
FINISH:                                 ; Force target time & date stamps
        mov     ah,57h                  ; to equal source, close files
        mov     al,0                    ; Request get time and date stamos
        mov     bx,SOURCE_HANDLE        ; for source file
        int     21h                     ; DX & CX contain data
        mov     ah,57h                  ; Request set date and time
        mov     al,1                    ; to force target file to
        mov     bx,TARGET_HANDLE        ; source stamp
        int     21h                     ; Call PC-DOS
        call    CLOSE_ALL               ; Go close all files
        mov     dx,offset CONFIRM_MSG_2 ; Confirm completion of task
        mov     ah,9h                   ; Request function 9H
        int     21h                     ; Call PC-DOS
        mov     si,offset TARGET_PATH+1 ; Point to source file
START2: lodsb                           ; Load each byte to AL
        cmp     al,0                    ; If ASCII 0, end of field
        je      CR_LF
        mov     dl,al                   ; Copy byte to DL for funciton 2H
        mov     ah,2h                   ; Request function 2H
        int     21h                     ; Call PC-DOS
        jmp     START2                  ; Get next character
CR_LF:  mov     dx,offset END_LINE      ; Terminate display line
        mov     ah,9h                   ; Request function 9H
        int     21h
        mov     EVENT_FLAG,1            ; Set flag to indicate file was copied
NEXT_FILE:                              ; Go Look for next file
        xor     ax,ax
        mov     ah,1ah                  ; Request to use SOURCE_DTA
        mov     dx,offset SOURCE_DTA    ; to house FCB for SOURCE_FILE
        int     21h                     ; Call PC-DOS
        mov     ah,4fh                  ; Request find next source file
        mov     cx,0                    ; Normal files only
        int     21h                     ; Call PC-DOS
        jnc     FOUND_ANOTHER           ; No error, another file was found
        jmp     END_OK                  ; Error, we're done finding files
FOUND_ANOTHER:
        jmp     FOUND_FILE              ; Go process next file
END_OK: cmp     EVENT_FLAG,1            ; Did anything happen?
        je      EXIT                    ; Yes, just exit
        mov     dx,offset NOTHING_TO_DO ; No, tell user that nothing happened
        mov     ah,9h
        int     21h
EXIT:   int     20h                     ; Exit to PC-DOS
ERROR_EXIT:                             ; Print Error Message and Exit
        push    dx                      ; Save error message pointer on stack
        mov     ah,9                    ; Display error header
        mov     dx,offset ERR_HEAD
        int     21h
        mov     ah,9                    ; Display error message
        pop     dx
        int     21h
        mov     ah,9                    ; Display error tail
        mov     dx,offset ERR_TAIL
        call    CLOSE_ALL
        int     21h
        int     20h                     ; Exit to PC-DOS


CLOSE_ALL       proc
        cmp     SOURCE_HANDLE,0         ; Check for valid SOURCE_HANDLE
        je      CLOSE_TARGET            ; None, then go close target
        mov     ah,3eh                  ; Request close file
        mov     bx,SOURCE_HANDLE        ; for source handle
        int     21h                     ; Call PC-DOS
        mov     SOURCE_HANDLE,0         ; Refresh handle
CLOSE_TARGET:
        cmp     TARGET_HANDLE,0         ; Check for valid TARGET_HANDLE
        je      CLOSE_RETURN            ; None, then return
        mov     bx,TARGET_HANDLE        ; Request close file
        mov     ah,3eh                  ; for target handle
        int     21h                     ; Call PC-DOS
        mov     TARGET_HANDLE,0         ; Refresh handle
CLOSE_RETURN:
        ret
CLOSE_ALL       endp
FILE_BUFFER     label   word
BAC     ends
        end     BEGIN
