home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS 1992 September / Simtel20_Sept92.cdr / msdos / pcmag / vol6n06.arc / COPYSAFE.ASM next >
Assembly Source File  |  1987-12-13  |  30KB  |  609 lines

  1. ;           COPYSAFE.ASM
  2. ; Resident program that warns if you are
  3. ; about to copy over an existing file.
  4.  
  5. CODE SEGMENT                       ;***********************;
  6. ASSUME CS:CODE,DS:CODE             ;                       ;
  7. ORG 100H                           ;  REMEMBER TO EXE2BIN  ;
  8.                                    ;                       ;
  9.                                    ;***********************;
  10. START:         JMP    INITIALIZE
  11.  
  12. COPYRIGHT      DB     'Copyright 1986 Ziff-Davis Publishing Co.',1AH
  13. PROGRAMMER     DB     'Michael J. Mefford'
  14. OLD_21         DD     ?
  15. DOS_SEG        DW     ?
  16. KBD_BUFFER     DW     ?
  17. OLD_DTA        DD     ?
  18.  
  19. PARA1_START    DW     ?
  20. PARA1_LENGTH   DW     ?
  21. PARA2_START    DW     OFFSET PARA2
  22.  
  23. FIRST_TIME     DB     0
  24. GLOBAL_FLAG    DB     0
  25. MATCH_FLAG     DB     0
  26. PATH_FLAG      DB     0
  27.  
  28. STAR_DOT_STAR  DB     '*.*'
  29. COPY           DB     'COPY'
  30. WARNING_STRING DB     13,10,13,10,'WARNING: The following file(s)',13,10
  31.                DB     'exist and will be copied over.',13,10,13,10
  32.                DB     'Directory $'
  33. QUERY_STRING   DB     13,10,'Do you wish to continue (Y/N)?  N$'
  34. BLANK_LINE     DB     13,10,'$'
  35.  
  36. ;-----------------------------------------------------------------------;
  37. ; Upon entry to the INT 21 inteceptor, check if buffered keyboard input ;
  38. ; (function call 0Ah).  If so, check the DOS segment as signature that  ;
  39. ; we are at the DOS prompt.  Check if command is COPY.  If so, process. ;
  40. ;-----------------------------------------------------------------------;
  41.  
  42. NEW_21:        PUSHF                         ;Save flags.
  43.  
  44.                CMP    AH,0AH                 ;Is it buffered keyboard
  45.                JZ     CK_FIRST               ; function call (0Ah)?
  46.                JMP    NO_BUSINESS            ;If no, exit.
  47.  
  48. CK_FIRST:      CMP    BYTE PTR CS:FIRST_TIME,0    ;Else, is it first time
  49.                JNZ    CK_PROMPT                   ;through? If no, is it DOS?
  50.                MOV    CS:FIRST_TIME,1        ;If yes, flag so won't repeat.
  51.                MOV    CS:DOS_SEG,DS          ;Save the segment as signature.
  52.                MOV    CS:KBD_BUFFER,DX       ;Save the offset as DOS buffer.
  53.  
  54. CK_PROMPT:     PUSH   AX                     ;Need to use AX to examine DS
  55.                MOV    AX,DS                  ; for lack of segment compare
  56.                CMP    AX,WORD PTR CS:DOS_SEG ; instruction. Is it DOS?
  57.                POP    AX                     ;
  58.                JNZ    NO_BUSINESS            ;If no, exit.
  59.  
  60.                CALL   CS:OLD_21              ;Let user enter in the command.
  61.  
  62.                PUSHF                         ;Upon return, save all registers.
  63.                PUSH   AX
  64.                PUSH   BX
  65.                PUSH   CX
  66.                PUSH   DX
  67.                PUSH   DS
  68.                PUSH   ES
  69.                PUSH   SI
  70.                PUSH   DI
  71.                PUSH   BP
  72.  
  73.                MOV    AH,2FH                      ;Get the current disk
  74.                INT    21H                         ;transfer address
  75.                MOV    WORD PTR CS:OLD_DTA,BX      ;and save.
  76.                MOV    WORD PTR CS:OLD_DTA[2],ES
  77.                MOV    CS:MATCH_FLAG,0             ;Reset the match flag.
  78.                MOV    SI,DX                  ;DX points to DOS kbd buffer.
  79.                INC    SI                     ;Second byte has bytes read.
  80.                XOR    CH,CH
  81.                MOV    CL,[SI]                ;Retrieve bytes read.
  82.                CMP    CX,6                   ;Has to be at least 6 bytes
  83.                JB     QUICK_EXIT             ; to be copy. E.g. (COPY A)
  84.                INC    CX                     ;Increment count to include CR.
  85.                MOV    BP,CX                  ;Use BP to keep track of bytes.
  86.  
  87.                PUSH   CS                     ;ES to point to us.
  88.                POP    ES
  89.                INC    SI                     ;Point to first byte of command.
  90.                MOV    DI,OFFSET COMMAND      ;Point to our work space.
  91.                CLD                           ;Move in forward direction.
  92.                REP    MOVSB                  ;Move command to our work space.
  93.                PUSH   CS
  94.                POP    DS                     ;DS to point to us.
  95.                CALL   CAPITALIZE             ;Capitalize command.
  96.                MOV    BX,OFFSET COMMAND
  97.                CALL   PARA_START             ;Find start of command.
  98.                JC     EXIT
  99.                MOV    CX,4
  100.                MOV    SI,OFFSET COPY
  101.                MOV    DI,BX
  102.                REPZ   CMPSB                  ;Is it COPY command?
  103.                JNZ    EXIT                   ;If no, exit.
  104.                MOV    BX,DI                  ;If yes, adjust pointers.
  105.                SUB    BP,4
  106.                CALL   PARA_START             ;Find start of first parameter.
  107.                JC     EXIT                   ;If none, exit.
  108.                MOV    PARA1_START,BX         ;Else, save starting position.
  109.                CALL   PARA_END               ;Find end of parameter.
  110.                MOV    CX,BX
  111.                SUB    CX,PARA1_START         ;Subtract start from end to
  112.                MOV    PARA1_LENGTH,CX        ; get length and save.
  113.                JMP    SHORT CK_PARA2         ;Jump around exit.
  114.  
  115. ;-----------------------------------------------------------------;
  116. ; These are the exits.  One exit jumps directly to the old INT 21 ;
  117. ; if the call is not 0Ah.  The other is the exit after we process ;
  118. ; the command.  The exits are in the middle of the main routine   ;
  119. ; so we can reach it with as many short jumps as possible.        ;
  120. ;-----------------------------------------------------------------;
  121.  
  122. NO_BUSINESS:   POPF
  123.                JMP    CS:OLD_21              ;Jump to the old DOS routine.
  124.  
  125. EXIT:          CMP    MATCH_FLAG,1           ;Did we get a match?
  126.                JNZ    DONE                   ;If no, we are done
  127.                CALL   QUERY                  ;else, query if want to continue.
  128. DONE:          MOV    DX,WORD PTR OLD_DTA    ;Restore the data transfer
  129.                MOV    AX,WORD PTR OLD_DTA[2] ; address.
  130.                MOV    DS,AX
  131.                CALL   SET_DTA
  132. QUICK_EXIT:    POP    BP                     ;Restore all registers.
  133.                POP    DI
  134.                POP    SI
  135.                POP    ES
  136.                POP    DS
  137.                POP    DX
  138.                POP    CX
  139.                POP    BX
  140.                POP    AX
  141.                POPF
  142.                IRET                          ;Return.
  143.  
  144. ;-------------------------------------------------------------------;
  145. ; Before we can check if the files exist the parameters must be     ;
  146. ; parsed as one or two parameters and if drive and/or path only.    ;
  147. ;-------------------------------------------------------------------;
  148.  
  149. CK_PARA2:      CALL   PARA_START             ;Is there a second parameter?
  150.                JC     ONE_PARA               ;If no, process as one.
  151.                MOV    SI,BX
  152.                CALL   PARA_END               ;Find end of parameter.
  153.                MOV    CX,BX
  154.                SUB    CX,SI                  ;Find length of parameter.
  155.                JMP    SHORT STORE_PARA2      ;And put in work space.
  156.  
  157. ONE_PARA:      MOV    SI,PARA1_START
  158.                MOV    DX,PARA1_LENGTH
  159.                ADD    SI,DX                  ;Separate the path by
  160.                CALL   SCAN_OFF               ;scanning off "\" and ":".
  161.                CMP    CX,PARA1_LENGTH        ;Is there a path?
  162.                JNZ    STORE_PARA2            ;If yes, store.
  163.                MOV    BYTE PTR PARA2,0       ;Else, store zero.
  164.                JMP    SHORT GET_PATHS        ;Check if it is a path request.
  165.  
  166. STORE_PARA2:   MOV    DI,PARA2_START         ;Store the second parameter.
  167.                REP    MOVSB
  168.                MOV    BYTE PTR [DI],0        ;Add the zero.
  169. GET_PATHS:     MOV    DX,OFFSET DESTINATION  ;Point to work space.
  170.                CALL   SET_DTA
  171.                MOV    DI,PARA1_START         ;Is the first parameter a path?
  172.                CALL   CK_PATH
  173.                JC     CK_TARGET                   ;If no, check the second.
  174.                MOV    SI,OFFSET STAR_DOT_STAR     ;Else, add "*.*"
  175.                MOV    CX,3
  176.                REP    MOVSB
  177.                MOV    BYTE PTR [DI],0             ;And the zero.
  178.                ADD    WORD PTR PARA1_LENGTH,4     ;Adjust the length by "\*.*"
  179.  
  180. CK_TARGET:     MOV    DI,PARA2_START         ;Is second a path request?
  181.                CALL   CK_PATH
  182.                JNC    APPEND                 ;If path, append the filename.
  183.                CALL   CK_GLOBAL
  184.                CMP    GLOBAL_FLAG,1
  185.                JZ     CK_EXIST
  186.                CALL   MATCH                  ;Else, it is a renamed file.
  187.                JMP    EXIT                   ;Display and exit.
  188.  
  189. APPEND:        MOV    SI,PARA1_START
  190.                MOV    DX,PARA1_LENGTH
  191.                ADD    SI,DX                  ;Else, scan off path from source
  192.                CALL   SCAN_OFF               ; and add to target.
  193.                REP    MOVSB
  194.                MOV    BYTE PTR [DI],0        ;End with a byte of zero.
  195.  
  196. ;----------------------------------------------------------------------;
  197. ; We are ready to see if the target file exists.  This is done by      ;
  198. ; checking if all matching files of source match the requested target. ;
  199. ;----------------------------------------------------------------------;
  200.  
  201. CK_EXIST:      CALL   CK_GLOBAL              ;Check if global characters.
  202.                MOV    DX,OFFSET SOURCE       ;Point to work space.
  203.                CALL   SET_DTA
  204.                MOV    DX,PARA1_START
  205.                MOV    CX,6
  206.                CALL   FIND_FIRST             ;Find first matching.
  207.                JNC    GET_DEST
  208.                JMP    EXIT                   ;If none, exit.
  209. GET_DEST:      MOV    DX,OFFSET DESTINATION  ;Point to target work space.
  210.                CALL   SET_DTA
  211.                MOV    DX,PARA2_START
  212.                MOV    CX,6
  213.                CALL   FIND_FIRST             ;Find first matching.
  214.                JNC    FIRST_COMP
  215.                JMP    EXIT                   ;If none, exit.
  216. FIRST_COMP:    CALL   COMPARE                ;See if filenames identical.
  217.  
  218. NEXT_DEST:     CALL   FIND_NEXT              ;Find next matching target.
  219.                JC     NEXT_SOURCE            ;If none, get next source.
  220.                CALL   COMPARE                ;See if filenames identical.
  221.                JMP    SHORT NEXT_DEST
  222.  
  223. NEXT_SOURCE:   MOV    DX,OFFSET SOURCE       ;Switch DTA back to source.
  224.                CALL   SET_DTA
  225.                CALL   FIND_NEXT              ;Get next matching source.
  226.                JNC    GET_DEST               ;If exist, get next target.
  227.                JMP    EXIT                   ;Else, we are done here.
  228.  
  229.                ;*************;
  230.                ; SUBROUTINES ;
  231.                ;*************;
  232.  
  233. ;---------------------------------------------------;
  234. ; This subroutine will capitalize the command line. ;
  235. ;---------------------------------------------------;
  236.  
  237. CAPITALIZE:    MOV    SI,OFFSET COMMAND      ;Point to command line.
  238.                MOV    CX,BP                  ;Get length of command.
  239. CK_CAP:        CMP    BYTE PTR [SI],'a'
  240.                JB     NEXT_CAP               ;If the byte is between
  241.                CMP    BYTE PTR [SI],'z'      ;"a" and "z", capitalize
  242.                JA     NEXT_CAP               ;by stripping the 5th bit.
  243.                AND    BYTE PTR [SI],5FH
  244. NEXT_CAP:      INC    SI
  245.                LOOP   CK_CAP
  246.                RET
  247.  
  248. ;--------------------------------------------------;
  249. ; These subroutines are INT 21 DOS function calls. ;
  250. ;--------------------------------------------------;
  251.  
  252. SET_DTA:       MOV    AH,1AH                 ;Set disk transfer address.
  253.                INT    21H
  254.                RET
  255.  
  256. FIND_FIRST:    MOV    AH,4EH                 ;Find first matching file.
  257.                INT    21H
  258.                RET
  259.  
  260. FIND_NEXT:     MOV    AH,4FH                 ;Find next matching file.
  261.                INT    21H
  262.                RET
  263.  
  264. PRINT_STRING:  MOV    AH,9                   ;Print string.
  265.                INT    21H
  266.                RET
  267.  
  268. PRINT_CHAR:    MOV    DL,AL
  269.                MOV    AH,2                   ;Display output.
  270.                INT    21H
  271.                RET
  272.  
  273. GET_DIR:       MOV    DL,0
  274.                MOV    AH,47H                 ;Get current directory.
  275.                INT    21H
  276.                RET
  277.  
  278. CHANGE_DIR:    MOV    AH,3BH                 ;Change current directory.
  279.                INT    21H
  280.                RET
  281.  
  282. ;-----------------------------------------------------;
  283. ; This subroutine will find the start of a parameter. ;
  284. ;-----------------------------------------------------;
  285.  
  286. PARA_START:    CMP    BYTE PTR [BX],32       ;Is it space character or below?
  287.                JBE    NEXT_START             ;If yes, get next character.
  288.                CMP    BYTE PTR [BX],'/'      ;Is it the switch character?
  289.                JNZ    END_START              ;If no, we have the start.
  290.                INC    BX                     ;Else, adjust pointers.
  291.                DEC    BP
  292.                JZ     END_COM                ;If end of command, no parameter.
  293. NEXT_START:    INC    BX                     ;Adjust pointers to next char.
  294.                DEC    BP
  295.                JNZ    PARA_START
  296. END_COM:       STC                           ;Indicate found parameter
  297.                RET                           ; by setting carry flag.
  298. END_START:     CLC                           ;Indicate did not find parameter
  299.                RET                           ; by clearing carry flag.
  300.  
  301. ;---------------------------------------------------;
  302. ; This subroutine will find the end of a parameter. ;
  303. ;---------------------------------------------------;
  304.  
  305. PARA_END:      INC    BX                     ;Adjust pointers.
  306.                DEC    BP
  307.                CMP    BYTE PTR [BX],32       ;Is it space character or below?
  308.                JBE    END_PARA               ;If yes, found end.
  309.                CMP    BYTE PTR [BX],'/'      ;Is it switch character?
  310.                JNZ    PARA_END               ;If no, get next character.
  311.                MOV    BYTE PTR [BX],0        ;Else, replace with zero for
  312.                JMP    SHORT PARA_END         ; DOS and get next.
  313. END_PARA:      MOV    BYTE PTR [BX],0        ;Mark end of parameter with zero.
  314.                RET
  315.  
  316. ;----------------------------------------------------------------;
  317. ; This subroutine will check to see if parameter is a path only. ;
  318. ;----------------------------------------------------------------;
  319.  
  320. CK_PATH:       MOV    DX,DI                  ;Save the start in DX and SI.
  321.                MOV    SI,DI
  322.                CMP    BYTE PTR [DI],0        ;If zero need to add "*.*"
  323.                JZ     PATH
  324. FIND_END:      INC    DI                     ;Find the end of parameter.
  325.                CMP    BYTE PTR [DI],0
  326.                JNZ    FIND_END
  327.                CMP    BYTE PTR [DI-1],':'         ;Is it drive only?
  328.                JZ     PATH                        ;If yes, path request.
  329.                CMP    BYTE PTR [DI-1],'\'         ;Is it root only?
  330.                JZ     PATH                        ;If yes, path request.
  331.                CMP    BYTE PTR [DI-1],'*'         ;Is it "*.*"?
  332.                JZ     CK_PATH_END                 ;If yes, not path request.
  333.                MOV    CX,10H
  334.                CALL   FIND_FIRST
  335.                JNC    CK_DIR
  336.                POP    AX                          ;If it doesn't exist, exit.
  337.                JMP    EXIT
  338. CK_DIR:        CMP    BYTE PTR DESTINATION+21,10H ;Is it directory?
  339.                JNZ    CK_PATH_END                 ;If no, not path request.
  340.                CMP    BYTE PTR [DI],'\'           ;Do we need to add "\"?
  341.                JZ     PATH
  342. ADD_PATH:      MOV    BYTE PTR [DI],'\'           ;If yes, do so.
  343.                INC    DI                          ;And adjust pointer.
  344.  
  345. PATH:          CLC                                ;Indicate found path.
  346.                RET
  347. CK_PATH_END:   STC                                ;Indicate path not found.
  348.                RET
  349.  
  350. ;--------------------------------------------------------------------;
  351. ; This subroutine will scan off the path by looking for "\" and ":". ;
  352. ;--------------------------------------------------------------------;
  353.  
  354. SCAN_OFF:      XOR    CX,CX                  ;Zero in counter.
  355. NEXT_SCAN:     CMP    BYTE PTR [SI-1],'\'    ;Is it "\"?
  356.                JZ     END_SCAN               ;If yes, got path.
  357.                CMP    BYTE PTR [SI-1],':'    ;Is it ":"?
  358.                JZ     END_SCAN               ;If yes, got path.
  359.                DEC    SI                     ;Else, adjust pointers.
  360.                INC    CX
  361.                CMP    CX,DX                  ;If at start of para, no path.
  362.                JB     NEXT_SCAN
  363. END_SCAN:      RET
  364.  
  365. ;-----------------------------------------------------------------;
  366. ; This subroutine will display a string until it comes to a zero. ;
  367. ;-----------------------------------------------------------------;
  368.  
  369. ASCIIZ_PRINT:  LODSB                         ;Get a byte.
  370.                CMP    AL,0                   ;Is it a zero?
  371.                JZ     PRINT_END              ;If yes, we are done here.
  372.                CALL   PRINT_CHAR             ;Else, print it.
  373.                JMP    SHORT ASCIIZ_PRINT     ;And get next character.
  374. PRINT_END:     RET
  375.  
  376. ;-------------------------------------------------------------;
  377. ; This subroutine will compare each matching source with each ;
  378. ; target to determine if the target exists.  If there are any ;
  379. ; global characters, only those positions will be compared.   ;
  380. ;-------------------------------------------------------------;
  381.  
  382. COMPARE:       MOV    SI,OFFSET SOURCE+30         ;Point to source and
  383.                MOV    DI,OFFSET DESTINATION+30    ;target filenames.
  384.                CMP    GLOBAL_FLAG,1               ;If global characters,
  385.                JZ     GLOBAL                      ;do a global compare.
  386.  
  387. NEXT_COMP:     CMPSB                              ;Are the bytes the same?
  388.                JNZ    NO_MATCH                    ;If no, no match.
  389.                CMP    BYTE PTR [DI-1],0           ;Are we at end of filename?
  390.                JNZ    NEXT_COMP                   ;If no, get next byte.
  391.                JMP    SHORT MATCH                 ;Else, we have a match.
  392.  
  393. GLOBAL:        MOV    BP,OFFSET DESTINATION+2     ;Point to DOS work space.
  394. NEXT_HALF:     XOR    BX,BX                       ;Zero in counter.
  395. NEXT_LENGTH:   CMP    BYTE PTR [DI+BX],'.'        ;Get length of target by
  396.                JZ     GOT_LENGTH                  ;looking for "." or zero.
  397.                CMP    BYTE PTR [DI+BX],0
  398.                JZ     GOT_LENGTH
  399.                INC    BX
  400.                JMP    SHORT NEXT_LENGTH
  401. GOT_LENGTH:    MOV    CX,BX
  402.  
  403. NEXT_BYTE:     CMP    BYTE PTR [SI],'.'      ;Did we get to end of source
  404.                JZ     NO_MATCH               ; before we got end target?
  405.                CMP    BYTE PTR [SI],0        ;If yes, no match.
  406.                JZ     NO_MATCH
  407.                CMP    BYTE PTR DS:[BP],'?'   ;Is it a global char request?
  408.                JZ     COMP_BYTE              ;If yes, compare.
  409.                INC    DI                     ;Else, adjust pointers.
  410.                INC    SI
  411.                JMP    SHORT NEXT_WILD        ;And get next byte.
  412. COMP_BYTE:     CMPSB                         ;Are the bytes the same?
  413.                JNZ    NO_MATCH               ;If no, no match.
  414. NEXT_WILD:     INC    BP                     ;Adjust pointer.
  415.                LOOP   NEXT_BYTE              ;Get next byte.
  416.  
  417.                CMP    BYTE PTR [DI],0        ;Are we at end of target?
  418.                JNZ    CK_EXTEN               ;If no, get extension.
  419.                CMP    BYTE PTR [SI],0        ;If yes, are we end of source?
  420.                JZ     MATCH                  ;If yes, we have match.
  421.                CMP    BYTE PTR DS:[BP],'?'   ;Else, is global request?
  422.                JNZ    MATCH                  ;If no, we have a match.
  423.                JMP    SHORT NO_MATCH         ;Else, no match.
  424.  
  425. CK_EXTEN:      INC    DI                     ;Bump pointer past "."
  426. NEXT_EXTEN:    CMP    BYTE PTR [SI],0        ;Are we at end of source?
  427.                JZ     NO_MATCH               ;If yes, no match.
  428.                INC    SI                          ;Else get extension.
  429.                CMP    BYTE PTR [SI-1],'.'         ;by looking for "."
  430.                JNZ    NEXT_EXTEN
  431.                MOV    BP,OFFSET DESTINATION+10    ;Adjust DOS work space.
  432.                JMP    SHORT NEXT_HALF             ;Get extension of filename.
  433.  
  434. MATCH:         CMP    MATCH_FLAG,1                ;Is this the first match?
  435.                JZ     GO_PRINT                    ;If no, skip warning.
  436.                CALL   PRINT_WARNING               ;Else, print warning.
  437. GO_PRINT:      MOV    AL,9                        ;Print a tab to indent.
  438.                CALL   PRINT_CHAR
  439.                MOV    SI,OFFSET DESTINATION+30    ;Point to filename to print.
  440.                CALL   ASCIIZ_PRINT
  441. CR_LF:         MOV    DX,OFFSET BLANK_LINE        ;Add carriage return.
  442.                CALL   PRINT_STRING
  443. NO_MATCH:      RET
  444.  
  445. ;-----------------------------------------------------------------;
  446. ; This subroutine will check for any global characters in target. ;
  447. ;-----------------------------------------------------------------;
  448.  
  449. CK_GLOBAL:     MOV    GLOBAL_FLAG,0          ;Reset global flag.
  450.                MOV    SI,PARA2_START         ;Point to target.
  451. NEXT_GLOBAL:   LODSB                         ;Get a byte.
  452.                CMP    AL,'*'                 ;Is it "*"?
  453.                JZ     FOUND_GLOBAL           ;If yes, got global.
  454.                CMP    AL,'?'                 ;Is it "?"?
  455.                JZ     FOUND_GLOBAL           ;If yes, got global.
  456.                CMP    AL,0                   ;Is it end of parameter?
  457.                JNZ    NEXT_GLOBAL            ;If no, get next byte.
  458.                RET
  459. FOUND_GLOBAL:  MOV    GLOBAL_FLAG,1
  460.                RET
  461.  
  462. ;---------------------------------------------------------------------;
  463. ; This subroutine will print the warning and directory of the target. ;
  464. ;---------------------------------------------------------------------;
  465.  
  466. PRINT_WARNING: MOV    PATH_FLAG,0                 ;Reset path flag.
  467.                MOV    DX,OFFSET WARNING_STRING    ;Print warning.
  468.                CALL   PRINT_STRING
  469.                MOV    MATCH_FLAG,1                ;Indicate match.
  470.                MOV    AH,19H                      ;Get current drive.
  471.                INT    21H
  472.                PUSH   AX                          ;And save.
  473.                MOV    SI,OFFSET CURRENT_DIR+1     ;Get current directory.
  474.                CALL   GET_DIR                     ;And save.
  475.  
  476.                MOV    BP,PARA2_START         ;Does target include drive?
  477.                CMP    BYTE PTR DS:[BP+1],':'
  478.                JNZ    GET_END                ;If no, check for path.
  479.                MOV    DL,BYTE PTR DS:[BP]    ;Else, get drive requested.
  480.                SUB    DL,'A'                 ;Convert to DOS format.
  481.                MOV    AH,0EH                 ;And change drive.
  482.                INT    21H
  483.                MOV    SI,OFFSET SOURCE_DIR+1
  484.                CALL   GET_DIR                ;Get directory of target drive.
  485.                MOV    PATH_FLAG,1            ;Indicate that we must restore.
  486.  
  487. GET_END:       INC    BP                     ;Get end of target parameter
  488.                CMP    BYTE PTR DS:[BP],0     ; by looking for zero.
  489.                JNZ    GET_END
  490.                MOV    SI,BP
  491.                MOV    DX,BP                  ;Get length of target parameter.
  492.                SUB    DX,PARA2_START
  493.                CALL   SCAN_OFF               ;Is path requested?
  494.                CMP    CX,DX
  495.                JZ     GET_DISK               ;If no, get default drive.
  496.                DEC    DX
  497.                CMP    CX,DX
  498.                JZ     ROOT
  499.                CMP    BYTE PTR [SI-1],'\'         ;Is it directory request?
  500.                JNZ    GET_DISK                    ;If no, get default.
  501.                CMP    BYTE PTR [SI-2],':'         ;Is it drive request?
  502.                JZ     ROOT                        ;If yes, adjust pointer.
  503.                DEC    SI
  504. ROOT:          MOV    BH,[SI]                ;Save byte and replace with
  505.                MOV    BYTE PTR [SI],0        ;zero to please DOS and get
  506.                MOV    DX,PARA2_START         ;requested directory.
  507.                CALL   CHANGE_DIR
  508.                MOV    [SI],BH                ;Replace character.
  509.  
  510. GET_DISK:      MOV    AH,19H                 ;Get current drive.
  511.                INT    21H
  512.                ADD    AL,'A'                 ;Convert to ASCII.
  513.                CALL   PRINT_CHAR             ;And display.
  514.                MOV    AL,':'                 ;Display ":\" as well.
  515.                CALL   PRINT_CHAR
  516.                MOV    AL,'\'
  517.                CALL   PRINT_CHAR
  518.                MOV    SI,OFFSET WORKING_DIR  ;Get the target directory.
  519.                CALL   GET_DIR
  520.                CMP    BYTE PTR WORKING_DIR,0
  521.                JZ     END_WARNING
  522.                CALL   ASCIIZ_PRINT           ;And display.
  523. END_WARNING:   MOV    DX,OFFSET BLANK_LINE   ;Double space
  524.                CALL   PRINT_STRING           ; by printing to carriage
  525.                CALL   PRINT_STRING           ; returns and linefeeds.
  526.                CMP    PATH_FLAG,1            ;Did we change directory?
  527.                JNZ    DRIVE_ONLY
  528.                MOV    BYTE PTR SOURCE_DIR,'\'
  529.                MOV    DX,OFFSET SOURCE_DIR
  530.                CALL   CHANGE_DIR             ;If yes, restore target directory
  531. DRIVE_ONLY:    POP    DX
  532.                MOV    AH,0EH                 ;Restore the drive as well.
  533.                INT    21H
  534.                MOV    BYTE PTR CURRENT_DIR,'\'   ;Now restore default drive's
  535.                MOV    DX,OFFSET CURRENT_DIR      ; directory.
  536.                CALL   CHANGE_DIR
  537.                RET
  538.  
  539. ;---------------------------------------------------------------------;
  540. ; This subroutine will ask if you wish to continue.  If no, the first ;
  541. ; byte of the command line will be replaced with a carriage return.   ;
  542. ;---------------------------------------------------------------------;
  543.  
  544. QUERY:         MOV    DX,OFFSET QUERY_STRING
  545.                CALL   PRINT_STRING           ;Print the string.
  546.                MOV    AX,0E08H               ;Back the cursor up one by
  547.                INT    10H                    ; printing BS (8) TTY.
  548.  
  549. INKEY:         MOV    AH,7                   ;Get a keystroke.
  550.                INT    21H
  551.                CMP    AL,27                  ;Is it Esc?
  552.                JZ     CANCEL                 ;If yes, cancel COPY
  553.                CMP    AL,13                  ;Is it carriage return?
  554.                JZ     RESPONSE               ;If yes, get response.
  555.                AND    AL,5FH                 ;Capitalize keystroke.
  556.                CMP    AL,'Y'                 ;Is it "Y"?
  557.                JZ     PRINT_KEY              ;If yes, print it.
  558.                CMP    AL,'N'                 ;Is it "N"?
  559.                JZ     PRINT_KEY              ;If yes, print it.
  560.                MOV    AX,0E07H               ;Else, print bell character at
  561.                INT    10H                    ; current cursor position.
  562.                JMP    SHORT INKEY            ; and get next keystroke.
  563.  
  564. PRINT_KEY:     XOR    BH,BH                  ;Print the character at
  565.                MOV    CX,1                   ; the current cursor position.
  566.                MOV    AH,0AH
  567.                INT    10H
  568.                JMP    SHORT INKEY            ;Get next keystroke.
  569.  
  570. RESPONSE:      XOR    BH,BH
  571.                MOV    AH,8                   ;Get character at current
  572.                INT    10H                    ; cursor position.
  573.                CMP    AL,'Y'                 ;Is it "Y"?
  574.                JZ     END_QUERY              ;If yes, done here.
  575. CANCEL:        MOV    BX,KBD_BUFFER          ;Else, it must be "N" and
  576.                MOV    AX,DOS_SEG             ; cancel request by putting a
  577.                MOV    ES,AX                  ; CR in first byte of DOS
  578.                MOV    BYTE PTR ES:[BX+2],13  ; command line buffer.
  579. END_QUERY:     RET
  580.  
  581. ;---------------------------------------------------;
  582. ; This routine installs this program in the path of ;
  583. ; interrupt 21h and then exits but stays resident.  ;
  584. ;---------------------------------------------------;
  585.  
  586. INITIALIZE:    MOV    AX,3521h               ;Get DOS 21 interrupt.
  587.                INT    21H
  588.                MOV    WORD PTR OLD_21,BX     ;Save old interrupt.
  589.                MOV    WORD PTR OLD_21[2],ES
  590.  
  591.                MOV    DX,OFFSET NEW_21       ;Install new interrupt.
  592.                MOV    AX,2521h
  593.                INT    21H
  594.  
  595.                MOV    DX,OFFSET END_RESIDENT ;Terminate but stay resident.
  596.                INT    27h
  597.  
  598. PARA2          EQU    INITIALIZE             ;This is COPYSAFE's reserved
  599. COMMAND        EQU    PARA2+70               ; work space.
  600. DESTINATION    EQU    COMMAND+140            ;It is placed at the end of
  601. SOURCE         EQU    DESTINATION+64         ; code to keep the basic data
  602. CURRENT_DIR    EQU    SOURCE+64              ; listing to a minimum.
  603. WORKING_DIR    EQU    CURRENT_DIR+64
  604. SOURCE_DIR     EQU    WORKING_DIR+64
  605. END_RESIDENT   EQU    SOURCE_DIR+65
  606.  
  607. CODE ENDS
  608. END  START
  609.