;
;		CRCK.ASM version 4.2
;	      by Keith Petersen, W8SDZ
;		 (revised 10/6/80)
;
;----> NOTE: MUST BE ASSEMBLED BY MAC <----
;
;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.
;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]
;
;Examples:
;	CRCK MYFILE.ASM	;CHECK ONLY MYFILE.ASM
;	CRCK *.ASM	;CHECK ALL .ASM FILES
;	CRCK *.* F	;CHECK ALL FILES, MAKE FILE OF RESULTS
;
;Program updates/fixes (these are written in reverse
;order to minimize reading time to find latest update):
;
;10/06/80 FIX TO ERASE TEMPORARY FILE WHEN OUTPUT FILE IS
;	  REQUESTED AND NAME NOT FOUND IN DIRECTORY. (KBP)
;
;10/05/80 FIX ABORT ROUTINE TO CHECK FOR FILE REQUEST
;	  AND TO CLOSE INCOMPLETE FILE BEFORE ERASING
;	  IT. ADDED TESTS FOR NO FILE NAME AND FILE
;	  READ ERROR.  (KBP)
;
;10/04/80 ADD ROUTINE TO GIVE OPTION TO MAKE
;	  DISK FILE OF CRC's. FIXED MFA ROUTINE
;	  SO FIRST-TIME FLAG NOW SHOWS IF NO FILE
;	  FOUND.  (KBP)
;
;04/21/80 ADD MULTIPLE FILENAME FEATURE, PRINT
;	  NAME OF CURRENT FILE BEING READ, AND
;	  ALLOW OPERATOR ABORT. (KBP)
;
;08/20/79 FIX BUG IN READIT ROUTINE WHICH
;	  SHOWED UP ONLY ON ALTCPM OPTION. (KBP)
;
;08/19/79 ADD CONDITIONAL ASSEMBLY FOR CP/M
;	  ON H8 OR TRS-80.  (KBP)
;
;06/27/79 FIRST WRITTEN BY KEITH PETERSEN, W8SDZ
;
;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
SRCHF	EQU	17
SRCHN	EQU	18
READ	EQU	20
STDMA	EQU	26
BDOS	EQU	BASE+5
FCB	EQU	BASE+5CH 
FCBEXT	EQU	FCB+12
FCBRNO	EQU	FCB+32
FCB2	EQU	BASE+6CH
;
;Program starts here
;
	ORG	BASE+100H
;
	MACLIB	SEQIO	;DEFINE MACRO LIBRARY USED
;
CRCK:	JMP	BEGIN	;JUMP AROUND IDENTIFICATION
	DB	'CRCK.COM 4.2 10/6/80'
;
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 4.2',CR,LF
	DB	'CTL-S pauses, CTL-C aborts',CR,LF,0
	LDA	FCB2+1	;GET OPTION
	STA	FFLAG	;SAVE IT FOR LATER
	CPI	'F'	;FILE WANTED?
	JNZ	AGAIN	;NO, SKIP FILE INIT
;
;'Declare' FCB for output file
;(temporarily named CRCKLIST.$$$)
;
	FILE	OUTFILE,CRCFILE,,CRCKLIST,$$$,BSIZE
;
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.$$$
	FINIS	CRCFILE
;
;Build FCB for final name of CRCKLIST.CRC
	FILE	SETFILE,FINAL,,CRCKLIST,CRC
;
;Erase any existing old file
	ERASE	FINAL
;
;Rename CRCKLIST.$$$ to CRCKLIST.CRC
	RENAME	FINAL,CRCFILE
;
;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:
	DB	CR,LF,'--> FILE:  '
FNAME:	DB	'XXXXXXXX.XXX',TAB,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,BASE+80H ;BUFFER LOCATION
;
NORD:	MOV	A,M	;GET FILE CHARACTER
	STA	MESS	;SAVE FOR DIVP
	INX	H
	SHLD	BUFAD	;UPDATE BUFFER ADR
	CALL	DIVP	;CALCULATE NEW CRC
	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
	MVI	A,' '
	CALL	TYPE	;TYPE A SPACE
	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++$'
;
;---------------------------------------------
;An 8080 routine for generating a CYCLIC-
;REDUNDANCY-CHECK.  Character leaves that
;character in location REM.  By Fred Gutman.
;From 'EDN' magazine, June 5, 1979 issue, page 84.
;
DIVP:	LHLD	REM	;GET REMAINDER
	MOV	A,H
	ANI	128	;Q-BIT MASK
	PUSH	PSW	;SAVE STATUS
	DAD	H	;2 X R(X)
	LDA	MESS	;MESSAGE BIT IN LSB
	ADD	L
	MOV	L,A
	POP	PSW
	JZ	QB2	;IF Q-BIT IS ZERO
;
QB:	MOV	A,H
	XRI	0A0H	;MS HALF OF GEN. POLY
	MOV	H,A
	MOV	A,L
	XRI	97H	;LS HALF OF GEN. POLY
	MOV	L,A
;
QB2:	SHLD	REM
	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
	PUT	CRCFILE ;SEND IT TO THE FILE
	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. The routine is commented in Pseudo
;code, each Pseudo code statement is in <<...>>
;
MFNAME:	;<<init DMA addr and FCB>>
	MVI	C,STDMA
	LXI	D,BASE+80H
	CALL	BDOS
	XRA	A
	STA	FCBEXT
	STA	FCBRNO
;<<if first time>>
	LDA	MFFLG1
	ORA	A
	JZ	MFN01
;<<save the requested name>>
;Save orig request
	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,BASE SHR 8
	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
;<<return>>
	RET
;------------------------------------------------
;
;Check for .$$$ files
;
TSTBAD:	CALL	TESTIT	;CHECK FIRST ONE FOR '$'
	RNZ		;NO, RETURN
	CALL	TESTIT	;CHECK SECOND ONE
	RNZ		;NO, RETURN
			;FALL INTO TESTIT TO CHECK THIRD
;
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	ABEXT3	;NO FILE, SKIP FILE STUFF
	FINIS	CRCFILE	;CLOSE INCOMPLETE FILE
	ERASE	CRCFILE	;ERASE INCOMPLETE FILE
;
ABEXT3:	CALL	ERXIT	;PRINT MSG, EXIT
	DB	CR,LF,CR,LF,'++ABORTED++$'
;
;Exit with message
;
MSGEXIT:EQU	$	;EXIT W/"INFORMATIONAL" MSG
ERXIT:	POP	D	;GET MSG
	MVI	C,PRINT
	CALL	BDOS
;
;Exit, restoring stack and return to CCP
;
EXIT:	LHLD	STACK
	SPHL
	RET		;TO CCP
;
;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
;
;Define location of file write buffer
BUFFERS:EQU	$
;
	END	CRCK
