;  PROGRAM:  COMPARE
;  AUTHOR:  Richard Conn
;  VERSION:  1.4
;  DATE:  6 JAN 83
;  PREVIOUS VERSIONS:  1.3 (19 DEC 82)
;  PREVIOUS VERSIONS:  1.2 (8 DEC 82), 1.1 (21 JULY 82), 1.0 (11 JULY 82)
VERS    EQU     14

;
;       This program is Copyright (c) 1982, 1983 by Richard Conn
;       All Rights Reserved
;
;       ZCPR2 and its utilities, including this one, are released
; to the public domain.  Anyone who wishes to USE them may do so with
; no strings attached.  The author assumes no responsibility or
; liability for the use of ZCPR2 and its utilities.
;
;       The author, Richard Conn, has sole rights to this program.
; ZCPR2 and its utilities may not be sold without the express,
; written permission of the author.
;


;
;       COMPARE is designed to provide the user with a convenient method
; to compare the contents of two files.  It is invoked by one of two basic
; forms:
;
;               COMPARE filename.typ
; or
;               COMPARE file1.typ file2.typ
;
;       The first form compares the file named "filename.typ" on drive A:
; to the file of the same name on drive B:; the second form compares the
; file named "file1.typ" on drive A: to the file named "file2.typ" on drive
; B:.
;

;  CP/M Constants
CPM     equ     0       ; CP/M Warm Boot
BUFF    equ     CPM+80H ; Temporary Buffer
CR      equ     0DH
LF      equ     0AH

;  CRC ROUTINES
       EXT     CRCCLR,CRCUPD,CRCDONE

;  SYSLIB ROUTINES
       EXT     COMPHD,BDOS,INITFCB,LOGUD,RETUD
       EXT     F$OPEN,F$CLOSE,F$READ
       EXT     CAPS,CIN,COUT,CRLF
       EXT     PADC,MOVEB,PRINT
       EXT     ZGPINS,ZFNAME

;
;  Branch to Start of Program
;
       JMP     START

;
;******************************************************************
;
;  SINSFORM -- ZCPR2 Utility Standard General Purpose Initialization Format
;
;       This data block precisely defines the data format for
; initial features of a ZCPR2 system which are required for proper
; initialization of the ZCPR2-Specific Routines in SYSLIB.
;

;
;  EXTERNAL PATH DATA
;
EPAVAIL:
       DB      0FFH    ; IS EXTERNAL PATH AVAILABLE? (0=NO, 0FFH=YES)
EPADR:
       DW      40H     ; ADDRESS OF EXTERNAL PATH IF AVAILABLE

;
;  INTERNAL PATH DATA
;
INTPATH:
       DB      0,0     ; DISK, USER FOR FIRST PATH ELEMENT
                       ; DISK = 1 FOR A, '$' FOR CURRENT
                       ; USER = NUMBER, '$' FOR CURRENT
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0     ; DISK, USER FOR 8TH PATH ELEMENT
       DB      0       ; END OF PATH

;
;  MULTIPLE COMMAND LINE BUFFER DATA
;
MCAVAIL:
       DB      0FFH    ; IS MULTIPLE COMMAND LINE BUFFER AVAILABLE?
MCADR:
       DW      0FF00H  ; ADDRESS OF MULTIPLE COMMAND LINE BUFFER IF AVAILABLE

;
;  DISK/USER LIMITS
;
MDISK:
       DB      4       ; MAXIMUM NUMBER OF DISKS
MUSER:
       DB      31      ; MAXIMUM USER NUMBER

;
;  FLAGS TO PERMIT LOG IN FOR DIFFERENT USER AREA OR DISK
;
DOK:
       DB      0FFH    ; ALLOW DISK CHANGE? (0=NO, 0FFH=YES)
UOK:
       DB      0FFH    ; ALLOW USER CHANGE? (0=NO, 0FFH=YES)

;
;  PRIVILEGED USER DATA
;
PUSER:
       DB      10      ; BEGINNING OF PRIVILEGED USER AREAS
PPASS:
       DB      'chdir',0       ; PASSWORD FOR MOVING INTO PRIV USER AREAS
       DS      41-($-PPASS)    ; 40 CHARS MAX IN BUFFER + 1 for ending NULL

;
;  CURRENT USER/DISK INDICATOR
;
CINDIC:
       DB      '$'     ; USUAL VALUE (FOR PATH EXPRESSIONS)

;
;  DMA ADDRESS FOR DISK TRANSFERS
;
DMADR:
       DW      80H     ; TBUFF AREA

;
;  NAMED DIRECTORY INFORMATION
;
NDRADR:
       DW      00000H  ; ADDRESS OF MEMORY-RESIDENT NAMED DIRECTORY
NDNAMES:
       DB      64      ; MAX NUMBER OF DIRECTORY NAMES
DNFILE:
       DB      'NAMES   '      ; NAME OF DISK NAME FILE
       DB      'DIR'           ; TYPE OF DISK NAME FILE

;
;  REQUIREMENTS FLAGS
;
EPREQD:
       DB      0FFH    ; EXTERNAL PATH?
MCREQD:
       DB      0FFH    ; MULTIPLE COMMAND LINE?
MXREQD:
       DB      0FFH    ; MAX USER/DISK?
UDREQD:
       DB      0FFH    ; ALLOW USER/DISK CHANGE?
PUREQD:
       DB      000H    ; PRIVILEGED USER?
CDREQD:
       DB      0FFH    ; CURRENT INDIC AND DMA?
NDREQD:
       DB      0FFH    ; NAMED DIRECTORIES?
Z2CLASS:
       DB      0       ; CLASS 0
       DB      'ZCPR2'
       DS      10      ; RESERVED

;
;  END OF SINSFORM -- STANDARD DEFAULT PARAMETER DATA
;
;******************************************************************
;

;
;  Start of Program
;
START:
       CALL    ZGPINS  ; INIT ZCPR2 BUFFERS
       XRA     A       ; SET NO MULTIPLE RUN
       STA     MULT
       CALL    RETUD   ; GET CURRENT USER/DISK
       MOV     A,B     ; SAVE DISK
       STA     CDISK
       MOV     A,C     ; SAVE USER
       STA     CUSER
       LXI     H,BUFF  ; PROCESS OPTIONS IN BUFFER
       MOV     A,M     ; GET CHAR COUNT
       INX     H       ; PT TO FIRST CHAR
       PUSH    H       ; SAVE PTR TO FIRST CHAR
       ADD     L       ; HL=HL+A
       MOV     L,A
       MOV     A,H
       ACI     0
       MOV     H,A
       MVI     M,0     ; STORE ENDING ZERO
       POP     H       ; GET PTR TO FIRST CHAR
       LXI     D,INLINE        ; PT TO INPUT LINE BUFFER
       PUSH    D       ; SAVE PTR
START0:
       MOV     A,M     ; COPY INPUT LINE SO BUFF MAY BE USED
       STAX    D       ; PUT BYTE
       INX     H       ; PT TO NEXT
       INX     D
       ORA     A       ; EOL?
       JNZ     START0
       POP     H       ; PT TO FIRST CHAR
       CALL    SBLANK  ; SKIP SPACES
       ORA     A       ; EOL?
       JZ      PRHELP  ; PRINT HELP IF SO
       CPI     '/'     ; ASKING FOR HELP?
       JZ      PRHELP
       LXI     D,FCBS  ; PT TO SOURCE FCB
       CALL    ZFNAME  ; EXTRACT NAME AND DIRECTORY DATA
       JNZ     START1
UDERR:
       CALL    PRINT
       DB      CR,LF,'Invalid Disk or User -- Aborting',0
       RET
START1:
       MOV     A,B     ; SAVE SOURCE DISK
       CPI     0FFH    ; CHECK FOR CURRENT
       JNZ     SDISK1
       LDA     CDISK   ; SPECIFY CURRENT DISK INSTEAD
       INR     A       ; ADD 1 FOR FOLLOWING DECREMENT
SDISK1:
       DCR     A       ; DOWN 1 SO RANGE IS 0-F
       STA     SDISK
       MOV     A,C     ; SAVE SOURCE USER
       CPI     0FFH    ; CHECK FOR CURRENT
       JZ      SUSER0
       CPI     '?'     ; WILD IS CURRENT
       JNZ     SUSER1
SUSER0:
       LDA     CUSER   ; GET CURRENT USER
SUSER1:
       STA     SUSER
       MOV     A,M     ; GET SEPARATION CHAR
       CPI     ','     ; COMMA IF SECOND NAME SPECIFIED
       JZ      START2
       PUSH    H       ; SAVE PTR
       LXI     H,FCBS+1        ; NO 2ND NAME, SO SET IT TO SAME AS FIRST
       LXI     D,FCBD+1
       MVI     B,11    ; 11 BYTES
       CALL    MOVEB
       LDA     CDISK   ; SET DISK AND USER TO CURRENT
       STA     DDISK
       LDA     CUSER
       STA     DUSER
       POP     H       ; GET PTR
       JMP     START3
START2:
       INX     H       ; PT TO NEXT CHAR AFTER COMMA
       LXI     D,FCBD  ; SET DEST FCB
       CALL    ZFNAME  ; PROCESS NAME
       JZ      UDERR
       LDA     FCBD+1  ; CHECK FOR AMBIGUOUS NAME
       CPI     '?'     ; ASSUME ALL IS AMBIGUOUS IF FIRST CHAR IS
       JNZ     NOSET
       PUSH    H       ; SAVE PTR
       PUSH    B       ; SAVE USER/DISK
       LXI     H,FCBS+1        ; SET NAMES THE SAME
       LXI     D,FCBD+1        ; COPY SOURCE TO DEST
       MVI     B,11    ; 11 BYTES
       CALL    MOVEB
       POP     B       ; RESTORE BC
       POP     H       ; RESTORE PTR
NOSET:
       MOV     A,B     ; GET DISK
       CPI     0FFH
       JNZ     DDISK1
       LDA     CDISK   ; SELECT CURRENT DISK IF DEFAULT
       INR     A       ; ADD 1 FOR FOLLOWING DECREMENT
DDISK1:
       DCR     A
       STA     DDISK
       MOV     A,C     ; GET USER
       CPI     0FFH    ; CURRENT?
       JZ      DUSER0
       CPI     '?'     ; WILD IS CURRENT
       JNZ     DUSER1
DUSER0:
       LDA     CUSER
DUSER1:
       STA     DUSER   ; SET DEST USER
START3:
       CALL    SBLANK  ; SKIP SPACES
       CPI     'M'     ; MULTIPLE OPTION?
       JNZ     START4
       MVI     A,0FFH  ; SET FLAG
       STA     MULT
START4:
       LXI     H,FCBS  ; SET UP SOURCE FCB
       CALL    QCHECK  ; NO AMBIGUOUS ENTRIES PERMITTED
       LXI     H,FCBD  ; SET UP DESTINATION FCB
       CALL    QCHECK  ; NO AMBIGUOUS ENTRIES PERMITTED
MLOOP:
       CALL    BANNER  ; PRINT BANNER
       LDA     MULT    ; MULTIPLE RUNS?
       ORA     A       ; 0=NO
       JZ      MLOOP1
       CALL    PRS1    ; PRINT SOURCE FILE 1
       CALL    PRS2    ; PRINT SOURCE FILE 2
       CALL    PRINT
       DB      CR,LF,'  Change Disks if Desired and Type ^C or A to Abort or '
       DB      '<RETURN> to Continue - ',0
       CALL    CIN     ; GET RESPONSE
       CALL    CAPS    ; CAPITALIZE
       CPI     3       ; ABORT?
       RZ
       CPI     'A'     ; ABORT?
       RZ
       MVI     C,13    ; RESET DISKS
       CALL    BDOS
MLOOP1:
       CALL    VERIFY  ; PERFORM VERIFICATION
       CALL    CRLF    ; NEW LINE
       LDA     MULT    ; MULTIPLE RUNS?
       ORA     A       ; 0=NO
       JNZ     MLOOP   ; CONTINUE IF SO
       RET

;
;  SKIP TO NON-BLANK CHAR
;
SBLANK:
       MOV     A,M     ; GET CHAR
       INX     H       ; PT TO NEXT
       CPI     ' '     ; BLANK?
       JZ      SBLANK
       DCX     H       ; PT TO NON-BLANK
       RET

;
;  PRINT HELP MESSAGE
;
PRHELP:
       CALL    BANNER  ; PRINT BANNER
       CALL    PRINT
       DB      CR,LF
       DB      CR,LF,'COMPARE is used to quickly compare two files for '
       DB      'equality'
       DB      CR,LF
       DB      CR,LF,'COMPARE is invoked by a command like:'
       DB      CR,LF,' COMPARE dir:file1.typ,dir:file2.typ M'
       DB      CR,LF,'where:'
       DB      CR,LF,'   "file1.typ" must be specified and is unambiguous'
       DB      CR,LF,'   "dir:" is an optional named directory (or DU:)'
       DB      CR,LF,'   "file2.typ" is optional and set equal to '
       DB      '"file1.typ" if omitted'
       DB      CR,LF,'   "M" is optional and allows Multiple Runs if given'
       DB      CR,LF
       DB      CR,LF,'Examples:'
       DB      CR,LF,'Command                  Files Compared'
       DB      CR,LF,'COMPARE T.COM,A1:        $$:T.COM, A1:T.COM'
       DB      CR,LF,'COMPARE A:T.COM          A$:T.COM, $$:T.COM'
       DB      CR,LF,'COMPARE A:T.COM,ROOT:    A$:T.COM, ROOT:T.COM'
       DB      CR,LF,'COMPARE A:T.COM,B:S.COM  A$:T.COM, B$:S.COM'
       DB      0
       RET

;
;  CHECK FOR ANY QUESTION MARKS FROM HL+1 TO HL+11
;  AFFECT ONLY AF REGISTERS IF OK
;
QCHECK:
       PUSH    H       ; SAVE HL
       PUSH    B       ; SAVE BC
       INX     H       ; PT TO FIRST CHAR
       MVI     B,11    ; 11 BYTES
       MVI     C,'?'   ; SCAN FOR '?'
QC:
       MOV     A,M     ; GET BYTE
       CMP     C       ; '?'?
       JZ      QC1
       INX     H       ; PT TO NEXT
       DCR     B       ; COUNT DOWN
       JNZ     QC
       POP     B       ; RESTORE
       POP     H
       RET
QC1:
       POP     B       ; RESTORE AND ABORT
       POP     H
       POP     D       ; CLEAR RETURN ADDRESS
       XCHG            ; FCB PTR IN DE
       CALL    BANNER  ; PRINT BANNER
       CALL    CRLF
       CALL    PRFN    ; PRINT FILE NAME
       CALL    PRINT
       DB      '  Ambiguous File Name not Allowed',CR,LF,0
       RET

;
;  PRINT BANNER
;
BANNER:
       CALL    PRINT
       DB      'COMPARE  Version '
       DB      VERS/10+'0','.',(VERS MOD 10)+'0'
       DB      0
       RET

;
;  PRINT NAMES OF SOURCE FILES
;    PRS1 -- SOURCE FILE 1
;    PRS2 -- SOURCE FILE 2
;
PRS1:
       CALL    PRINT
       DB      CR,LF,'Source File 1 -- ',0
       LXI     H,SDISK ; PT TO FIRST BYTE
       CALL    PRUD
       LXI     D,FCBS  ; COMPUTE CRC FOR SOURCE FCB
       JMP     PRFN    ; PRINT FILE NAME
PRS2:
       CALL    PRINT
       DB      CR,LF,'Source File 2 -- ',0
       LXI     H,DDISK ; PT TO FIRST BYTE
       CALL    PRUD
       LXI     D,FCBD  ; COMPUTE CRC FOR DESTINATION FCB
       JMP     PRFN    ; PRINT FILE NAME

;
;  MAIN VERIFY ROUTINE
;
VERIFY:
       CALL    PRS1    ; PRINT SOURCE 1
       LDA     SDISK   ; SELECT DISK AND USER
       MOV     B,A
       LDA     SUSER
       MOV     C,A
       CALL    LOGUD   ; LOG IN DISK AND USER IN B,C
       CALL    CRCCLR  ; CLEAR CRC
       CALL    COMPCRC ; READ IN FILE
       CALL    CRCDONE ; GET CRCK VALUE
       SHLD    CRCVAL  ; SAVE IT
       LDA     BCNT    ; GET OLD BLOCK COUNT
       STA     BCNT1   ; SAVE IT
       CALL    PRS2    ; PRINT NAME OF SOURCE 2
       LDA     DDISK   ; SELECT DISK AND USER
       MOV     B,A
       LDA     DUSER
       MOV     C,A
       CALL    LOGUD   ; LOG IN DISK AND USER IN B,C
       CALL    CRCCLR  ; CLEAR CRC
       CALL    COMPCRC ; READ IN FILE
       LDA     BCNT    ; GET BLOCK COUNT
       MOV     B,A     ; RESULT IN B
       LDA     BCNT1   ; CHECK FOR SAME SIZE
       CMP     B       ; COMPARE BLOCK SIZES
       JNZ     NOMATCH ; NO MATCH IF NOT SAME
       LHLD    CRCVAL  ; GET OLD CRC VALUE
       XCHG            ; VALUE IN DE
       CALL    CRCDONE ; UPDATE COMPLETE
       CALL    COMPHD  ; COMPARE HL TO DE
       JNZ     NOMATCH
       CALL    PRINT
       DB      CR,LF,'** Files are Identical **',0
       RET

;  VERIFY ERROR
NOMATCH:
       CALL    PRINT
       DB      CR,LF,'** Files are Different **',0
       RET

;
;  READ IN FILE AND COMPUTE ITS CRC; DE PTS TO FILE'S FCB
;
COMPCRC:
       CALL    CRCCLR  ; CLEAR CRCK VALUE
       CALL    INITFCB ; INIT FCB FIELDS
       CALL    F$OPEN  ; OPEN FILE
       CPI     0FFH    ; FILE NOT FOUND?
       JNZ     LOAD    ; LOAD DATA AND COMPUTE CRCVAL AND BCNT
       POP     H       ; CLEAR 2 LEVELS OF RETURN ADDRESS
       POP     H
       CALL    CRLF
       CALL    PRFN    ; PRINT FILE NAME
       CALL    PRINT
       DB      '  File Not Found',CR,LF,0
       RET

;
;  LOAD BUFFER FROM FILE WHOSE FCB IS PTED TO BY DE
;    ON OUTPUT, BCNT=NUMBER OF BLOCKS LOADED (UP TO 128) AND
;    CONT=0 IF DONE OR 128 IF NOT DONE
;
LOAD:
       XRA     A       ; A=0
       STA     BCNT    ; SET BLOCK COUNT

;  MAIN CRC EVALUATION LOOP
LOAD1:
       CALL    F$READ  ; READ IN BLOCK
       ORA     A       ; END OF FILE?
       JNZ     LOAD3   ; RETURN
       LXI     H,BUFF  ; PT TO BUFFER TO COMPUTE CRC FROM
       MVI     B,128   ; COPY 128 BYTES
LOAD2:
       MOV     A,M     ; GET BYTE
       CALL    CRCUPD  ; UPDATE CRC
       INX     H       ; PT TO NEXT
       DCR     B       ; COUNT DOWN
       JNZ     LOAD2
       LDA     BCNT    ; GET BLOCK COUNT
       INR     A       ; INCREMENT IT
       STA     BCNT    ; SET IT
       JMP     LOAD1   ; CONTINUE
LOAD3:
       CALL    F$CLOSE ; CLOSE FILE
       RET

;
;  PRINT DISK/USER PTED TO BY HL (2 BYTES)
;
PRUD:
       MOV     A,M     ; GET DISK
       ADI     'A'     ; CONVERT TO LETTER
       CALL    COUT
       INX     H       ; PT TO USER
       MOV     A,M     ; GET USER
       CALL    PADC    ; PRINT AS DEC
       CALL    PRINT
       DB      ': ',0
       RET

;
;  PRINT FILE NAME WHOSE FCB IS PTED TO BY DE
;
PRFN:
       PUSH    H       ; SAVE REGS
       PUSH    D
       PUSH    B
       XCHG            ; FN PTED TO BY HL
       INX     H       ; PT TO FIRST CHAR
       MVI     B,8     ; 8 CHARS
       CALL    PRFN1
       MVI     A,'.'
       CALL    COUT
       MVI     B,3     ; 3 CHARS FOR FILE TYPE
       CALL    PRFN1
       POP     B       ; RESTORE REGS
       POP     D
       POP     H
       RET
PRFN1:
       MOV     A,M     ; GET CHAR
       INX     H       ; PT TO NEXT
       CALL    COUT    ; PRINT
       DCR     B       ; COUNT DOWN
       JNZ     PRFN1
       RET

;
;  BUFFERS
;
MULT:
       DS      1       ; MULTIPLE RUN FLAG (0=NO MULT RUNS)
CDISK:
       DS      1       ; CURRENT DISK
CUSER:
       DS      1       ; CURRENT USER
SDISK:
       DS      1       ; SOURCE DISK (MUST BE FOLLOWED BY SUSER)
SUSER:
       DS      1       ; SOURCE USER
FCBS:
       DS      36      ; SOURCE FCB
DDISK:
       DS      1       ; DEST DISK (MUST BE FOLLOWED BY DUSER)
DUSER:
       DS      1       ; DEST USER
FCBD:
       DS      36      ; DESTINATION FCB
CRCVAL:
       DS      2       ; CRC VALUE
CURDISK:
       DS      1       ; CURRENT DISK NUMBER
BCNT:
       DS      1       ; BUFFER COUNT
BCNT1:
       DS      1       ; SECOND BUFFER COUNT
INLINE:
       DS      200     ; INPUT LINE BUFFER

       END