;    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
; 
; system equates
; 
base	set	0
	if	altcpm
base	set	4200h
	endif		; altcpm
; 
; 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
;
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:	lxi	h,0	; get stack...
	dad	sp	; pointer so we can...
	shld	stack	; save it
	lxi	sp,stack ; initialize local stack
	call	crlf	; turn up a new line
	lda	fcb+1
	cpi	' '	; 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
;
	lxi	h,hitab
	mvi	c,0	; the table index
gloop:	xchg
	lxi	h,0	; init the crc
	mov	a,c
	call	lcrc
	xchg		; de now has the crc, hl pointing into table
	mov	m,d	; store the high byte of crc
	inr	h
	mov	m,e	; store the low byte
	dcr	h
	inx	h	; move to next table entry
	inr	c	; next index
	jnz	gloop
	lda	fcb2+1	; get option
	sta	fflag	; save it for later
	cpi	'F'	; file wanted?
	jnz	again	; no, skip file init
	xra	a
	sta	fcbcrcfile+12	; clear extent
	sta	fcbcrcfile+32	; clear current record count
	lxi	h,crcfilesiz	; set buffer size
	shld	crcfilelen
	lxi	h,0		; set next to fill
	shld	crcfileptr
	mvi	c,delet		; delete file function
	lxi	d,fcbcrcfile	; delete 'old' crcklist file
	call	bdos
	mvi	c,make		; make file function
	lxi	d,fcbcrcfile	; make 'new' crcklist file
	call	bdos
	inr	a		; make ok?
	jnz	again
	mvi	c,print		; print string function
	lxi	d,dir$full	; indicate that directory is full
	call	bdos
	jmp	filerr
;
;
;
putcrcfile:
;
	push	psw		; save output character
	lhld	crcfilelen	; get current buffer length
	xchg			; de has length
	lhld	crcfileptr	; load next to get/put to hl
	mov	a,l		; compute current length
	sub	e 	
	mov	a,h
	sbb	d		; carry if next < length
	jc	putcrc4		; carry if length > current
	lxi	h,0		; end of buffer, fill (empty) buffers	
	shld	crcfileptr	; clear next to get/put
;
putcrc1:			; process next disk sector
;
	xchg			; file pointer to de
	lhld	crcfilelen	; hl is maximum buffer length
	mov	a,e		; compute next length
	sub	l		; to get carry, if more fill
	mov	a,d
	sbb	h		
	jnc	putcrc3
	lhld	crcfileadr	; got carry, more to fill yet
	dad	d		; hl is next buffer address
	xchg
	mvi	c,stdma		; set dma address
	call	bdos
	lxi	d,fcbcrcfile	; fcb address to de
	mvi	c,write		; file write
	call	bdos
	ora	a		; check return code
	jnz	putcrc2		; end-of-file yet?
	lxi	d,buf$siz	; not eof, increment length by 128
	lhld	crcfileptr	; next to fill
	dad	d
	shld	crcfileptr	; save new pointer
	jmp	putcrc1		; process another sector
;
putcrc2:			; got end-of-file
;
	mvi	c,print		; print string function
	lxi	d,dsk$full	; disk is full
	call	bdos
	pop	psw		; clean stack
	jmp	filerr		; file error, exit
;
putcrc3:			; end of buffer, reset dma and pointer
;
	lxi	d,tbuf		; point to temporary buffer
	mvi	c,stdma		; set dma function
	call	bdos
	lxi	h,0		; reset pointer for next to get
	shld	crcfileptr
;
putcrc4:			; process the next character
;
	xchg			; index to get/put in de
	lhld	crcfileadr	; base of buffer
	dad	d		; address of character in hl
	xchg			; and swap to de
	pop	psw		; get save character
	stax	d		; character to buffer
	lhld	crcfileptr	; index to get/put
	inx	h		; and update for next character
	shld	crcfileptr
	ret
; 
again:	lxi	sp,stack; re-init stack pointer
	call	mfname	; search for names
	jnc	namtst	; another found, print name
	lda	mfflg1	; nothing found, check...
	ora	a	; ... first time flag
	jz	done	; at least one was found
	call	abexit	; print msg, then exit
	db	'++File Not Found++$'
; 
done:	lda	fflag	; see if we're making file
	cpi	'F'
	jnz	done2	; no, skip the file stuff
; 
; close crcklist.$$$
;
closecrc:
;
	lhld	crcfileptr
	mov	a,l
 	ani	07fh
	jnz	close1
	shld	crcfilelen
close1:	mvi	a,eof
	push	psw
	call	putcrcfile
	pop	psw
	jnz	closecrc
	mvi	c,close
	lxi	d,fcbcrcfile
	call	bdos
	inr	a
	jnz	erase
	mvi	c,print
	lxi	d,no$close
	call	bdos
; 
; erase any existing old file
;
erase:	mvi	c,delet
	lxi	d,fcbfinal
	call	bdos
; 
; rename crcklist.$$$ to crcklist.crc
;
	lxi	h,fcbcrcfile
	lxi	d,fcbfinal
	push	h
	lxi	b,16
	dad	b
;
mov$name:
;
	ldax	d
	mov	m,a
	inx	d
 	inx	h
	dcr	c
	jnz	mov$name
	pop	d
	mvi	c,renam
	call	bdos
; 
; now exit to cp/m
;
done2:	call	msgexit	; print done, then exit
	db	cr,lf,'Done$'
; 
; test for names to ignore
; 
namtst:	if	nosys
	lda	fcb+10	; get sys attribute
	ani	80h	; 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).
; 
	lxi	h,fcb+9	; point to filetype in fcb
	call	tstbad	; check for .$$$ files
	jz	again	; if zero flag, ignore them
; 
; move 8 characters from fcb+1 to fname
;
	lxi	h,fcb+1
	lxi	d,fname
	lxi	b,8
	call	mover
;
; move 3 characters from fcb+9 to fname+9
;
	lxi	h,fcb+9
	lxi	d,fname+9
	lxi	b,3
	call	mover
;
; now print filename.type
;
	call	ilprt	; print:
;
fname:	db	'xxxxxxxx.xxx',tab,'CRC = ',0
; 
; open the file
;
	lxi	d,fcb
	mvi	c,open
	call	bdos
	inr	a
	jnz	rdinit
	call	abexit
	db	'++Open Failed++$'
; 
; initialize crc to zero and set bufad to cause initial read
;
rdinit:	lxi	h,0
 	shld	rem	; init remainder to zero
	lxi	h,base+100h
	shld	bufad	; init buffer adrs
; 
; this is the read loop
;
readit:	lhld	bufad
	mov	a,h	; time to read?
	cpi	base shr 8
	jz	nord	; no read
	mvi	c,cstat
	call	bdos	; check for operator abort
	ora	a
	jz	read2	; nothing from operator
	mvi	c,rdcon
	call	bdos	; get character inputted
	cpi	'C'-40h	; control c?
	jz	abext2	; yes exit
; 
read2:	lxi	d,fcb
	mvi	c,read	; read another sector of file
	call	bdos
	ora	a	; check return code
	jnz	finish	; error or eof
	lxi	h,tbuf	; buffer location
; 
nord:	mov	a,m	; get file character
	inx	h
	shld	bufad
	lhld	rem	; pick up the partial remainder
; 
; table lookup method for crc generation
;
	xchg		; de now has the partial
	mvi	b,0
	xra	d
	mov	c,a
	lxi	h,hitab
	dad	b
	mov	a,m
	xra	e
	mov	d,a
	inr	h
	mov	e,m
	xchg
	shld	rem
	jmp	readit	; go read more characters
; 
;
;
finish:	cpi	1	; normal end-of-file?
	jnz	filerr	; no, it was a read error
	lda	rem+1	; get msp of crc
	call	hexo	; print it
	lda	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	b
	mvi	b,8
	xra	h
	mov	h,a
loop:	dad	h
	jnc	skip
	mvi	a,himsk
	xra	h
	mov	h,a
	mvi	a,lomsk
	xra	l
	mov	l,a
skip:	dcr	b
	jnz	loop
	pop	b
	ret
; 
; hex output
; 
hexo:	push	psw	; save for right digit
	rar		; right..
	rar		; ..justify..
	rar		; ..left..
	rar		; ..digit..
	call	nibbl	; print left digit
	pop	psw	; restore right
; 
nibbl:	ani	0fh	; isolate digit
	cpi	10	; is is <10?
	jc	isnum	; yes, not alpha
	adi	7	; add alpha bias
; 
isnum:	adi	'0'	; make printable
	jmp	type	; print it, then return
; 
;
;
; inline print routine
; 
ilprt:	xthl		; save hl, get msg
; 
ilplp:	mov	a,m	; get char
	call	type	; output it
	inx	h	; point to next
	mov	a,m	; test
	ora	a	; ..for end
	jnz	ilplp
	xthl		; restore hl, ret addr
	ret		; ret past msg
; 
;
;
; send carriage return, line feed to output
; 
crlf:	mvi	a,cr	; carriage return
	call	type
	mvi	a,lf	; line feed, fall into 'type'
; 
; send character in a register to output
; 
type:	push	b
	push	d
	push	h
	ani	7fh	; strip parity bit
	mov	e,a
	push	d
	call	wrfile	; write to file if requested
	pop	d
	mvi	c,wrcon	; send character to console
	call	bdos
	pop	h
	pop	d
	pop	b
	ret
; 
;
;
; write character in e register to output file
; 
wrfile:	lda	fflag	; get file trigger
	cpi	'F'	; is it set?
	rnz		; no, return
	mov	a,e	; 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
	mvi	c,stdma
	lxi	d,tbuf
	call	bdos
	xra	a
	sta	fcbext
	sta	fcbrno
;
; if first time
;
	lda	mfflg1
	ora	a
	jz	mfn01
;
; save the requested name
;
	lxi	h,fcb
	lxi	d,mfreq
	lxi	b,12
 	call	mover
	lda	fcb
	sta	mfcur	; save disk in curr fcb
;
; srchf requested name
;
	lxi	h,mfreq
	lxi	d,fcb
	lxi	b,12
	call	mover
	mvi	c,srchf
	lxi	d,fcb
	call	bdos
;
; else
;
	jmp	mfn02
; 
mfn01:			; srchf current name
	lxi	h,mfcur
	lxi	d,fcb
	lxi	b,12
	call	mover
	mvi	c,srchf
	lxi	d,fcb
	call	bdos
;
; srchn requested name
;
	lxi	h,mfreq
	lxi	d,fcb
	lxi	b,12
	call	mover
	mvi	c,srchn
	lxi	d,fcb
	call	bdos
;
; endif
;
mfn02:			; return carry if not found
	inr	a
	stc
	rz
;
; move name found to current name
;
	dcr	a
	ani	3
	add	a
	add	a
	add	a
	add	a
	add	a
	adi	81h
	mov	l,a
	mvi	h,0
	push	h	; save name pointer
	lxi	d,mfcur+1
	lxi	b,11
	call	mover
;
; move name found to fcb
;
	pop	h
	lxi	d,fcb+1
	lxi	b,11
	call	mover
;
; setup fcb
;
 	xra	a
	sta	fcbext
	sta	fcbrno
	sta	mfflg1	; turn off 1st time sw
	ret
;
;
; 
; check for .$$$ files
; 
tstbad:	call	testit	; check first one for '$'
	rnz		; no, return
	call	testit	; check second one
	rnz		; no, return
testit:	mov	a,m
	ani	7fh	; strip attribute
	cpi	'$'	; check for $ filetype
	inx	h
	ret
;
;
; 
; move (bc) bytes from (hl) to (de)
; 
mover:	mov	a,m
	stax	d
	inx	h
	inx	d
	dcx	b
	mov	a,b
	ora	c
	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	d	; get msg adrs
	mvi	c,print
	call	bdos	; print msg
; 
abext2:	lda	fflag	; see if we are making file
	cpi	'F'
	jnz	abext5	; no file, skip file stuff
abext3:	lhld	crcfileptr
	mov	a,l
	ani	07fh
	jnz	abext4
	shld	crcfilelen
abext4:	mvi	a,eof
	push	psw
	call	putcrcfile
	pop	psw
	jnz	abext3
 	mvi	c,close
	lxi	d,fcbcrcfile
	call	bdos
	inr	a
	jnz	era$crc
	mvi	c,print
	lxi	d,no$close
	call	bdos
;
; erase incomplete file
;
era$crc:
;
	mvi	c,delet
	lxi	d,fcbcrcfile
	call	bdos
; 
abext5:	call	erxit	; print msg, exit
	db	cr,lf,cr,lf,'++Aborted++$'
;
; exit with message
; 
msgexit:equ	$	; exit with "informational" message
erxit:	pop	d	; get msg
	mvi	c,print
	call	bdos
; 
; exit, restoring stack and return to ccp
; 
exit:	lhld	stack
	sphl
	ret		; to ccp
;
;
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
; 
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:	ds	12	; requested name
mfcur:	ds	12	; current name
bufad:	ds	2	; read buffer address
;
	ds	60	; stack area
stack:	equ	$
oldstk:	ds	2	; old stack pointer saved here
;
hitab:	ds	512	; the 2 tables for crc lookup
;
crcfilelen:
;
	dw	crcfilesiz
;
crcfileptr:
;
	ds	2
; 
; build fcb for final name of crcklist.crc
;
fcbfinal:
;
	db	0,'CRCKLISTCRC'
	db	0
	ds	20
;
; 'declare' fcb for output file (temporarily named crcklist.$$$)
;
fcbcrcfile:
;
	db	0,'CRCKLIST$$$'
	db	0
	ds	20
;
crcfileadr:
;
	dw	crcfileadr+2	; buffer all crc file data here
;
;
; 
	end
