;    CRCK.ASM Version 5.1 (Originally by: Keith Petersen, W8SDZ)
;
; CRCK  is  a  program to read any CP/M file and  print  a  cyclic-
; redundancy-check number based on the CCITT standard polynominal:
;
;           x^16 + x^15 + x^13 + x^7 + x^4 + x^2 + x + 1
;
; Useful for checking accuracy of file transfers, and more accurate
; than a simple checksum.   Optionally will write an output file to
; the  default drive,  listing the CRC's of all files checked in  a
; single session.
;
; Commands:   crck [drive:]<filename.filetype> [f]<cr>
;
; Examples:
;
; crck myfile.asm<cr>      (check only myfile.asm)
; crck *.asm<cr>           (check all .asm files
; crck *.* f<cr>           (check all files, make file of results)
;
;
; Program  updates/fixes  (these  are written in reverse  order  to
; minimize reading time to find latest update):
;
; 04/10/82 version 5.1, Kelly Smith
;
; Removed requirement for MAC.ASM and SEQIO.LIB for assembly
;
; 11/27/81 version 5.0, Dave Barker
;
; All  earlier  versions of CRCK.ASM (up to at least  Ver.  4.2  of
; 10/06/80)  seen by this writer (DAB) have a serious flaw  in  the
; algorithm  used to generate the CRC value.   Mr.  Petersen used a
; routine from "EDN" magazine,  June 5, 1979.  Although the routine
; published  in  EDN was a workable one,  the way in which  it  was
; applied in CRCK.ASM was incorrect (i.e.  the routine should  have
; been called 8 times per byte,  each time with only one bit of the
; message in the A register,  then,  at the end of the file, 2 null
; bytes  should  have  been processed as if they were part  of  the
; file). The method that is used in CRCK.ASM Version 5.0 is a table
; lookup method.  Instead of calling a routine 8 times each byte of
; the  message  is processed in one short piece  of  straight  line
; code.    The table that is used in this method is first generated
; during initialization.
;
;                           - Validity -
;
; Version 5.0 generates exactly the same CRC value that the earlier
; versions  would  have  generated if they had correctly  used  the
; algorithm.   The message (the file) is processed in the order: MS
; bit  of the MS byte first (if the file were to be processed as  a
; serial  data transmission,  then the LS bit of the MS byte  would
; come  first  --> the order in which it is transmitted  through  a
; UART).
;
; Note:   Usually, the CRC of a message is appended to the end of a
; message  when it is sent.   This causes the resultant CRC at  the
; receiving  end  to be zero (this is the reason that the  2  dummy
; null  bytes are added to the end of the message when the  CRC  is
; generated or checked).
;
;
;
; define true and false
;
false   EQU     0
true    EQU     not false
;
; conditional assembly switches
;
stdcpm  EQU     true                    ; true is standard cp/m
altcpm  EQU     false                   ; true is h8 or trs-80
nosys   EQU     false                   ; true if sys files not wanted
;
M       EQU     Byte Ptr 0[BX]
;
; system equates
;
base    EQU     0
;
; define write buffer size (presently set for 8k)
;
bsize   EQU     8*1024                  ; disk write buffer size
;
; bdos equates
;
rdcon   EQU     1
wrcon   EQU     2
print   EQU     9
cstat   EQU     11
open    EQU     15
close   EQU     16
srchf   EQU     17
srchn   EQU     18
delet   EQU     19
read    EQU     20
write   EQU     21
make    EQU     22
renam   EQU     23
stdma   EQU     26
stbas   EQU     51
;
bdos    EQU     base+5
;
fcb     EQU     base+5ch
fcbext  EQU     fcb+12
fcbrno  EQU     fcb+32
fcb2    EQU     base+6ch
;
tbuf    EQU     base+80h                ; temporary buffer (default) address
buf@siz EQU     80h                     ; buffer size (128 bytes)
;
crcfilesiz EQU  2000h
;
tab     EQU     09h                     ; tab character
lf      EQU     0ah                     ; line feed character
cr      EQU     0dh                     ; carriage return character
eof     EQU     'Z'-40h                 ; end-of-file character
;
; CCIT CRC polynomial mask bytes
;
himsk   EQU     0a0h                    ; high mask byte
lomsk   EQU     097h                    ; low mask byte
;
;
;
; program starts here
;
    ORG        base+100h
;
begin:  MOV     SP,(offset stktop)      ; make local stack
       CALL    crlf                    ; turn up a new line
       MOV     AL,Byte Ptr .fcb+1
       CMP     AL,' '                  ; see if name there
       JNZ     begin2                  ; yes, continue
       CALL    erxit                   ; print msg, then exit
       DB      '++No File Name Specified++',cr,lf,'$'
;
begin2: CALL    ilprt                   ; print:
       DB      '--------- CRCK Ver 5.1 ---------'
       DB      cr,lf
       DB      'CTRL-S to Pause, CTRL-C to Abort'
       DB      cr,lf,cr,lf,0
;
; generate the lookup table for fast crc
;
       MOV     BX,(Offset hitab)
       MOV     CL,0                    ; the table index
gloop:  XCHG    BX,DX
       MOV     BX,0                    ; init the crc
       MOV     AL,CL
       CALL    lcrc
       XCHG    BX,DX                   ; de now has the crc, hl pointing into table
       MOV     M,DH                    ; store the high byte of crc
       INC     BH
       MOV     M,DL                    ; store the low byte
       DEC     BH
       INC     BX                      ; move to next table entry
       INC     CL                      ; next index
       JNZ     gloop
       MOV     AL,Byte Ptr .fcb2+1     ; get option
       MOV     Byte Ptr fflag,AL       ; save it for later
       CMP     AL,'F'                  ; file wanted?
       JZ      L_1
       JMP     again                   ; no, skip file init
L_1:
       XOR     AL,AL
       MOV     Byte Ptr fcbcrcfile+12,AL       ; clear extent
       MOV     Byte Ptr fcbcrcfile+32,AL       ; clear current record count
       MOV     BX,crcfilesiz           ; set buffer size
       MOV     Word Ptr crcfilelen,BX
       MOV     BX,0                    ; set next to fill
       MOV     Word Ptr crcfileptr,BX
       MOV     CL,delet                ; delete file function
       MOV     DX,(Offset fcbcrcfile)  ; delete 'old' crcklist file
       INT     224
       MOV     CL,make                 ; make file function
       MOV     DX,(Offset fcbcrcfile)  ; make 'new' crcklist file
       INT     224
       INC     AL                      ; make ok?
       JZ      L_2
       JMP     again
L_2:
       MOV     CL,print                ; print string function
       MOV     DX,(Offset dir@full)    ; indicate that directory is full
       INT     224
       JMP     filerr
;
;
;
putcrcfile:
;
       LAHF                            ; save output character
       XCHG    AL,AH
       PUSH    AX
       MOV     BX,Word Ptr crcfilelen  ; get current buffer length
       XCHG    BX,DX                   ; de has length
       MOV     BX,Word Ptr crcfileptr  ; load next to get/put to hl
       MOV     AL,BL                   ; compute current length
       SUB     AL,DL
       MOV     AL,BH
       SBB     AL,DH                   ; carry if next < length
       JB      putcrc4                 ; carry if length > current
       MOV     BX,0                    ; end of buffer, fill (empty) buffers
       MOV     Word Ptr crcfileptr,BX  ; clear next to get/put
;
putcrc1:                                ; process next disk sector
;
       XCHG    BX,DX                   ; file pointer to de
       MOV     BX,Word Ptr crcfilelen  ; hl is maximum buffer length
       MOV     AL,DL                   ; compute next length
       SUB     AL,BL                   ; to get carry, if more fill
       MOV     AL,DH
       SBB     AL,BH
       JNB     putcrc3
       MOV     BX,Word Ptr crcfileadr  ; got carry, more to fill yet
       ADD     BX,DX                   ; hl is next buffer address
       XCHG    BX,DX
       MOV     CL,stdma                ; set dma address
       INT     224
       MOV     CL,stbas                ; set dma base
       MOV     DX,DS
       INT     224
       MOV     DX,(Offset fcbcrcfile)  ; fcb address to de
       MOV     CL,write                ; file write
       INT     224
       OR      AL,AL                   ; check return code
       JNZ     putcrc2                 ; end-of-file yet?
       MOV     DX,buf@siz              ; not eof, increment length by 128
       MOV     BX,Word Ptr crcfileptr  ; next to fill
       ADD     BX,DX
       MOV     Word Ptr crcfileptr,BX  ; save new pointer
       JMPS    putcrc1                 ; process another sector
;
putcrc2:                                ; got end-of-file
;
       MOV     CL,print                ; print string function
       MOV     DX,(Offset dsk@full)    ; disk is full
       INT     224
       POP     AX                      ; clean stack
       XCHG    AL,AH
       JMP     filerr                  ; file error, exit
;
putcrc3:                                ; end of buffer, reset dma and pointer
;
       MOV     DX,tbuf                 ; point to temporary buffer
       MOV     CL,stdma                ; set dma function
       INT     224
       MOV     CL,stbas                ; set dma base
       MOV     DX,DS
       INT     224
       MOV     BX,0                    ; reset pointer for next to get
       MOV     Word Ptr crcfileptr,BX
;
putcrc4:                                ; process the next character
;
       XCHG    BX,DX                   ; index to get/put in de
       MOV     BX,Word Ptr crcfileadr  ; base of buffer
       ADD     BX,DX                   ; address of character in hl
       XCHG    BX,DX                   ; and swap to de
       POP     AX                      ; get save character
       XCHG    AL,AH
       SAHF
       MOV     DI,DX                   ; character to buffer
       MOV     [DI],AL
       MOV     BX,Word Ptr crcfileptr  ; index to get/put
       LAHF                            ; and update for next character
       INC     BX
       SAHF
       MOV     Word Ptr crcfileptr,BX
       RET
;
again:  MOV     SP,(offset stktop)      ; make local stack
       CALL    mfname                  ; search for names
       JNAE    L_3
       JMP     namtst                  ; another found, print name
L_3:
       MOV     AL,Byte Ptr mfflg1      ; nothing found, check...
       OR      AL,AL                   ; ... first time flag
       JZ      done                    ; at least one was found
       CALL    abexit                  ; print msg, then exit
       DB      '++File Not Found++$'
;
done:   MOV     AL,Byte Ptr fflag       ; see if we're making file
       CMP     AL,'F'
       JNZ     done2                   ; no, skip the file stuff
;
; close crcklist.$$$
;
closecrc:
;
       MOV     BX,Word Ptr crcfileptr
       MOV     AL,BL
       AND     AL,07fh
       JNZ     close1
       MOV     Word Ptr crcfilelen,BX
close1: MOV     AL,eof
       LAHF
       XCHG    AL,AH
       PUSH    AX
       XCHG    AL,AH
       CALL    putcrcfile
       POP     AX
       XCHG    AL,AH
       SAHF
       JNZ     closecrc
       MOV     CL,close
       MOV     DX,(Offset fcbcrcfile)
       INT     224
       INC     AL
       JNZ     erase
       MOV     CL,print
       MOV     DX,(Offset no@close)
       INT     224
;
; erase any existing old file
;
erase:  MOV     CL,delet
       MOV     DX,(Offset fcbfinal)
       INT     224
;
; rename crcklist.$$$ to crcklist.crc
;
       MOV     BX,(Offset fcbcrcfile)
       MOV     DX,(Offset fcbfinal)
       PUSH    BX
       MOV     CX,16
       LAHF
       ADD     BX,CX
       RCR     SI,1
       SAHF
       RCL     SI,1
;
mov@name:
;
       MOV     SI,DX
       MOV     AL,[SI]
       MOV     M,AL
       LAHF
       INC     DX
       SAHF
       LAHF
       INC     BX
       SAHF
       DEC     CL
       JNZ     mov@name
       POP     DX
       MOV     CL,renam
       INT     224
;
; now exit to cp/m
;
done2:  CALL    erxit                   ; print done, then exit
       DB      cr,lf,'Done$'
;
; test for names to ignore
;
namtst:
;
       if      nosys                   ; if $SYS file, ignore it
       MOV     AL,Byte Ptr .fcb+10     ; get $SYS file attribute
       AND     AL,080h                 ; is it $SYS?
       JNZ     again                   ; yes, ignore this file
       endif                           ; nosys
;
; ignore files with .$$$ filetype (they are usually
; zero-length and clutter up our display.  we also
; want to ignore our own crcklist.$$$ temporary file).
;
       MOV     BX,fcb+9                ; point to filetype in fcb
       CALL    tstbad                  ; check for .$$$ files
       JNZ     L_4
       JMP     again                   ; if zero flag, ignore them
L_4:
;
; move 8 characters from fcb+1 to fname
;
       MOV     BX,fcb+1
       MOV     DX,(Offset fname)
       MOV     CX,8
       CALL    mover
;
; move 3 characters from fcb+9 to fname+9
;
       MOV     BX,fcb+9
       MOV     DX,(Offset fname)+9
       MOV     CX,3
       CALL    mover
;
; now print filename.type
;
       CALL    ilprt                   ; print:
;
fname   DB      'xxxxxxxx.xxx',tab,'CRC = ',0
;
; open the file
;
       MOV     DX,fcb
       MOV     CL,open
       INT     224
       INC     AL
       JNZ     rdinit
       CALL    abexit
       DB      '++Open Failed++$'
;
; initialize crc to zero and set bufad to cause initial read
;
rdinit: MOV     BX,0
       MOV     Word Ptr rem,BX         ; init remainder to zero
       MOV     BX,base+100h
       MOV     Word Ptr bufad,BX       ; init buffer adrs
;
; this is the read loop
;
readit: MOV     BX,Word Ptr bufad
       MOV     AL,BH                   ; time to read?
       CMP     AL,base shr 8
       JZ      nord                    ; no read
       MOV     CL,cstat
       INT     224                     ; check for operator abort
       OR      AL,AL
       JZ      read2                   ; nothing from operator
       MOV     CL,rdcon
       INT     224                     ; get character inputted
       CMP     AL,'C'-40h              ; control c?
       JNZ     L_5
       JMP     abext2                  ; yes exit
L_5:
;
read2:  MOV     DX,fcb
       MOV     CL,read                 ; read another sector of file
       INT     224
       OR      AL,AL                   ; check return code
       JNZ     finish                  ; error or eof
       MOV     BX,tbuf                 ; buffer location
;
nord:   MOV     AL,M                    ; get file character
       INC     BX
       MOV     Word Ptr bufad,BX
       MOV     BX,Word Ptr rem         ; pick up the partial remainder
;
; table lookup method for crc generation
;
       XCHG    BX,DX                   ; de now has the partial
       MOV     CH,0
       XOR     AL,DH
       MOV     CL,AL
       MOV     BX,(Offset hitab)
       ADD     BX,CX
       MOV     AL,M
       XOR     AL,DL
       MOV     DH,AL
       INC     BH
       MOV     DL,M
       XCHG    BX,DX
       MOV     Word Ptr rem,BX
       JMPS    readit                  ; go read more characters
;
;
;
finish: CMP     AL,1                    ; normal end-of-file?
       JNZ     filerr                  ; no, it was a read error
       MOV     AL,Byte Ptr rem+1       ; get msp of crc
       CALL    hexo                    ; print it
       MOV     AL,Byte Ptr rem         ; get lsp of crc
       CALL    hexo                    ; print it
       CALL    crlf                    ; turn up new line
       JMP     again                   ; see if more files to do
;
filerr: CALL    abexit                  ; abort because of file read error
       DB      '++File Read Error++$'
;
;  hl contains the partial, a the character to be crc'd
;
lcrc:   PUSH    CX
       MOV     CH,8
       XOR     AL,BH
       MOV     BH,AL
loop:   SHL     BX,1
       JNB     skip
       MOV     AL,himsk
       XOR     AL,BH
       MOV     BH,AL
       MOV     AL,lomsk
       XOR     AL,BL
       MOV     BL,AL
skip:   DEC     CH
       JNZ     loop
       POP     CX
       RET
;
; hex output
;
hexo:   LAHF                            ; save for right digit
       XCHG    AL,AH
       PUSH    AX
       XCHG    AL,AH
       RCR     AL,1                    ; right..
       RCR     AL,1                    ; ..justify..
       RCR     AL,1                    ; ..left..
       RCR     AL,1                    ; ..digit..
       CALL    nibbl                   ; print left digit
       POP     AX                      ; restore right
       XCHG    AL,AH
;
nibbl:  AND     AL,0fh                  ; isolate digit
       CMP     AL,10                   ; is is <10?
       JB      isnum                   ; yes, not alpha
       ADD     AL,7                    ; add alpha bias
;
isnum:  ADD     AL,'0'                  ; make printable
       JMPS    display                 ; print it, then return
;
;
;
; inline print routine
;
ilprt:  MOV     BP,SP                   ; save hl, get msg
       XCHG    BX,[BP]
;
ilplp:  MOV     AL,M                    ; get char
       CALL    display                 ; output it
       INC     BX                      ; point to next
       MOV     AL,M                    ; test
       OR      AL,AL                   ; ..for end
       JNZ     ilplp
       INC     BX                      ; bump pointer for return address
       MOV     BP,SP                   ; restore hl, ret addr
       XCHG    BX,[BP]
       RET                             ; ret past msg
;
;
;
; send carriage return, line feed to output
;
crlf:   MOV     AL,cr                   ; carriage return
       CALL    display
       MOV     AL,lf                   ; line feed, fall into 'type'
;
; send character in a register to output
;
display:
;
       PUSH    CX
       PUSH    DX
       PUSH    BX
       AND     AL,7fh                  ; strip parity bit
       MOV     DL,AL
       PUSH    DX
       CALL    wrfile                  ; write to file if requested
       POP     DX
       MOV     CL,wrcon                ; send character to console
       INT     224
       POP     BX
       POP     DX
       POP     CX
       RET
;
;
;
; write character in e register to output file
;
wrfile: MOV     AL,Byte Ptr fflag       ; get file trigger
       CMP     AL,'F'                  ; is it set?
       JZ      L_6
       RET                             ; no, return
L_6:
       MOV     AL,DL                   ; get character back
       CALL    putcrcfile
       RET
;
; multi-file access subroutine.  allows processing
; of multiple files (i.e. *.asm) from disk.  this
; routine builds the proper name in the fcb each
; time it is called. carry is set if no more names
; can be found.
;
mfname:                         ; init dma addr and fcb
       MOV     CL,stdma
       MOV     DX,tbuf
       INT     224
       MOV     CL,stbas                ; set dma base
       MOV     DX,DS
       INT     224
       XOR     AL,AL
       MOV     Byte Ptr .fcbext,AL
       MOV     Byte Ptr .fcbrno,AL
;
; if first time
;
       MOV     AL,Byte Ptr mfflg1
       OR      AL,AL
       JZ      mfn01
;
; save the requested name
;
       MOV     BX,fcb
       MOV     DX,(Offset mfreq)
       MOV     CX,12
       CALL    mover
       MOV     AL,Byte Ptr .fcb
       MOV     Byte Ptr mfcur,AL       ; save disk in curr fcb
;
; srchf requested name
;
       MOV     BX,(Offset mfreq)
       MOV     DX,fcb
       MOV     CX,12
       CALL    mover
       MOV     CL,srchf
       MOV     DX,fcb
       INT     224
;
; else
;
       JMPS    mfn02
;
mfn01:                                  ; srchf current name
       MOV     BX,(Offset mfcur)
       MOV     DX,fcb
       MOV     CX,12
       CALL    mover
       MOV     CL,srchf
       MOV     DX,fcb
       INT     224
;
; srchn requested name
;
       MOV     BX,(Offset mfreq)
       MOV     DX,fcb
       MOV     CX,12
       CALL    mover
       MOV     CL,srchn
       MOV     DX,fcb
       INT     224
;
; endif
;
mfn02:                                  ; return carry if not found
       INC     AL
       STC
       JNZ     L_7
       RET
L_7:
;
; move name found to current name
;
       DEC     AL
       AND     AL,3
       ADD     AL,AL
       ADD     AL,AL
       ADD     AL,AL
       ADD     AL,AL
       ADD     AL,AL
       ADD     AL,81h
       MOV     BL,AL
       MOV     BH,0
       PUSH    BX                      ; save name pointer
       MOV     DX,(Offset mfcur)+1
       MOV     CX,11
       CALL    mover
;
; move name found to fcb
;
       POP     BX
       MOV     DX,fcb+1
       MOV     CX,11
       CALL    mover
;
; setup fcb
;
       XOR     AL,AL
       MOV     Byte Ptr .fcbext,AL
       MOV     Byte Ptr .fcbrno,AL
       MOV     Byte Ptr mfflg1,AL      ; turn off 1st time sw
       RET
;
;
;
; check for .$$$ files
;
tstbad: CALL    testit                  ; check first one for '$'
       JZ      L_8
       RET                             ; no, return
L_8:
       CALL    testit                  ; check second one
       JZ      L_9
       RET                             ; no, return
L_9:
testit: MOV     AL,M
       AND     AL,7fh                  ; strip attribute
       CMP     AL,'$'                  ; check for $ filetype
       LAHF
       INC     BX
       SAHF
       RET
;
;
;
; move (bc) bytes from (hl) to (de)
;
mover:  MOV     AL,M
       MOV     SI,DX
       MOV     [SI],AL
       INC     BX
       INC     DX
       DEC     CX
       MOV     AL,CH
       OR      AL,CL
       JNZ     mover
       RET
;
;
;
; aborted - print reason.  if making output file,
; close the incomplete file to update cp/m's bit map,
; then erase it.
;
abexit: POP     DX                      ; get msg adrs
       MOV     CL,print
       INT     224                     ; print msg
;
abext2: MOV     AL,Byte Ptr fflag       ; see if we are making file
       CMP     AL,'F'
       JNZ     abext5                  ; no file, skip file stuff
abext3: MOV     BX,Word Ptr crcfileptr
       MOV     AL,BL
       AND     AL,07fh
       JNZ     abext4
       MOV     Word Ptr crcfilelen,BX
abext4: MOV     AL,eof
       LAHF
       XCHG    AL,AH
       PUSH    AX
       XCHG    AL,AH
       CALL    putcrcfile
       POP     AX
       XCHG    AL,AH
       SAHF
       JNZ     abext3
       MOV     CL,close
       MOV     DX,(Offset fcbcrcfile)
       INT     224
       INC     AL
       JNZ     era@crc
       MOV     CL,print
       MOV     DX,(Offset no@close)
       INT     224
;
; erase incomplete file
;
era@crc:
;
       MOV     CL,delet
       MOV     DX,(Offset fcbcrcfile)
       INT     224
;
abext5: CALL    erxit                   ; print msg, exit
       DB      cr,lf,cr,lf,'++Aborted++$'
;
; exit with message
;
erxit:  POP     DX                      ; get msg
       MOV     CL,print
       INT     224
;
; exit, via system warm boot
;
exit:   mov     cl,0    ; warm boot to cp/m
       mov     dl,0
       int 224
;
;
;
dir@full DB     cr,lf
       DB      '++No Directory Space for CRC File++'
       DB      '$'
;
dsk@full DB     cr,lf
       DB      '++No Disk Space for CRC File++'
       DB      '$'
;
no@close DB     cr,lf
       DB      '++Cannot Close CRC File++'
       DB      '$'

;
; program storage area
;
       RS      64              ; 32 level local stack
stktop  equ     $               ; top of local stack
;
fflag   DB      0                       ; file write request flag
rem     DW      0                       ; crc remainder storage
mess    DB      0                       ; crc message char goes here
mfflg1  DB      1                       ; 1st time switch
mfreq   RS      12                      ; requested name
mfcur   RS      12                      ; current name
bufad   RS      2                       ; read buffer address
;
hitab   RS      512                     ; the 2 tables for crc lookup
;
crcfilelen      DW      crcfilesiz
;
crcfileptr      RS      2
;
; build fcb for final name of crcklist.crc
;
fcbfinal        DB      0,'CRCKLISTCRC'
               DB      0
               RS      20
;
; 'declare' fcb for output file (temporarily named crcklist.$$$)
;
fcbcrcfile      DB      0,'CRCKLIST$$$'
               DB      0
               RS      20
;
crcfileadr      DW      crcfileadr+2    ; buffer all crc file data here
               RS      02000h          ; force buffer area
               DB      0               ; tag 'end' for GENCMD
;
;
;
       END