;**************************************************************
; CP/M UTILITY TO MAKE ALTAIR PAPER TAPES                     *
; Written by Martin Eberhard 2 DECEMBER   2010                *
;                                                             *
; REVISION HISTORY                                            *
; REV      DATE     AUTHOR              NOTE                  *
; 1.00 02 DEC 2010 EBERHARD CREATED                           *
; 1.01 12 DEC 2010 EBERHARD CORRECT CHECKSUM, PRINT VALUES    *
;                           FOR BOOT LOADER                   *
; 1.02 14 DEC 2010 EBERHARD SUPPORT HEX, BIN, TAP FILE TYPES, *
;                           USE GO ADDRESS FROM HEX FILE, FIX *
;                           SEVERAL BUGS                      *
;                                                             *
; 1.03 23 JAN 2011 EBERHARD CLEANUP COMMENTS ONLY             *
; 1.04 25 FEB 2013 EBERHARD PREVENT JUNK AT END OF BINARY     *
;                           FILE FROM OVERWRITING LOADER      *
; 1.05 26 FEB 2013 EBERHARD FIX BUG WHEN NO BANNER SPECIFIED  *
; 1.06 26 FEB 2013 EBERHARD FIX SOURCE FCB INITIALIZATION BUG *
; 1.07 07 OCT 2013 EBERHARD ALLOW FOR NO CHECKSUM LOADER      *
; 1.08 17 MAR 2014 EBERHARD FIX CHECKSUM LOADER BUG FROM 1.07 *
; 1.09 18 AUG 2014 EBERHARD NULL LOADER OF NO CHECKSUM LOADER *
;                           SPECIFIED, SO TURMON WILL WORK    *
; FUTURE REVISIONS:                                           *
;  OPTION FOR END BANNER TOO                                  *
;  CHECK ALTAIR INPUT FILE CHECKSUMS, WITH IGNORE OPTION      *
;  ALLOW USER TO SPECIFY DRIVE LETTERS                        *
;**************************************************************
FALSE	EQU	0
TRUE	EQU	0FFFFH
;-----------------------
; THIS PROGRAM'S VERSION
;-----------------------
VERSION	EQU	1	;MAJOR VERSION NUMBER
VERSMIN	EQU	9	;MINOR VERSION NUMBER
UNDERC	EQU	FALSE	;TRUE IF VERSION IS UNDER CONSTRUCTION
;--------------
; DEBUG OPTIONS
;--------------
DEBUG	EQU	FALSE	;FALSE TURNS OFF ALL DEBUG FEATURES
;----------------
; GENERAL EQUATES
;----------------
DBSIZ	EQU	8	;128-BYTE RECORDS PER DISK BUFFER
			;MUST BE AT LEAST FSIZE+1 (8)
			;BEST IF MULTIPLE OF 8
NULLDR	EQU	0DH	;NULL LOADER CHARACTER
NLLEN	EQU	20	;LENGTH OF NULL LOADER
;--------------
; ASCII EQUATES
;--------------
NULL	EQU	0
CTRLC	EQU	3	;ASCII ETX
BELL	EQU	7	;ASCII BELL CHARACTER
BS	EQU	8	;BACKSPACE
CR	EQU	0DH	;ASCII CARRIAGE RETURN
LF	EQU	0AH	;ASCII LINE FEED
DQUOTE	EQU	22H	;ASCII DOUBLE-QUOTE
;-------------
; CP/M EQUATES
;-------------
WBOOT	EQU	0000H	;JUMP TO BIOS WARM BOOT
IOBYTE	EQU	0003H
CDISK	EQU	0004H	;LOGIN DRIVE AND CURRENT USER
BDOS	EQU	0005H	;BDOS ENTRY POINT

	IF NOT DEBUG
COMBUF	EQU	0080H	;COMMAND LINE PARAMETERS BUFFER
	ENDIF

; BDOS FUNCTION CODES, PASSED IN REGISTER C

RESETF	EQU	0	;SYSTEM RESET
CONIF	EQU	1	;READ CONSOLE CHARACTER
CONOF	EQU	2	;TYPE ON CONSOLE FUNCTION
PUNOF	EQU	4	;OUT CHR TO PUNCH
LISOF	EQU	5	;OUT CHR TO LIST DEVICE
PRINTF	EQU	9	;PRINT STRING
RDCONF	EQU	10	;GET LINE FROM CONSOLE
CONSTF	EQU	11	;CONSOLE STAT FUNCT (<>0 IF CHR READY)
RSTDSF	EQU	13	;RESET DISK SYSTEM
SELDF	EQU	14	;SELECT DISK
OPENF	EQU	15	;FILE OPEN
CLOSEF	EQU	16	;FILE CLOSE
SRCHFF	EQU	17	;SEARCH FOR FIRST
SRCHNF	EQU	18	;SEARCH FOR NEXT
DELFF	EQU	19	;DELETE FILE
RDSEQF	EQU	20	;READ FILE SEQUENTIAL
WRSEQF	EQU	21	;WRITE FILE SEQUENTIAL
MAKEFF	EQU	22	;MAKE FILE
RENFF	EQU	23	;RENAME FILE
RCURDF	EQU	25	;RETURN CURRENT DRIVE
STDMAF	EQU	26	;SET DMA ADDRESS
GTAVF	EQU	27	;GET ALLOCATION VECTOR ADDRESS
STFAF	EQU	30	;SET FILE ATTRIBUTES
GTDPBF	EQU	31	;GET ADDRESS OF DISK PARAMETER BLOCK
RDRNDF	EQU	33	;READ FILE RANDOM
WRRNDF	EQU	34	;WRITE FILE RANDOM
CFSIZF	EQU	35	;COMPUTE FILE SIZE
STRRF	EQU	36	;SET RANDOM RECORD
RSTDRF	EQU	37	;RESET DRIVE
WRRZF	EQU	40	;WRITE RANDOM WITH ZERO-FILL

;BDOS ENTRY POINTS

WBOOTA	EQU	1	;ADDRESS OF WARM BOOT (OTHER ENTRY
			;POINTS ARE RELATIVE TO WBOOTA)
SELDSK	EQU	24	;WBOOT+24 FOR DISK SELECT
SETTRK	EQU	27	;WBOOT+27 FOR SET TRACK FUNCTION
SETSEC	EQU	30	;WBOOT+30 FOR SET SECTOR FUNCTION
SETDMA	EQU	33	;WBOOT+33 FOR SET DMA ADDRESS
READEF	EQU	36	;WBOOT+36 FOR READ FUNCTION
WRITEF	EQU	39	;WBOOT+39 FOR WRITE FUNCTION

;------------------------------------
; ALTAIR PAPER TAPE FILE-TYPE EQUATES
;------------------------------------
ALTBNR	EQU	55H	;BEGIN/PROGRAM NAME RECORD (NOT USED) 
ALTBND	EQU	0DH	;END-OF-PROGRAM NAME-MARK (NOT USED)
ALTPLR	EQU	3CH	;PROGRAM LOAD RECORD
ALTEOF	EQU	78H	;EOF/GO ADDRESS RECORD

;==============================================================
; MAIN PROGRAM
;==============================================================
	ORG	100H

MAIN:	LXI	SP,LSTACK	;SET UP LOCAL STACK
;---------------------------------
; GET AND PROCESS INPUT PARAMETERS
;---------------------------------
	CALL	SETUP
;----------------------------------------
; DELETE TEMP FILE <TF>.$$$, IF IT EXISTS
;----------------------------------------
	LXI	D,DSTEXT	;CREATE EXTENSION IN FCB
	LXI	H,DOLSTR	;'$$$'
	CALL	COPY3

	LXI	D,DSTFCB	;USE DEST FILE CONTROL BLOCK
	PUSH	D		;WE NEED THIS ADDRESS AGAIN

	MVI	C,DELFF		;BDOS DELETE FILE FUNCTION
	CALL	BDOS

	POP	D		;DEST FILE CONTROL BLOCK
;-----------------------------------
; MAKE (AND OPEN) TEMP FILE <TF>.$$$
;-----------------------------------
	MVI	C,MAKEFF	;BDOS MAKE FILE FUNCTION
	CALL	BDOS		;DE POINTS TO THE FCB
	LXI	D,DFMMSG	;FILE CREATION ERROR?
	INR	A		;TEST FOR FF=ERROR
	JZ	PRTEXT
;-----------------------------------------------------------
; RENDER TAPE BANNER FROM TEXT (SAVED BY SETUP IN INTBUF) AS
; FONT CHARACTERS IN RDBUF, THEN WRITE RESULT TO TEMP FILE
;-----------------------------------------------------------
	CALL	TPRINT

	LXI	H,0		; REPAIR RDBUF
	SHLD	RDBBC
	LXI	H,RDBUF
	SHLD	RDBPTR
;----------------------------------------------------------
; OPEN CHECKSUM LOADER FILE <CF>.HEX. IF GOTLDR
; =0 THEN NO CHECKSUM LOAD WILL BE PUT IN OUTPUT FILE.
;----------------------------------------------------------
	LXI	D,SRCFCB	;INPUT FILE CONTROL BLOCK
	PUSH	D		;RE-USE THIS ADDRESS LATER

	LDA	GOTLDR		;DID USER SPECIFY A LOADER?
	ORA	A
	CZ	MAKENL		;NO: INSTALL NULL LOADER
				;RET WITH Z SET
	JZ	DOPROG		;NO:SKIP INSTALLING LOADER

	MVI	C,OPENF		;OPEN FILE FUNCTION
	CALL	BDOS		;DE POINTS TO FCB
	LXI	D,CFNMSG	;CHECKSUM LOADER FILE NOT FOUND
	INR	A		;TEST FOR FF=FILE NOT FOUND
	JZ	PRTEXT		
;--------------------------------------------------------------
; WRITE (NLEDER) LEADER BYTES AND CHECKSUM LOADER TO TEMP FILE
;--------------------------------------------------------------
	CALL	MKLODR
;------------------------------------
; WRITE (INULLS) NULLS INTO TEMP FILE
;------------------------------------
DOPROG:	LXI	H,INULLS
	CALL	NULFIL		;NULLS BEFORE PROGRAM FILE	
;-------------------------------------------------------------
; CREATE FCB FOR PROGRAM FILE, OVERWRITING CHECKSUM LOADER FCB
;-------------------------------------------------------------
	LXI	B,24*256+11	;11-BYTE FILENAME, CLR 24 BYTES

	LXI	H,PRGFIL	;REQUESTED PROGRAM FILE & EXT
	LXI	D,SRCFIL
	CALL	COPYC		;COPY C BYTES FROM HL TO DE

; CLEAR THE REST OF (B BYTES OF) THE SOURCE FCB STARTING AT DE
	XRA	A

CLRSLP:	STAX	D
	INX	D
	DCR	B
	JNZ	CLRSLP	
;------------------------------------------------------------
; USE THIS SOURCE FCB TO OPEN PROGRAM FILE <PF>.{HEX,BIN,TAP}
;------------------------------------------------------------
	POP	D		;INPUT FILE CONTROL BLOCK

	MVI	C,OPENF		;OPEN FILE FUNCTION
	CALL	BDOS
	LXI	D,SFNMSG	;SOURCE FILE NOT FOUND
	INR	A		;TEST FOR FF=FILE NOT FOUND
	JZ	PRTEXT

	LXI	H,RDBUF		;RESET READ BUFFER POINTERS
	SHLD	RDBPTR
	LXI	H,0
	SHLD	RDBBC
;--------------------------------------------------------------
; CONVERT FILE FROM FORMAT SPECIFIED BY PROGRAM FILE EXTENSION
; TO ALTAIR FILE FORMAT AND WRITE IT TO THE TEMP FILE <TF>.$$$.
; IF AN ALTAIR BLOCK IS ADDRESSED AT ADDRESS 0, SAVE IT IN
; PG0BUF BUFFER SO THAT IT CAN BE WRITTEN LAST.
; HL=0 HERE.
;--------------------------------------------------------------
	DCX	H		;PRESET BYTE ADDRESS IN CASE OF
	SHLD	BYTADR		;...BINARY FILE

	LDA	FILTYP		;GET SOURCE FILE TYPE
	CPI	2		;0=.HEX, 1=.BIN

; TAPALT HANDLES ALTAIR TAPE INPUT FILES

	CNC	TAPALT		;RETURNS WITH C CLEARED
	JNC	FLUSH

; HBALT HANDLES BOTH HEX AND BINARY INPUT FILES

	CC	HBALT
;------------------------------------------------
; WRITE PAGE 0 DATA TO TEMP FILE <TF>.$$$, IF ANY
;------------------------------------------------
	LDA	PAGE0		;FLAG THAT INDICATES PAGE0 DATA
	ORA	A
	JZ	NOPAG0

; COMPUTE TRANSFER SIZE

	LXI	H,PG0BUF+1	;GET BYTE COUNT FROM BUFFER
	MOV	C,M
	MVI	B,0		;HIGH BYTE
	INX	B		;ONE BYTE FOR RECORD TYPE
	INX	B		;ONE BYTE FOR ADDRESS LOW
	INX	B		;ONE BYTE FOR ADDRESS HIGH
	INX	B		;ONE BYTE FOR BYTE COUNT
	INX	B		;ONE BYTE FOR CHECKSUM

	DCX	H		;POINT HL TO BUFFER BEGINNING
	CALL	WRDISK		;WRITE BC BYTES AT HL TO DISK
NOPAG0:
;------------------------------------
; WRITE (JNULLS) NULLS (AFTER PROGRAM
; BUT BEFORE GO RECORD) TO TEMP FILE
;------------------------------------
	LXI	H,JNULLS
	CALL	NULFIL	
;---------------------------------------------
; WRITE EOF RECORD (WITH SPECIFIED GO ADDRESS)
; TO TEMP FILE FOLLOWED BY (KNULLS) NULLS
;---------------------------------------------
	LHLD	GOADDR		;GET GO ADDRESS
	SHLD	ALTBUF+1	;PUT GO ADDRESS IN BUFFER

	LXI	H,ALTBUF
	MVI	A,ALTEOF	;EOF MARKER
	MOV	M,A		;1ST BYTE OF AN EOF RECORD

	LXI	B,3		;3-BYTE RECORD
	CALL	WRDISK		;WRITE BC BYTES AT HL TO DISK

	LXI	H,KNULLS	;NULLS AFTER EVERYTHING IS DONE
	CALL	NULFIL
;------------------------------------------------------------
; FLUSH DISK WRITE BUFFER, FILLING IT WITH NULLS AS NEEDED TO
; END EXACTLY ON A 128-BYTE BOUNDARY, THEN CLOSE TEMP FILE
;------------------------------------------------------------
FLUSH:	CALL	FLSHWR

	LXI	D,DSTFCB	;WRITE FILE CONTROL BLOCK
	MVI	C,CLOSEF	;CLOSE FUNCTION
	CALL	BDOS
	INR	A		;TEST FOR FF=FILE NOT FOUND
	JZ	ERDFC
;-----------------------------------------------------------
; <TF>.$$$ FILE IS DONE: DELETE ANY OLD OUTPUT FILE <TF>.TAP
;-----------------------------------------------------------
	LXI	H,TAPSTR	;'TAP' STRING
	LXI	D,DSTEXT	;RE-USE DEST FCB
	CALL	COPY3

	LXI	D,DSTFCB	;DEST FILE CONTROL BLOCK
	MVI	C,DELFF		;DELETE FILE FUNCTION
	CALL	BDOS
;-------------------------------------------------------
; RENAME TEMP FILE <TF>.$$$ BECOMES OUTPUT FILE <TF>.TAP
;-------------------------------------------------------
	LXI	H,DSTFCB	;NEW NAME IS LIKE OLD NAME
	LXI	D,DSTD0		;SECOND 16 BYTES IS NEW NAME
	MVI	C,16
	CALL	COPYC

	LXI	H,DOLSTR	;OLD EXTENSION $$$
	LXI	D,DSTEXT
	CALL	COPY3

	LXI	D,DSTFCB	;SOURCE FILE CONTROL BLOCK
	MVI	C,RENFF		;RENAME FILE FUNCTION
	CALL	BDOS
	INR	A		;TEST FOR FF=FILE NOT FOUND
	JZ	ERDFC
;------------------------------------------------------------
; PRINT CHECKSUM LOADER DETAILS, SO THAT THE BOOTSTRAP LOADER
; CAN BE SET UP CORRECTLY. PRINT IN OCTAL TO BE COMPATIBLE
; WITH ALTAIR DOCUMENTATION FOR THE BOOTSTRAP LOADERS.
;------------------------------------------------------------
	LDA	GOTLDR
	ORA	A		;WAS THERE A LOADER AT ALL?
	JNZ	YESLOD

	LXI	D,NLDMSG	;NO LOADER
	CALL	PCRSTR
	JMP	TRIMMD

YESLOD:	LXI	D,LDRMSG	;LEADER CHARACTER:
	CALL	PCRSTR
	LDA	LEADER		;PRINT LEADER CHR IN OCTAL
	CALL	PRTOCT
	LXI	D,OCTMSG	;OCTAL
	CALL	PRTSTR

	LXI	D,CKPMSG	;CHECKSUM LOADER PAGE:
	CALL	PCRSTR
	LDA	LDRADR+1	;PRINT LOADER PAGE IN OCTAL
	CALL	PRTOCT
	LXI	D,OCTMSG	;OCTAL
	CALL	PRTSTR

TRIMMD:	LDA	TRMCNT		;ANY BYTES TRIMMED FROM FILE?
	MOV	B,A		;SAVE FOR OVERFLOW TEST
	ORA	A
	JZ	ALLDON

	LXI	D,TRMMSG
	CALL	PCRSTR		;TRIMMED BYTES:

	LXI	D,GEMSG		;>=
	INR	B
	CZ	PRTSTR		;YES: PRINT >=
	DCR	B		;UNDO INR B ABOVE

	CALL	PRINTD		;PRINT B IN DECIMAL
	
;----------------------------------
; ALL DONE - PRINT MESSAGE AND QUIT
;----------------------------------
ALLDON:	LXI	D,EXTMSG	;EXIT MESSAGE
	JMP	PRTEXT

;**************************************************************
; SUBROUTINE COPY3: COPY 3 BYTES AT (HL) TO (DE)
;                   (FOR FILE EXTENSIONS)
; SUBROUTINE COPYC: COPY (C) BYTES AT (HL) TO (DE)
;
; ON EXIT:
;  DE=next address in HL FCB
;   C=0
;   A,DE,HL TRASHED
;**************************************************************
COPY3:	MVI	C,3
COPYC:	MOV	A,M		;REPLACE .HEX WITH .TAP
	STAX	D
	INX	H
	INX	D
	DCR	C
	JNZ	COPYC

	RET

;**************************************************************
; SUBROUTINE MAKE NULL LOADER: WRITE NLLEN BYTES OF
; NULLDR TO THE OUTPUT FILE. MAKE SURE INULLS >= NULLDR.
; ON EXIT:
;   Z SET
;   IF INULLS < NULLDR THEN SET INULLS = NULLDR
;   A,BC,HL TRASHED
;**************************************************************
MAKENL:	MVI	B,NLLEN		;NULL LOADER BYTE COUNT
	LXI	H,INULLS	;POST-LOADER NULLS

	MVI	A,NULLDR	;NULL LOADER CHARACTER
	CMP	M		;INULLS MUST BE >= NULLDR
	JC	MNL1

	MOV	M,A		;MIN NUMBER OF NULLS		
MNL1:

	JMP	AFILL		;GO WRITE NULL LOADER
				;SETS Z ON RETURN

;**************************************************************
; SUBROUTINE NULL FILL: WRITE (HL) NULLS TO THE OUTPUT FILE
; ON ENTRY:
;   HL= ADDRESS OF (8-BIT) NULL COUNT
; ON EXIT:
;   A,BC,HL TRASHED
;**************************************************************
NULFIL:	MOV	B,M		;GET NULL COUNT
	XRA	A		;TEST FOR 0 BYTES, CREATE NULL
	CMP	B
	RZ

; FALL INTO AFILL

;**************************************************************
; SUBROUTINE FILL: WRITE B BYTES OF A TO THE OUTPUT FILE
; ON ENTRY:
;   A= FILL CHARACTER
;   B = BYTE COUNT
; ON EXIT:
;   Z SET
;   A,BC,HL TRASHED
;**************************************************************
AFILL:	LXI	H,ALTBUF	;USE ALTAIR BUFFER FOR NULLS
	MOV	C,B		;BYTE COUNT FOR WRDISK
	PUSH	H

NFLOOP:	MOV	M,A
	INX	H
	DCR	B
	JNZ	NFLOOP		;B=0 AT END

	POP	H		;DATA ADDRESS IN HL
	JMP	WRDISK		;WRITE BC BYTES AT HL TO DISK
				;SETS Z ON RETURN

;**************************************************************
; SUBROUTINE MAKE LOADER: READ INTEL HEX FILE AND WRITE IT 
; BACKWARDS TO DISK (FOR CHECKSUM LOADER), PRECEEDED BY
; (NLEDER) COPIES OF THE CORRECT LEADER CHARACTER (WHICH IS
; THE LOW BYTE OF THE LAST LOADER ADDRESS +1)
;**************************************************************
MKLODR:	MVI	A,1		;INITIALIZE HEX BYTE COUNT+1
	STA	INTBC		;...NEEDED FOR CALL GETHC

; GET THE FIRST BYTE, WHICH WILL BE A NON-CONTIGUOUS BYTE

	XRA	A		;LOOK FOR HEX FILE
	CALL	GETHBC		;GET THE 1ST (NON CONTIG) BYTE
	LXI	D,NLFMSG	;NULL LOADER FILE MESSAGE
	JZ	CLOEXT		;NO NON-CONTIG RECORDS ALLOWED

; REMEMBER CHECKSUM LOADER'S ADDRESS FOR EXIT MESSAGE, AND
; SO THAT JUNK AT THE END OF BINARY FILES CAN BE TRIMMED OFF

	LHLD	BYTADR
	SHLD	LDRADR

 ;INSTALL FIRST LOADER BYTE IN THE BUFFER

	LXI	H,ALTBFE-1	;END OF BUFFER ADDRESS
	MOV	M,A		;INSTALL FIRST BYTE
	LXI	B,1		;16-BIT BYTE COUNT IN BC

; LOOP TO GET THE REST OF THE LOADER, CHECKING CONTIGUOUSNESS

MKLDR1:	DCX	H		;WRITE BACKWARDS INTO BUFFER	
	INR	C		;8-BIT BUMP BYTE COUNT
	LXI	D,CLSMSG	;LOADER TOO BIG
	JZ	CLOEXT		;MAX CHECKSUM LOADER=256 BYTES

	PUSH	H
	PUSH	B
	XRA	A		;LOOK FOR HEX FILE
	CALL	GETHBC		;GET A BYTE FROM THE HEX FILE
	POP	B
	POP	H

	LXI	D,NCRMSG	;NON-CONTIGUOUS RECORD
	JC	CLOEXT		;NO NON-CONTIG RECORDS ALLOWED

	MOV	M,A		;BYTE INTO BUFFER
	
	JNZ	MKLDR1		;NOT EOF: KEEP GOING

; EOF IN LOADER FILE ENCOUNTERED. TIDY UP POINTERS

	INX	H		;LAST BYTE WAS BOGUS
	DCR	C		;UNDO LAST CHR SINCE IT WAS EOF
	MOV	E,C		;BYTE COUNT LSB IS LEADER	

; NOW THAT WE KNOW THE LEADER CHARACTER, WRITE THE LEADER TO
; THE FILE. E IS LEADER BYTE, BC IS BYTE COUNT SO FAR

	LDA	NLEDER		;NUMBER OF LEADER BYTES

MKLDR2:	DCX	H
	MOV	M,E
	INX	B		;BUMP 16-BIT BYTE COUNT
	DCR	A
	JNZ	MKLDR2

; SAVE THE LEADER BYTE IN THE BOOT LODAER FILE
	MOV	A,E
	STA	LEADER
			
	JMP	WRDISK		;WRITE BC BYTES AT HL TO DISK
				;THEN RETURN

;**************************************************************
; SUBROUTINE CONVERT HEX OR BINARY FILE TO ALTAIR FILE:
; BUILD ALTAIR RECORDS FROM AN INTEL HEX OR BINARY FILE AND
; WRITE THEM TO DISK. FOR INTEL HEX, TERMINATE WHEN AN INTEL
; HEX EOF FOUND - EITHER RECORD TYPE = 01 OR RECORD LENGTH = 0
; AND RECORD TYPE = 0. (THE ADDRESS IN THIS EOF RECORD IS ALSO
; THE GO ADDRESS.) FOR BINARY, TERMINATE WITH DISK EOF.
;
; ALTAIR RECORDS ARE ALWAYS 255 BYTES LONG, UNLESS IT'S THE
; LAST OF THE PROGRAM FILE AND WE DON'T HAVE 255 BYTES TO
; WRITE, OR THE ADDRESS OF AN INTEL HEX RECORD IS NOT
; CONTIGUOUS WITH THE LAST ADDRESS OF THE PREVIOUS RECORD.
;
; IF WE BUILD AN ALTAIR RECORD THAT GOES AT ADDRESS 0, THEN
; SAVE IT IN PG0BUF, TO BE WRITTEN TO DISK AFTER ALL THE OTHER
; ALTAIR RECORDS. (MITS DID THIS SO THAT THE BOOTSTRAP LOADER
; WOULD STILL BE IN MEMORY SHOULD A CHECKSUM ERROR OCCUR
; DURING LOADING.)
;
; ON ENTRY:
;   FILTYP={0,1} FOR {.HEX, .BIN}
;
; ON EXIT:
;   CARRY IS CLEARED
;
; BUFFER STRUCTURE:
;  BYTE 0: RECORD TYPE (ALTPLR=3CH=DATA RECORD)
;  BYTE 1: DATA BYTE COUNT
;  BYTE 2: ADDRESS LOW BYTE
;  BYTE 3: ADDRESS HIGH BYTE
;  BYTES4-N: DATA
;  BYTE N+1: CHECKSUM (ALL EXCEPT BYTE COUNT AND RECORD TYPE)
;**************************************************************
HBALT:	MVI	A,1		;INITIALIZE HEX BYTE COUNT+1
	STA	INTBC		;...NEEDED FOR CALL GETBC

MKALT0:	CALL	GETBC		;GET FIRST BYTE & ADDRESS
	RZ			;END OF FILE RECORD FOUND

MKALT1:	LHLD	BYTADR		;PUT BLOCK ADDRESS IN RECORD
	SHLD	ALTBUF+2

	MOV	C,A		;SAVE CHR FOR A MOMENT
	ADD	L		;BEGIN BUILDING CHECKSUM
	ADD	H
	MOV	B,A		;CHECKSUM IN B

	LXI	H,ALTBUF+4
	MOV	M,C		;SAVE FIRST BYTE IN RECORD
	INX	H		;HL=DATA POINTER

	MVI	C,2		;BYTE COUNT +1 START

	MVI	A,ALTPLR	;'DATA RECORD' = 3CH
	STA	ALTBUF		;PUT 'DATA RECORD' IN RECORD

; LOOP TO FILL ALTAIR RECORD, STOPPING AFTER 255 BYTES,
; OR EOF ENCOUNTERED, OR NON-CONTIGUOUS INTEL HEX RECORD

MKALT2:	PUSH	H
	PUSH	B
	CALL	GETBC		;GET ANOTHER BYTE
	POP	B
	POP	H

	JZ	MKALT3		;Z MEANS EOF RECORD FOUND
	JC	MKALT3		;C MEANS NON-CONTIGUOUS

;IF THIS IS A BINARY FILE, SEE IF THIS DATA BYTE WILL OVERWRITE
;THE CHECKSUM LOADER. DISCARD IT IF SO.
	PUSH	B		;SAVE DATA BYTE
	MOV	B,A

	LDA	GOTLDR		;IS THERE A CHECKSUM LOADER?
	ORA	A
	JZ	MKA23		;NO: NO TRIMMING NEEDED

	LDA	FILTYP		;BINARY FILE?
	DCR	A
	JNZ	MKA23		;NO: SKIP THIS TEST, C CLEARED

	PUSH	H
	PUSH	D
	LHLD	BYTADR
	XCHG			;DE=THIS BYTE'S BYTE ADDRESS
	LHLD	LDRADR		;HL=LOADER'S ADDRESS

	MOV	A,H
	CMP	D		;C SET IF BYTADR > LDRADR
	JNZ	MKA21		;C & Z CLEAR IF LDRADR>BYTADR

	MOV	A,E		;NEED TO LOOK AT LOW BYTE
	CMP	L		;C SET IF LDRADR>BYTADR
	CMC			;C NOW SET IF BYTADR >= LDRADR			

MKA21:	JNC	MKA22
	LDA	TRMCNT		;BUMP TRIMMED BYTE COUNT
	INR	A		;WON'T EFFECT CARRY
	JZ	MKA22		;DON'T LET IT WRAP
	STA	TRMCNT

MKA22:	POP	D
	POP	H

MKA23:	MOV	A,B		;RECOVER DATA BYTE
	POP	B
	JC	MKALT2		;CARRY: DON'T PUT BYTE IN RECORD

;INSTALL DATA BYTE IN ALTAIR RECORD AND KEEP GOING

	MOV	M,A		;PUT BYTE IN ALTAIR RECORD
	ADD	B		;COMPUTE CHECKSUM IN B
	MOV	B,A

	INX	H		;NEXT DATA BYTE
	INR	C		;BUMP BYTE COUNT, TEST
	JNZ	MKALT2		;255 BYTES YET?

	XRA	A		;CLEAR C FLAG
	INR	A		;CLEAR Z FLAG

; END OF BLOCK: FINISH BUILDING ALTAIR RECORD
; ON ENTRY: Z SET FOR EOF, C SET FOR NON-CONTIGUOUS RECORD

MKALT3:	PUSH	A		;REMEMBER NEXT BYTE AND FLAGS

	MOV	M,B		;PUT CHECKSUM IN RECORD
	DCR	C		;BYTE COUNT INSTEAD OF +1
	MOV	A,C
	STA	ALTBUF+1	;PUT BYTE COUNT IN RECORD

	MVI	B,0		;COMPUTE TRANSFER SIZE
	INX	B		;RECORD TYPE
	INX	B		;ADDRESS LOW
	INX	B		;ADDRESS HIGH
	INX	B		;BYTE COUNT
	INX	B		;CHECKSUM

; IF THE ADDRESS OF THIS RECORD IS ZERO, SAVE IT FOR LATER

	LXI	H,ALTBUF+3	;GET TARGET ADDRESS HIGH BYTE
	MOV	A,M
	DCX	H
	ORA	M
	DCX	H
	DCX	H		;HL=ALTBUF
	JNZ	MKALT5

; COPY INTEL HEX RECORD INTO PG0BUF

	INR	A		;A=1
	STA	PAGE0		;REMEMBER THAT WE FOUND PAGE 0

	LXI	D,PG0BUF

MKALT4:	MOV	A,M
	STAX	D
	INX	H
	INX	D
	DCX	B
	MOV	A,B
	ORA	C
	JNZ	MKALT4		;END WITH Z SET

MKALT5:	CNZ	WRDISK		;WRITE BC BYTES AT HL TO DISK
	POP	A		;RECOVER FLAGS, DATA IN A

	JC	MKALT1		;USE DATA FROM NON-CONTIGUOUS	
	RZ			;DONE IF EOF ENCOUNTERED
	JMP	MKALT0		;GO BUILD ANOTHER RECORD

;**************************************************************
; SUBROUTINE CONVERT TAP FILE TO ALTAIR FILE:
; BUILD ALTAIR RECORDS FROM ALTAIR PROGRAM FILE. (USE THIS
; OPTION TO REPLACE THE CHECKSUM LOADER ON A TAPE) NO ATTEMPT
; IS MADE TO INTERPRET OR VALIDATE THE ALTAIR RECORD. THIS
; ROUTINE FINDS AND REMOVES THE LEADER AND CHECKSUM LOADER IN
; THE ALTAIR FILE, AND REPLACES THEM WITH THE SPECIFIED
; CHECKSUM LOADER AND ITS APPROPRIATE LEADER. SEPERATION NULLS
; ARE AS SPECIFIED BY THE COMMAND LINE, BUT NO NEW GO-RECORD IS
; ADDED.
;
; ON EXIT:
;   CARRY=0
;**************************************************************
TAPALT:	LXI	D,BPFERR	;BAD PROGRAM FILE MSG

; SEARCH FOR LEADER = 12 IN A ROW OF THE SAME CHARACTER
; THAT NOT A NULL

TA2:	MVI	C,12		;MIN LEADER IS 12 BYTES
	MOV	B,A		;HUNTING FOR VALUE IN B

TA3:	CALL	RDDISK
	JZ	CLOEXT		;FILE END IS AN ERROR
	ORA	A		;NULL?
	JZ	TA2		;NULLS ARE NOT LEADER
	CMP	B		;SAME AS LAST CHR?
	JNZ	TA2		;NO MATCH
	DCR	C
	JNZ	TA3		;ENOUGH MATCHES YET?	

; SEARCH FOR END OF LEADER CHR IN B

TA4:	CALL	SKIPB

; LEADER CHARACTER IS CHECKSUM LOADER BYTE COUNT. SKIP LOADER.

	DCR	B		;ALREADY GOT 1 CHR

TA5:	CALL	RDDISK
	JZ	CLOEXT		;FILE END IS AN ERROR
	DCR	B
	JNZ	TA5

; HUNT FOR NON-NULL CHR, AND WRITE IT INTO ALTBUF. B=0

	CALL	SKIPB		;RETURNS W/ 1ST CHR IN A

	LXI	D,ALTBUF

	STAX	D		;STASH FIRST BYTE
	INX	D	

	LXI	B,101H		;ALREADY HAVE ONE BYTE IN A

;LOOP TO SEND 256 BYTES AT A TIME THROUGH ALTBUF

TA6:	CALL	RDDISK		;GET A RECORD BYTE
	JZ	TA7		;LAST BYTE?

	STAX	D		;STASH BYTE IN ALTBUF
	INX	D
	INR	C
	JNZ	TA6		;256 BYTES WHEN C WRAPS

	PUSH	B		;SAVE BC=100H
	CALL	TA8		;WRITE BC BYTES FROM (HL)
	POP	B
	LXI	D,ALTBUF
	JMP	TA6

; FLUSH ANY REMAINING BYTES FROM ALTBUF

TA7:	MOV	A,C
	ORA	A
	RZ			;NOTHING TO FLUSH?

	MVI	B,0		;HIGH BYTE OF BYTE COUNT

;	FALL INTO TA8

;LOCAL SUBROUTINE TO WRITE BC BYTES FROM ALTBUF

TA8:	LXI	H,ALTBUF	;BUFFER BEGINNING
	JMP	WRDISK		;WRITE THE LAST TIDBIT	

;********************************************
; LOCAL SUBROUTINE TO SKIP BYTES THAT MATCH B
; ON ENTRY:
;    B=CHR TO SKIP
;   DE=ADDRESS OF ERROR MESSAGE STRING
; ON EXIT:
;    A=FIRST NON-'B' CHR
; RUDE EXIT IF EOF FOUND
;********************************************
SKIPB:	CALL	RDDISK
	JZ	CLOEXT		;FILE END IS AN ERROR
	CMP	B
	JZ	SKIPB

	RET

;**************************************************************
; SUBROUTINE GET INPUT CHARACTER:
; GET THE NEXT CHARACTER FROM AN INPUT FILE
; ENTRY AT GETBC: LOOKS UP FILE TYPE IN FILTYP
; ENTRY AT GETHBC: A=0 MEANS HEX FILE, A=1 MEANS BINARY FILE
;
; ON EXIT:
;   Z MEANS END OF FILE ENCOUNTERED, AND A INVALID
;   NZ AND NC MEANS CONTIGUOUS BYTE IN A, ADDRESS IN BYTADR
;   NZ AND CARRY MEANS NON-CONTIGUOUOS BYTE IN A, ADDR IN BYTADR
;   BYTADR=TARGET ADDRESS FOR THIS BYTE
;   INTPTR IS CURRENT FOR NEXT BYTE
;   INTBC HAS REMAINING BYTES +1
;**************************************************************
GETBC:	LDA	FILTYP	;0=HEX, 1=BIN

; RECOVER STATE FROM PREVIOUS CALLS (A=0 FOR HEX, 1 FOR BIN)

GETHBC:	LHLD	BYTADR	;PREVIOUS BYTE'S ADDRESS
	INX	H
	SHLD	BYTADR	;THIS BYTE'S ADDRESS IF CONTIGUOUS

	ORA	A	;IS SOURCE BILE .BIN?
	JNZ	RDDISK	;YES: JUST GET A PROG FILE BYTE

; GET A BYTE FROM A .HEX FILE.
; NOTE: A=0 HERE, WHICH IMPLIES CONTIGUOUS RECORD SO FAR
; (OF WE JUMP TO GETHC1)

	XCHG		;THIS BYTE'S ADDRESS IN DE
	LXI	H,INTBC	;GET HEX BYTE COUNT+1
	DCR	M	;TEST BYTE COUNT
	LHLD	INTPTR
	JNZ	GETHC1	;HEX BYTE COUNT = 0?

; NEED TO GET ANOTHER HEX RECORD

	PUSH	D
	CALL	GETINT
	POP	D	;DE = THIS BYTES ADDRESS IF CONTIGUOUS

	SHLD	BYTADR	;THIS RECORD'S BYTE ADDRESS
	RZ		;EOF RETURN

	STA	INTBC	;BYTE COUNT BECOMES +1

; IS THIS RECORD CONTIGUOUS WITH THE LAST ONE?

	MOV	A,D	;16-BIT COMPARE
	XRA	H
	MOV	D,A
	MOV	A,E
	XRA	L
	ORA	D	;A=0 IF SAME

	LXI	H,INTBUF ;RESTART INTPTR	

GETHC1:	MOV	D,A	;D=0 MEANS CONTIGUOUS
	XRA	A	;PREPARE TO CLEAR Z
	CMP	D	;SET CARRY IF NOT CONTIGUOUS
	INR	A	;CLEAR Z, DON'T TOUCH CARRY

	MOV	A,M	;GET A BYTE FROM THE HEX BUFFER
	INX	H	;BUMP & SAVE POINTER
	SHLD	INTPTR
	RET

;**************************************************************
; SUBROUTINE GET INTEL: READ AN INTEL HEX RECORD INTO INTBUF
;
; ON EXIT:
;   Z SET MEANS EOF ENCOUNTERED
;   
;   A=BYTE COUNT
;   HL=TARGET DATA ADDRESS FOR THIS RECORD
;   DATA IN RAM STARTING AT INTBUF
; REGISTER USAGE:
;   B=RETURNED VALUE FROM GETBYTE
;   C=BYTE COUNT
;   D=CHECKSUM
;   E=RECORD TYPE
;   HL=TARGET ADDRESS   
;**************************************************************
GETINT:
GETCOL:	CALL	RDDISK		;WAIT FOR RECORD START
	RZ			;DONE IF EOF
	CPI	':'
	JNZ	GETCOL

	CALL	CKSBYT		; GET BYTE COUNT
	MOV	C,B		;BYTE COUNT GOES IN C
	MOV	D,B		;START CHECKSUM IN D

	CALL	CKSBYT		; GET RECORD ADDRESS
	MOV	H,B		;HIGH BYTE OF ADDRESS
	CALL	CKSBYT
	MOV	L,B		;LOW BYTE OF ADDRESS

	CALL	CKSBYT		;GET RECORD TYPE
	MOV	E,B		;E=RECORD TYPE

	MOV	A,C		;CHECK FOR 0-BYTE RECORD
	ORA	A
	JZ	GCHKSM

	PUSH	B		;SAVE B=BYTE COUNT
	PUSH	H		;SAVE RECORD ADDRESS

;LOOP TO GET DATA INTO MEMORY

	LXI	H,INTBUF	;BUFFER BEGINNING

BYTLUP:	CALL	CKSBYT		;GET NEXT DATA BYTE

	MOV	M,B		;SAVE DATA BYTE IN BUFFER
	INX	H
	DCR	C		;RECORD BYTE COUNT-DOWN
	JNZ	BYTLUP

	POP	H		;RECOVER RECORD ADDRESS
	POP	B		;RECOVER BYTE COUNT

; GET AND TEST CHECKSUM

GCHKSM:	CALL	CKSBYT
	JNZ	ERRCKS		;TEST CHECKSUM, ERROR IF BAD

	DCR	E		;TEST FOR EOF RECORD
	JZ	SETGO		;EOF RECORD (TYPE 1) FOUND

	INR	E		;RECORD TYPE 0?
	JNZ	GETCOL		;IGNORE OTHER RECORD TYPES

	MOV	A,C		;TEST FOR 0-BYTE RECORD (A=0)
	ORA	A		;0-BYTE RECORD IS ALSO EOF
	RNZ

;EOF RECORD FOUND. SAVE GO-ADDRESS, SET Z, AND RETURN

SETGO:	XCHG			;GO ADDRESS FROM INTEL HEX

	LHLD	GOADDR		;DID USER OVERIDE HEX ADDRESS?
	INX	H		;GOADDR=0FFFF MEANS DEFAULT
	MOV	A,L
	ORA	H
	JNZ	GODONE		;NOT OFFFF: USER SPECIFIED GO

	XCHG			;SAVE GO ADDRESS FROM THE HEX
	SHLD	GOADDR

GODONE:	XRA	A		;SET Z
	RET			;DONE:Z MEANS EOF

;*****************************************
; LOCAL SUBROUTINE: GET 2 HEX DIGITS AND
; COMBINE THEM INTO A BYTE, AND ADD RESULT
; TO CHECKSUM IN D. RESULT RETURNED IN B.
; Z FLAG SET IF CHECKSUM IS 0
; ON ENTRY:
;   D=CHECKSUM SO FAR
; ON EXIT: 
;   B=HEX BYTE VALUE
;   D=D+B
; RUDE EXIT ON HEX ERROR OR EOF
;*****************************************
CKSBYT:	CALL	GETBYT

	MOV	B,A		;SAVE RESULT FOR RETURN
	ADD	D		;COMPUTE CHECKSUM
	MOV	D,A
	RET

;****************************************
; LOCAL SUBROUTINE: GET 2 HEX DIGITS AND
; COMBINE THEM INTO A BYTE.
; Z FLAG IS SET IF CHECKSUM IS 0
; ON ENTRY:
; ON EXIT: 
;   A=HEX BYTE VALUE
;   B TRASHED
; RUDE EXIT ON HEX ERROR OR EOF
;****************************************
GETBYT:	XRA	A		;USE B FOR DIGIT ACCUMULATION
	CALL	GETHEX

	RLC			;SHIFT HIGH NIBBLE IN PLACE
	RLC
	RLC
	RLC

;	FALL INTO GETHEX, SET CARRY IF ERROR

;**************************************
; LOCAL SUBROUTINE: GET A HEX DIGIT AND
; COMBINE IT WITH THE VALUE IN A.
; RUDE EXIT ON HEX ERROR OR EOF
;**************************************
GETHEX:	MOV	B,A		;SAVE PREVIOUS NIBBLE
	CALL	RDDISK		;GET ONE BYTE IN A
	JZ	ERRASC		;EOF IS AN ERROR HERE
	CALL	HEXCON		;CONVERT A TO BINARY
	JC	ERRASC		;CARRY: ASCII ERROR

	ORA	B		;COMBINE NIBBLES
	RET

;**************************************************************
; SUBROUTINE HEX CONVERT:
; CONVERT ASCII CHARACTER IN A TO A HEXIDECIMAL VALUE IN A
; CARRY SET ON ERROR
;**************************************************************
HEXCON:	CPI	'9'+1		;IS CHR BELOW ASCII 9
	JC	HC1		;YES: GO DEAL WITH DIGIT

	CPI	'A'		;IS CHR BETWEEN 9 AND A?
	RC			;YES: ERROR
	SUI	'A'-'9'-1	;NO: SUBTRACT OFFSET	

HC1:	SUI	'0'
	CPI	10H
	CMC			;SET CARRY ON ERROR
	RET

;**************************************************************
; SUBROUTINE TAPE PRINT: RENDER CHARACTERS FOR TAPE INTO OUTPUT
; FILE
; STRING MAY ONLY CONTAIN CHRS 20H <= CHR <= 61H
; ON ENTRY:
;   INTBUF = ADDRESS OF STRING
;   INTBC=BYTE COUNT OF STRING
; ON EXIT: STRING IS RENDERED AS FONT CHRS INTO RDBUF, AND THEN
;   WRITTEN TO DISK
; TRASHES ALL REGS AND RDBUF
;**************************************************************
TPRINT:	LDA	INTBC		;BYTE COUNT OF BANNER
	ORA	A		;ANY BANNER?
	RZ			;NO: DONE

	MOV	C,A		;C HAS CHARACTER COUNT
	LXI	H,INTBUF	;POINT TO STRING

TPLOOP:	MOV	A,M		;GET CHR
	SUI	FMIN		;MIN SUPPORTED CHR
 
	PUSH	H		;SAVE ASCII POINTER
	PUSH	B		;SAVE BYTE COUNT
;--------------------------------------------------------------
; RENDER CHR IN A AS AN FSIZEx8 CHARACTER WITH 1 COL SEPERATION
;--------------------------------------------------------------
	MVI	D,0	;COMPUTE HL= FONT+ A*FONTS
	MOV	E,A	;DE=CHR VALUE-FMIN
	LXI	H,FONT
	MVI	B,FSIZE	;FONT SIZE (COLUMNS)

FONTP0:	DAD	D
	DCR	B
	JNZ	FONTP0	

;B=0 NOW

	XCHG		;DE HAS FONT ADDRESS
	LHLD	RDBPTR	;HL HAS DEST ADDRESS
	MOV	C,B	;B=0: COLUMN COUNT

FONTP1:	LDAX	D	;GET ONE COLUMN OF CHR
	CPI	SKP	;SKP MEANS DONE FOR SHORT CHRS
	JZ	FONTP2

	MOV	M,A	;PUT CHR IN BUFFER
	INX	H
	INX	D
	
	INR	C
	MOV	A,C
	CPI	FSIZE
	JNZ	FONTP1

FONTP2:	MOV	M,B	;B=0:SPACE BETWEEN CHARACTERS
	INX	H
	INR	C

	SHLD	RDBPTR
	LHLD	RDBBC	;UPDATE BYTE COUNT
	DAD	B
	SHLD	RDBBC

	POP	B	;RECOVER BYTE COUNT
	POP	H	;RECOVER ASCII POINTER

;---------------
; NEXT CHARACTER
;---------------
	INX	H
	DCR	C
	JNZ	TPLOOP

;-----------------------------
; SEND BANNER TO THE TEMP FILE
;-----------------------------

	LHLD	RDBBC
	MOV	B,H
	MOV	C,L	;BC HAS BYTE COINT
	LXI	H,RDBUF	;HL HAS ADDRESS

;fall into WRDISK

;**************************************************************
; SUBROUTINE WRITE DISK: WRITE BLOCK TO DISK
; ON ENTRY:
;   BC = BYTE COUNT (NON-ZERO)
;   HL = ADDRESS OF DATA
;   WRBBC = NUMBER OF FREE BYTES IN BUFFER
;   WRBPTR = NEXT FREE LOCATION IN BUFFER
; ON EXIT:
;   A=0
;   CARRY CLEARED
;   Z SET
;   WRBBC = NUMBER OF FREE BYTES IN BUFFER
;   WRBPTR = NEXT FREE LOCATION IN BUFFER
;   TRASHES A,BC,HL
; RUDE EXIT (IN FLSHWR) ON DISK ERROR
;**************************************************************
WRDISK:	PUSH	D
	XCHG			;DE= DATA ADDRESS
	LHLD	WRBPTR		;HL=DEST ADDRESS
	XCHG			;DE=DEST, HL=SOURCE

WRD1:	PUSH	H		;SAVE SOURCE DATA ADDRESS

	LHLD	WRBBC		;FREE BYTE COUNT
	MOV	A,L		;TEST FOR NO ROOM
	ORA	H
	CZ	FLSHWR		;FULL: FLUSH THE WRITE BUFFER
				;RETURNS FREE BYTE CNT IN HL
				;AND BUFFER ADDRESS IN DE
	DCX	H
	SHLD	WRBBC		;SAVE BUMPED BYTE COUNTER

	POP	H		;RECOVER SOURCE ADDRESS

	MOV	A,M		;TRANSFER A BYTE
	STAX	D
	INX	H		;BUMP POINTERS
	INX	D
	DCX	B		;DECREMENT BYTE COUNT
	MOV	A,B		;TEST FOR DONE
	ORA	C		;ALSO CLEARS CARRY
	JNZ	WRD1		;SET Z FOR RETURN TOO

	XCHG			;SAVE NEW POINTER
	SHLD	WRBPTR
	POP	D
	RET			;A=0, CARRY=0, Z=0

;**************************************************************
; SUBROUTINE FLUSH WRITE BUFFER: WRITE ANY REMAINING BYTES IN
; THE WRITE BUFFER TO DISK - FIRST FILL OUT TO AN EVEN 128-BYTE
; BOUNDARY WITH NULLS
; ON ENTRY:
;    WRBBC=NUMBER OF FREE BYTES IN THE BUFFER
;    WRPTR POINTS TO THE NEXT EMPTY SPACE NI THE BUFFER
;
; ON EXIT:
;    DE = BUFFER BEGINNING ADDRESS
;    HL = BUFFER BYTE COUNT
;    BC UNCHANGED
; RUDE EXIT ON WRITE ERROR
;**************************************************************
FLSHWR:	PUSH	B
	LHLD	WRBPTR		;POINTER INTO BUFFER
	XCHG			;POINTER TO DE

	LHLD	WRBBC		;FREE BYTE COUNT
	MOV	A,L		;NEED TO ROUND UP?
	ANI	7FH
	JZ	FLWRB2		;JUMP IF ON BOUNDARY

; FILL UP TO 128-BYTE BOUNDARY WITH NULLS

	MOV	B,A		;COUNT OF BYTES TO FILL
	XRA	A		;CLEAR CARRY TOO

FLWRB1:	STAX	D
	INX	D		;BUMP POINTER
	DCR	L		;ADJUST FREE-BYTE COUNT
	DCR	B
	JNZ	FLWRB1

; COMPUTE B= NUMBER OF FREE 128-BYTE RECORDS

FLWRB2:	MOV	A,L
	RAL
	MOV	A,H		;HIGH BYTE OF BYTE COUNT
	RAL			;A= # OF FREE 128-BYTE RECS

	MOV	B,A

; COMPUTE NUMBER OF 128-BYTE RECORDS TO COPY

	MVI	A,DBSIZ
	SUB	B		;B= # OF 128-BYTE RECS TO COPY
	POP	B
	RZ			;QUIT IF NOTHING TO DO	

	PUSH	B

	MOV	B,A		;WRITE B 128-BYTE RECORDS
	LXI	H,WRBUF		;RESET FILE POINTER
	XCHG			;DE = BUFFER ADDRESS
	PUSH	D		;SAVE FOR RETURN

	MVI	C,STDMAF	;SET DMA FUNCTION

; LOOP TO WRITE DBSIZ 128-BYTE RECORDS FROM BUFFER TO DISK

FLWRB3:	PUSH	B		;SAVE COUNT AND STDMAF
	PUSH	D		;SAVE DMA ADDRESS
	CALL	BDOS		;SET DMA ADDRESS=DE

	LXI	D,DSTFCB	;DEST FILE COTROL BLOCK
	MVI	C,WRSEQF	;WRITE SEQUENTIAL
	CALL	BDOS
	POP	D		;RECOVER BUFFER ADDRESS

	ORA	A
	JNZ	ERRWRT		;WRITE ERROR: DISK FULL

	LXI	B,128		;BUMP DE=BUFFER ADDRESS
	XCHG
	DAD	B
	XCHG			;DE HAS NEW DMA ADDRESS
		
	POP	B		;RECOVER COUNT AND STDMAF
	DCR	B		;COUNT DOWN 128-BYTE RECORDS
	JNZ	FLWRB3

	POP	D		;BUFFER BEGINNING ADDRESS
	POP	B
	LXI	H,128*DBSIZ	;FREE BYTE COUNT
	RET

;**************************************************************
; SUBROUTINE READ BYTE FROM DISK BUFFER:
; ON ENTRY:
;   RDBBC = BYTE COUNT IN READ BUFFER
;   RDBPTR = ADDRESS OF NEXT BYTE IN READ BUFFER
; ON EXIT:
;   A=CHR
;   RDBBC = BYTE COUNT IN READ BUFFER
;   RDBPTR = ADDRESS OF NEXT BYTE IN READ BUFFER
;   ALL REGISTERS PRESERVED
;   Z SET MEANS NO DATA: END OF BUFFER
;**************************************************************
RDDISK:	PUSH	H

	LHLD	RDBBC		;READ BUFFER BYTE COUNT
	MOV	A,L		;TEST FOR BYTE COUNT=0
	ORA	H
	CZ	FILRDB		;EMPTY: GO FILL THE READ BUFFER
				;Z SET FOR EOF, HL=NEW BYTE CNT

	DCX	H		;DECREMENT BYTE COUNT
	SHLD	RDBBC

	LHLD	RDBPTR
	MOV	A,M		;ACTUALLY GET THE DATA NOW
	INX	H
	SHLD	RDBPTR

	POP	H
	RET			;Z STILL VALID FROM FILRDB
				;OR 'ORA H' ABOVE	
;**************************************************************
; SUBROUTINE FILL READ BUFFER: READ AS MANY 128-BYTE CP/M DISK
; "SECTORS" AS WE CAN, STOPPING WHEN OUR BUFFER IS FULL OR WE
; FIND THE END OF FILE.
; ON EXIT:
;   RDBPTR = RDBUF
;   HL = NUMBER OF BYTES IN RDBUF
;   Z SET IF NO MORE DATA (EOF AND RDBBC=0)
;   A TRASHED
;   ALL OTHER REGS PRESERVED
;**************************************************************
FILRDB:	PUSH	D
	PUSH	B

	MVI	B,DBSIZ		;DBSIZ 128-BYTE RECS AT A TIME
	LXI	H,RDBUF		;RESET FILE POINTER
	SHLD	RDBPTR

	XCHG			;DE = BUFFER ADDRESS
	MVI	C,STDMAF	;SET DMA FUNCTION

	LXI	H,0		;BYTE COUNT IN HL

; LOOP TO READ UP TO DBSIZ 128-BYTE RECORDS FROM DISK INTO BUFF

FLRDB1:	PUSH	B		;SAVE COUNT AND STDMAF
	PUSH	H		;SAVE BYTE COUNT
	PUSH	D		;SAVE DMA ADDRESS
	CALL	BDOS		;SET DMA ADDRESS=DE

	LXI	D,SRCFCB	;SOURCE FILE COTROL BLOCK
	MVI	C,RDSEQF	;READ SEQUENTIAL
	CALL	BDOS
	POP	D		;RECOVER BUFFER ADDRESS
	POP	H		;BYTE COUNT

	ORA	A
	JNZ	FLRDB2		;EOF FOUND?

	LXI	B,128		;BUMP DE=BUFFER ADDRESS
	XCHG
	DAD	B
	XCHG			;DE HAS NEW DMA ADDRESS

	DAD	B		;HL HAS NEW BYE COUNT
		
	POP	B		;RECOVER COUNT AND STDMAF
	DCR	B		;COUNT DOWN 128-BYTE RECORDS
	JNZ	FLRDB1

	PUSH	B		;COMPENSATE FOR FOLLOWING POP

; DETERMINE IF WE GOT ANY DATA AND RETURN

FLRDB2:	POP	B		;RECOVER B=RECORD COUNT
	MVI	A,DBSIZ
	SUB	B		;SET Z IF B=DBSIZ

	POP	B		;RESTORE & RETURN
	POP	D
	RET

;**************************************************************
; SUBROUTINE PRINT STRING ON CP/M CONSOLE:
; ON ENTRY:
;   DE=ADDRESS OF $-TERMINATED STRING
;   BC AND HL ARE PRESERVED
;   ENTRY AT PCRSTR PRINTS A CR-LF FIRST
;   ENTRY AT PRTSTR DOES NOT
; ON EXIT:
;   Z FLAG SET
;**************************************************************
PCRSTR:	PUSH	D
	LXI	D,CRMSG
	CALL	PRTSTR
	POP	D
;	FALL INTO PRTSTR

PRTSTR:	PUSH	B
	PUSH	H	;PRESERVE REGISTERS
	MVI	C,PRINTF ;TYPE CHARACTER IN E ON CONSOLE
DOBDOS:	CALL	BDOS
	POP	H
FNCDON:	POP	B	;RESTORE REGISTERS

	ORA	A	;SET Z FLAG
	RET

;**************************************************************
; SUBROUTINE PRINT DECIMAL:
; PRINT VALUE IN B IN DECIMAL ON CONSOLE
; ALL REGS TRASHED	
;**************************************************************
PRINTD:	LXI	H,0		;SUPRESS LEADING 0'S
	MVI	C,100
	CALL	DIGIT
	MVI	C,10
	CALL	DIGIT

	MOV	A,B		;last digit is simple
	JMP	DIGIT0		;and is not suppressed

;----------------------------------------------------
; Local subroutine to divide B by power of 10 in C
; and print result, unless it's a leading 0.
; On Entry:
;   B=Dividend
;   C=divisor (a power of 10)
;   H=0 if all prior digits were 0
; On Exit:
;   Quotent has been printed, unless it's a leading 0
;   B=remainder
;   H=0 iff this and all prior digits are 0
;----------------------------------------------------
DIGIT:	MVI	L,0FFH	;will go 1 too many times
	MOV	A,B

DIGLP:	INR	L
	SUB	C
	JNC	DIGLP

	ADD	C	;UNDO LAST SUB C
	MOV	B,A	;SAVE REMAINDER FOR RETURN

	MOV	A,L	;TEST NEW DIGIT FOR 0
	ORA	H	;ANOTHER LEADING 0?
	MOV	H,A	;SAVE LEADING 0 STATE
	RZ		;DONE IF STILL SUPPRESSING
 
	MOV	A,L	;MAKE DIGIT ASCII
DIGIT0:	ADI	'0'	

	JMP	PRTCHR	;PRINT A AND RETURN (TRASHES DE)


;**************************************************************
; SUBROUTINE PRINT OCTAL: PRINT VALUE IN A IN OCTAL ON CONSOLE
; HL PRESERVED, ALL OTHER REGS TRASHED	
;**************************************************************
PRTOCT:	MVI	B,3	;HIGH DIGIT IS 2 BITS	
	CALL	OCTDG2
	MVI	B,7	;SECOND DIGIT IS 3 BITS
	CALL	OCTDIG

;	FALL INTO OCTDIG FOR THIRD DIGIT

;************************************************
; LOCAL SUBROUTINE TO PRINT 1 OCTAL DIGIT
; ENTRY AT OCTDIG: 3-BIT VALUE IS IN A BITS 5-7
; ENTRY AT OCTDG2: 2-BIT VALUE IS IN A BITS 6 & 7
; B HAS POST-ROTATE BIT MASK
;************************************************
OCTDIG:	MOV	A,C	;RECOVER ROTATED BYTE
	RLC
OCTDG2:	RLC
	RLC
	MOV	C,A
	ANA	B
	ADI	'0'

; FALL INTO PRTCHR

;**************************************************************
; SUBROUTINE: PRINT CHARACTER IN A ON CP/M CONSOLE
; BC, HL REGISTERS ARE PRESERVED, A, DE TRASHED
;**************************************************************
PRTCHR:	PUSH	B
	PUSH	H	;PRESERVE REGISTERS
	MOV	E,A	;BDOS WANTS THE CHR IN E
	MVI	D,0	;CP/M WANTS D TO BE 0
	MVI	C,CONOF	;TYPE CHARACTER IN E ON CONSOLE
	JMP	DOBDOS	;RECYCLE CODE

;**************************************************************
; ERROR HANDLERS - ALL ASSUME THE STACK IS NOW HOSED
;**************************************************************
ERRWRT:	LXI	D,WRTMSG	;DISK WRITE FAILURE
	JMP	CLOEXT

ERDFC:	LXI	D,DFCMSG	;ERROR CLOSING OUTPUT FILE
	JMP	PRTEXT

ERRCKS:	LXI	D,CKSMSG	;CHECKSUM ERROR IN HEX FILE
	JMP	CLOEXT

ERRASC:	LXI	D,ASCMSG	;ASCII ERROR IN HEX FILE

;	FALL INTO CLOEXT

;**************************************************************
; ASSORTED EXITS
; SINCE THESE ALL EXIT THROUGH WARM BOOT, IT IS OKAY TO LEAVE
; THE STACK HOSED.
; ENTRY AT CLOEXT CLOSES OPEN WRITE FILE FIRST
; ENTRY AT PRTEXT PRINTS $-TERMINATED STRING AT DE PRECEEDED
;   AND FOLLOWED BY A CR LF, THEN EXITS
;**************************************************************
; PRINT ERROR MESSAGE, TIDY UP, AND QUIT
CLOEXT:	PUSH	D

	MVI	C,CLOSEF	;BDOS CLOSE FUNCTION
	LXI	D,DSTFCB	;OUTPUT FILE CONTROL BLOCK
	CALL	BDOS		;CLOSE DESTINATION FILE

	POP	D

PRTEXT:	CALL	PCRSTR	;PRINT CR, THEN MESSAGE AT DE

CREXIT:	LXI	D,CRMSG	;A CR LF AT THE END TO LOOK PRETTY
	CALL	PRTSTR

	JMP	WBOOT

;**************************************************************
; STRINGS NEEDED AFTER SETUP
;**************************************************************
; EXTSTR, TAPSTR, DOLSTR MUST BE IN THIS ORDER:
EXTSTR:	DB	'HEXBIN'
TAPSTR:	DB	'TAP'
DOLSTR:	DB	'$$$'

EXTMSG:	DB	'Operation Complete'	;FALL INTO CRMSG
CRMSG:	DB	CR,LF,'$'
WRTMSG:	DB	'Disk write fail$'
DFMMSG:	DB	'File creation fail$'
SFNMSG:	DB	'Program file not found$'
CFNMSG:	DB	'Loader file not found$'
DFCMSG:	DB	'File close Fail$'
CKSMSG:	DB	'HEX Checkum fail$'
ASCMSG:	DB	'Bad Chr in HEX file$'
BPFERR:	DB	'Bad program file$'
NLFMSG:	DB	'Empty loader file$'
NCRMSG:	DB	'Non-contigous loader file$'
CLSMSG:	DB	'Oversize loader$'
NLDMSG:	DB	'Null loader installed$'
LDRMSG:	DB	'Leader character: $'
CKPMSG:	DB	'Checksum loader page: $'
TRMMSG:	DB	'Bytes trimmed from end: $'
GEMSG:	DB	'>=$'
OCTMSG:	DB	' octal$'

;**************************************************************
; INITIALIZED RAM
;**************************************************************
RDBPTR:	DW	RDBUF		;READ DMA BUFFER POINTER
RDBBC:	DW	0		;READ DMA BUFFER BYTE COUNT
WRBPTR:	DW	WRBUF		;WRITE DMA BUFFER POINTER
WRBBC:	DW	128*DBSIZ	;WRITE DMA BUFFER FREE-BYTE CNT
INTPTR:	DW	0		;INTEL HEX BUFFER POINTER
; PAGE0 MUST FOLLOW INTBC, BECAUSE IT IS ALSO HIGH BYTE
; OF BANNER LENGTH (ALWAYS 0)
INTBC:	DB	0		;DEFAULT BANNER LENGTH
PAGE0:	DB	0		;1 MEANS PAGE 0 DATA PRESENT
TRMCNT:	DB	0		;COUNT OF BYTES TRIMMED FROM END

FILTYP:	DB	0FFH		;FILE TYPE: 0=HEX 1=BIN 2=TAP
LEADER:	DB	0		;LEADER BYTE
BYTADR:	DW	0		;PROGRAM BYTE TARGET ADDRESS
				;-1 FOR PRE-INC ON BIN FILES
LDRADR:	DW	0		;ADDRESS OF LOADER
GOADDR:	DW	0		;GO ADDRESS
NLEDER:	DW	0FFH		;DEFAULT LEADER CHRS
INULLS:	DW	20H		;INITIAL NULLS (AFTER LOADER)
JNULLS:	DW	0CH		;J NULLS (BEFORE GO RECORD)
KNULLS:	DW	0FFH		;DEFAULT TAILER CHRS
GOTLDR:	DB	0		;Z MEANS NO CHECKSUM LOADER

;**************************************************************
; FILE CONTROL BLOCKS FOR READING AND WRITING DISK FILES
;**************************************************************

; SOURCE FCB

SRCFCB:	DB	0		;DR - DEFAULT DRIVE
SRCFIL:	DB	0,'       '	;8 BYTES FOR FILE NAME
				;1ST BYTE 0 FOR DEFAULT
SRCEXT:	DB	'$$$'		;FILE EXTENSION
SRCEX:	DB	0,0,0,0		;EX,S1,S2,RC
SRCD0:	DW	0,0,0,0,0,0,0,0	;D0-D15
	DB	0		;CR
	DB	0,0,0		;R0,R1,R2

; DEST FCB

DSTFCB:	DB	0		;DR - DEFAULT DRIVE
DSTFIL:	DB	'        '	;8 BYTES FOR FILE NAME
DSTEXT:	DB	'$$$'		;FILE EXTENSION
DSTEX:	DB	0,0,0,0		;EX,S1,S2,RC
DSTD0:	DW	0,0,0,0,0,0,0,0	;D0-D15
	DB	0		;CR
	DB	0,0,0		;R0,R1,R2

; PLACE TO SAVE PROGRAM FILE NAME - GOES IN FCB LATER

PRGFIL:	DB	'        $$$'	;PROGRAM FILE NAME

;**************************************************************
; DEBUG INPUT BUFFER, BIG ENOUGH SO THAT IT CAN BE EDITED
; IN DDT TO TRY VARIOUS INPUTS
;**************************************************************
	IF	DEBUG
COMBUF:
 DB 	64	;NUMBER OF SUBSEQUENT BYTES IN BUFFER
 DB 'TESTO.TAP=TEST.BIN CKSUM.HEX { BANNER } /GABCD /I1F /FA /L20 /TF'
;   '0123456789012345678901234567890123456789012345678901234567890123'
	ENDIF
	
;**************************************************************
; 5x8 CHARACTER FONT FOR PRINTING BANNER ON TAPE: THE LEAST-
; SIGNIFICANT BIT IS THE BOTTOM. '1' MEANS THIS AND ALL
; SUBSEQUENT COLUMNS ARE NOT PRINTED
;**************************************************************
FMIN	EQU	20H	;MIN FONT CHR
FMAX	EQU	60H	;MAX FONT CHR
FSIZE	EQU	7	;COLUMNS PER CHARACTER
SKP	EQU	0FDH	;SKIP FURTHER COLUMNS

FONT:	DB	0,0,0,0,0,0,0			;20H SPACE
	DB	0,5Fh,0,SKP,0,0,0		;21H EXCLAIMATION PT
	DB	7,0,7,SKP,0,0,0			;22H DOUBLE QUOTE
	DB	28H,28H,0FEH,28H,0FEH,28H,28H	;23H #
	DB	48H,54H,54H,0FEH,54H,54H,24H	;24H $
	DB	8EH,4AH,2EH,10H,0E8H,0A4H,0E2H	;25H %
	DB	76H,89H,89H,95H,62H,0A0H,SKP	;26H &
	DB	0,6,0,SKP,0,0,0			;27H SINGLE QUOTE
	DB	3CH,42H,81H,SKP,0,0,0		;28H (
	DB	81H,42H,3CH,SKP,0,0,0		;29H (
	DB	24H,18H,7EH,18H,24H,SKP,0	;2AH *
	DB	10H,10H,7CH,10H,10H,SKP,0	;2BH +
	DB	40H,30H,SKP,0,0,0,0		;2CH COMMA
	DB	10H,10H,10H,10H,10H,SKP,0	;2DH MINUS
	DB	0,80H,0,SKP,0,0,0		;2EH PERIOD
	DB	2,4,8,10H,20H,40H,80H		;2FH /
	DB	7CH,86H,8AH,92H,0A2H,0C2H,7CH	;30H 0
	DB	84H,82H,0FEH,80H,80H,SKP,0	;1
	DB	84H,0C2H,0A2H,92H,8CH,SKP,0	;2
	DB	44H,92H,92H,92H,92H,6CH,SKP	;3
	DB	1EH,10H,10H,10H,0FEH,10H,SKP	;4
	DB	5EH,92H,92H,92H,92H,62H,SKP	;5
	DB	7CH,92H,92H,92H,92H,64H,SKP	;6
	DB	82H,42H,22H,12H,0AH,6,SKP	;7
	DB	6CH,92H,92H,92H,92H,6CH,SKP	;8
	DB	0CH,92H,92H,92H,92H,6CH,SKP	;39H 9
	DB	0,28H,0,SKP,0,0,0		;3AH COLON
	DB	80H,68H,SKP,0,0,0,0		;3BH SEMICOLON
	DB	10H,28H,44H,82H,SKP,0,0		;3CH <
	DB	28H,28H,28H,28H,28H,SKP,0	;3DH =
	DB	82H,44H,28H,10H,SKP,0,0		;3EH >
	DB	6,1,0A1H,11H,0EH,SKP,0		;3FH ?
	DB	7CH,82H,0BAH,0AAH,0BAH,0A2H,1CH	;40H @
	DB	0F8H,24H,22H,22H,24H,0F8H,SKP	;41H A
	DB	0FEH,92H,92H,92H,92H,6CH,SKP	;B
	DB	7CH,82H,82H,82H,82H,44H,SKP	;C
	DB	0FEH,82h,82H,82H,44H,38H,SKP	;D
	DB	0FEH,92H,92H,92H,82H,82H,SKP	;E
	DB	0FEH,12H,12H,12H,2,2,SKP	;F
	DB	38H,44H,82H,0A2H,0A2H,64H,SKP	;G
	DB	0FEH,10H,10H,10H,10H,0FEH,SKP	;H
	DB	82H,82H,0FEH,82H,82H,SKP,0	;I
	DB	40H,80H,80H,82H,7EH,2,SKP	;J
	DB	0FEH,20H,10H,28H,44H,82H,SKP	;K
	DB	0FEH,80H,80H,80H,80H,80H,SKP	;L
	DB	0FEH,4,8,10H,8,4,0FEH		;M
	DB	0FEH,4,8,10H,20H,0FEH,SKP	;N
	DB	7CH,82H,82H,82H,82H,7CH,SKP	;O
	DB	0FEH,12H,12H,12H,12H,0CH,SKP	;P
	DB	38H,44H,82H,92H,0A2H,44H,0B8H	;Q
	DB	0FEH,12H,12H,32H,52H,8CH,SKP	;R
	DB	4CH,92H,92H,92H,92H,64H,SKP	;S
	DB	2,2,2,0FEH,2,2,2		;T
	DB	7EH,80H,80H,80H,80H,7EH,SKP	;U
	DB	6,18H,60H,80H,60H,18H,6		;V
	DB	3EH,40H,80H,60H,80H,40H,3EH	;W
	DB	82H,44H,28H,10H,28H,44H,82H	;X
	DB	2,4,8,0F0H,8,4,2		;Y
	DB	82H,0C2H,0A2H,92H,8AH,86H,82H	;5AH Z
	DB	0FFH,81H,81H,SKP,0,0,0		;5BH L BRACKET
	DB	80H,40H,20H,10H,8,4,2		;5CH \
	DB	81H,81H,0FFH,SKP,0,0,0		;5DH R BRACKET
	DB	8,4,2,4,8,SKP,0	;5EH CARROT
	DB	80H,80H,80H,80H,80H,80H,80H	;5FH UNDERLINE
	DB	0,0FFH,0,SKP			;60H |

; NOTE: THE LAST CHR OVERLAPS THE FOLLOWING STUFF, WHICH IS OK
	
;**************************************************************
; STACK AND BUFFER SPACE, INITIALIZED FOR EASY DEBUGGING
;**************************************************************
SSPACE:	DW	0,0,0,0,0,0,0,0	;SPACE FOR STACK
	DW	0,0,0,0,0,0,0,0
	DW	0,0,0,0,0,0,0,0
	DW	0,0,0,0,0,0,0,0
LSTACK:	EQU	$	;STACK GROWS TOWARD ADDRESS 0

;==============================================================
;ALL CODE PAST THIS POINT GETS OVERWRITTEN BY THE BUFFERS
;==============================================================

;**************************************************************
;SETUP
;SUBROUTINE: PARSE COMMAND LINE OPTIONS PASSED TO US BY CP/M,
; VALIDATE & SAVE RESULTING VALUES IN MEMORY

; THIS CODE GETS OVERWRITTEN BY THE BUFFERS DURING OPERATION
; ON ENTRY:
;   SP IS VALID
;   COMBUF CONTAINS A VALID STRING FROM CP/M
;   PROGRAM LOAD HAS INITIALIZED RAM VARIABLES
; ON EXIT:
;   COMMAND LINE OPTIONS ARE VALID
;   DESTINATION FCB HAS DESTINATION FILE NAME <TF>.TAP
;   PRGFIL HAS PROGRAM FILE NAME <PF>.{BIN,HEX,TAP}
;   SOURCE FCB HAS CHECKSUM LOADER FILE NAME <CF>.HEX OR HAS
;     EXTENSION $$$, WHICH INDICATES NO CHECKSUM LOADER
;   GOADDR HAS USER INPUT FOR GO ADDRESS OR DEFAULT
;   INTBUF HAS BANNER STRING, IF ANY, INTBC HAS BANNER BYTE CNT
;   INULLS, JNULLS, KNULLS, NLEDER HAVE USER INPUT OR DEFAULTS 
;**************************************************************
;---------------------
; PRINT SIGN-ON BANNER
;---------------------
SETUP:	LXI	D,BANNER
	CALL	PCRSTR
;---------------------------------------------------
; BEGIN TO PARSE OPTIONS PASSED VIA THE COMMAND LINE
;---------------------------------------------------
	LXI	H,COMBUF	;WHERE CP/M PUT THE COMMAND

	MOV	A,M	;FIRST BYTE IS BYTE COUNT
	ORA	A	;ZERO BYTES MEANS HELP
	JZ	HLPEXT	;NO OPTIONS: PRINT HELP STRING

	MOV	B,A	;B KEEPS BYTE COUNT
	INX	H	;POINT PAST BYTE COUNT TO STRING
;----------------------------------------------
; GET THE REQUESTED OUTPUT & PROGRAM FILE NAMES
;----------------------------------------------
	CALL	SKIPW	;SKIP OVER ANY WHITE SPACE TO GET CHR
	JZ	HLPEXT	;NO OPTIONS: PRINT HELP STRING

	CPI	'?'	;IS USER ASKING FOR HELP?
	JZ	HLPEXT

; GET OUTPUT FILE NAME AND PASTE IT INTO THE DEST FCB
; A=FIRST CHARACTER OF FILE NAME

	LXI	D,DSTFIL
	CALL	GTFILA		;RETURNS A=2 FOR .TAP
	CPI	2		;MUST BE .TAP FILE
	JNZ	FNERR		;ONLY .TAP ALLOWED

; GET THE PROGRAM FILE NAME AND SAVE IT AT PRGFIL 
; SAVE THE FILE TYPE AT FILTYP
	CALL	GETCHR		;GET MANDATORY = SIGN
	LXI	D,ERRMSG	;PROGRAM FILE NOT SPECIFIED
	CPI	'='
	JNZ	PRTEXT		;MANDATORY = SIGN MISSING

	LXI	D,PRGFIL
	CALL	GTFIL
	STA	FILTYP	;REMEMBER FILE TYPE FROM EXTENSION

; SET GO ADDRESS TO 0 FOR .BIN FILES AND OFFFFH FOR .HEX FILES.
; THE GO ADDRESS IS NOT USED FOR .TAP FILES

	DCR	A		;A=FF, 00, OR 01
	STA	GOADDR		;SET DEFAULT GO ADDRESS TO 0
	STA	GOADDR+1

;-----------------------------------------------------------
; GET OPTIONS AND PARAMETER VALUES FROM THE CMD LINE BUFFER.
; {BANNER IS ENCLOSED IN BRACES LIKE THIS.}
; EACH OPTION MUST BE PRECEEDED BY A SLASH AND FOLLOWED BY
; 1 OR 2 NUMERIC CHARACTERS. /C IS A SPECIAL CASE, AND IS
; FOLLOWED BY THE CHECKSUM LOADER FILE NAME, WHICH MUST HAVE
; ',HEX' AS ITS EXTENSION. OPTIONS MAY BE PRECEED BY ANY
; REASONABLE NUMBER OF SPACES.
;-----------------------------------------------------------
OPTLUP:	CALL	SKIPW	;SKIP OVER ANY WHITE SPACE TO GET CHR
	RZ		;SETUP DONE AFTER ALL OPTIONS

	CPI	'{'	;{ IS FOLLOWED BY A STRING }
	JZ	GETSTR

	CPI	'/'
	JNZ	INPERR	;ANYTHING ELSE IS BAD INPUT

	CALL	GETCHR	;GET OPTION CHARACTER
	JZ	INPERR	;ERROR: NOTHING AFTER THE SLASH

;CHECK TO SEE IF THIS IS /C, SPECIFYING A CHECKSUM LOADER FILE

	CPI	'C'
	JNZ	NORMOP	;NOT C:IT'S A NORMAL OPTION

	STA	GOTLDR	;WE NOW HAVE A CHECKSUM LOADER

;GET CHECKSUM LOADER FILE NAME, AND PASTE IT IN SRCFIL FCB

	LXI	D,SRCFIL
	CALL	GTFIL	;RETURNS A=0 FOR .HEX
	ORA	A
	JNZ	FNERR	;NOT .HEX IS AN ERROR

	JMP	OPTLUP	;GO GET NEXT OPTION

; NORMAL COMMAND LINE OPTION: GET ITS VALUE, AND SAVE IT	

NORMOP:	MOV	C,A	;SAVE IT WHILE WE GET VALUE

	CALL	GETVAL	;GET DE=VALUE THAT CAME WITH THE OPTION
			;DEAL WITH VALUE ERROR (CARRY=1) LATER
;--------------------------------------------------------------
; GOT A COMMAND LINE OPTION IN C AND A VALUE FOR THAT OPTION IN
; DE. (CARRY SET MEANS BAD VALUE FOUND) LOOP THROUGH VALID
; OPTION TABLE TO FIND MATCHING OPTION. OPTION-ERROR EXIT IF NO
; MATCH. SAVE PARAMETER VAL IN LOCATION SPECIFIED IN THE TABLE.
;--------------------------------------------------------------
	PUSH	H	;REMEMBER POINTER INTO CMD LINE ENTRY
	PUSH	D	;REMEMBER OPTION VALUE
	PUSH	PSW	;REMEMBER CARRY FLAG FROM GETVAL
	MOV	A,C	;RECOVER OPTION CHR

	LXI	H,OPTTAB-1

CHKLUP:	INX	H	;NEXT ENTRY
	CMP	M	;MATCHING OPTION? (ALPHABETIC ORDER)

	INX	H	;POINT DE TO ADDRESS FOR VALUE
	MOV	E,M
	INX	H
	MOV	D,M

	JC	UNRERR	;CARRY MEANS NOT IN TABLE (ALPHABETIC)
	JNZ	CHKLUP	;NO MATCH: GO TRY NEXT OPTION IN TABLE

	POP	PSW	;RECOVER CARRY FROM GETVAL
	JC	BVERR	;BAD VALUE OF SOME SORT

	XCHG		;HL GETS ADDRESS FOR THIS OPTION'S VAL
	POP	D	;RECOVER OPTION VALUE

	MOV	M,E	;SAVE LOW BYTE
	INX	H
	MOV	M,D	;HIGH BYTE

	POP	H	;RESTORE POINTER INTO CMD LINE ENTRY
;-------------------------------------------
; GO GET THE NEXT INPUT CHR, IF THERE IS ONE
;-------------------------------------------
	JMP	OPTLUP
;-------------------------------------------------------
; GET '}'-TERMINATED BANNER STRING AND SAVE IT IN INTBUF
;-------------------------------------------------------
GETSTR:	MVI	C,0	;STRING BYTE COUNT=0
	LXI	D,INTBUF-1 ;DEST POINTER

GSLOOP:	INX	D
	INR	C	;TEST FOR C WRAPPING TO 0
	STC		;CARRY SET IN CASE OF ERROR EXIT
	JM	INPERR	;ERROR: >127 CHARACTERS

	CALL	GETCHR
	JZ	INPERR	;ERROR: END WITHOUT }

	STAX	D	;PUT CHR IN BUFFER

	CPI	'}'
	JNZ	GSLOOP	;CLEARS CARRY WHEN EQUAL

	DCR	C	;'}' DOES NOT COUNT
	MOV	A,C
	STA	INTBC	;SAVE BYTE COUNT

	JMP	OPTLUP

;**************************************************************
; SUBROUTINE GET FILE NAME: GET FILE NAME FROM INPUT BUFFER AND
; SAVE IT AT (DE), SAVE THE 3-BYTE FILE EXTENSION AT (DE)+8
; ON ENTRY AT GETFILA:
;   A HAS FIRST CHR OF FILE NAME
; ENTRY AT GETFIL & GETFILA
;   B IS CHR COUNT FOR COMMAND LINE TEXT
;   DE HAS ADDRESS FOR FILE NAME
;   HL IS POINTER INTO COMMAND LINE TEXT
; ON EXIT:
;   A=0 IF FILE EXTENSION IS .HEX
;   A=1 IF FILE EXTENSION IS .BIN
;   A=2 IF FILE EXTENSION IS .HEX
;   C, DE TRASHED
;**************************************************************
GTFIL:	CALL	GETCHR
	JZ	FNERR		;END OF INPUT?

GTFILA:	PUSH	D		;SAVE ADDRESS FOR EXTENSION

; LOOP: READ IN UP TO 8 BYTES OF FILE NAME, SAVING IT AT (DE)

	MVI	C,8		;FILENAME CHR COUNT-DOWN
GF1:	CPI	' '		;SPACE?
	JZ	FNERR		;ERROR: EXTESION MISSING

	STAX	D		;SAVE FILE NAME BYTE
	INX	D

	CALL	GETCHR
	JZ	FNERR		;END OF INPUT?

	CPI	'.'		;READY FOR EXTENSION?
	JZ	GF2

	DCR	C		;TOO LONG?
	JNZ	GF1

;ABORT: ERROR IN FILE NAME

FNERR:	LXI	D,MNMSG		;FILE NAME ERROR
	JMP	PRTEXT

; GET THE FILE EXTENSION AND PASTE IT IN PLACE

GF2:	POP	D		;DESTINATION ADDRESS

	PUSH	H
	LXI	H,8		;OFFSET TO EXT ADDRESS
	DAD	D
	XCHG			;DE=EXTENSION ADDRESS
	POP	H

; LOOP: READ EXACTLY 3 BYTES, SAVING RESULT AT (DE)+3

	PUSH	D		;SAVE ADDRESS OF EXTENSION

	MVI	C,3
GF3:	CALL	GETCHR
	JZ	FNERR

	STAX	D		;SAVE EXTENSION BYTE
	INX	D

	DCR	C
	JNZ	GF3

	POP	D		;EXTENSION ADDRESS

; GET AND CHECK FILE TYPE: 
; .HEX: A=0
; .BIN: A=1
; .TAP: A=2
; ANYTHING ELSE: ERROR EXIT

	PUSH	H
	LXI	H,EXTSTR	;TABLE OF EXTENSION STRINGS
	MVI	C,0FFH		;RESULT IN C

GF4:	INR	C
	MOV	A,M		;DONE YET?
	CPI	'$'		;$ ENDS LIST
	JZ	FNERR		;ERROR:EXTENSION NOT FOUND
	
	PUSH	D		;SAVE EXTENSION ADDRESS
	PUSH	B		;SAVE COUNT
	CALL	CHKEXT
	POP	B
	POP	D
	JNZ	GF4		;NO MATCH:KEEP LOOKING

	POP	H
	MOV	A,C		;C=FILE TYPE
	RET

;***************************************
; LOCAL SUBROUTINE CHECK EXTENSION:
; COMPARE 3-BYTE TEXT AT DE TO 3-BYTE
; TEXT AT HL. A=0 AND Z SET IF SAME.
; HL AND DE GET ADVANCED.
;***************************************
CHKEXT:	LXI	B,3	;B=RESULT SO FAR, C=BYTES/EXT

CE0:	LDAX	D
	XRA	M	;COMPARE BYTE
	ORA	B	;COMBINE WITH PREVIOUS COMPARES
	MOV	B,A
	INX	H
	INX	D
	DCR	C
	JNZ	CE0
	ORA	A
	RET

;**************************************************************
; SUBROUTINE SKIP WHITE SPACE: SKIP OVER WHITE SPACE IN THE
; COMMAND LINE BUFFER UNTIL A CHARACTER IS FOUND
; ON ENTRY:
;   B HAS REMAINING BYTE COUNT + 1
;   HL POINTS TO BUFFER
; ON EXIT
;   A=CHR
;   B HAS BEEN DECREMETED
;   HL HAS BEEN ADVANCED
;   Z=1 MEANS END OF BUFFER WITHOUT DATA (A IS INVALID)
;**************************************************************
SKIPW:	CALL	GETCHR
	RZ		;Z SET FOR ERROR
	CPI	' '	;WHITE SPACE
	JZ	SKIPW
	RET		;RETURN WITH CHR IN A AND Z CLEARED

;**************************************************************
; SUBROUTINE GET COMMAND LINE CHARACTER: GET THE NEXT CHARACTER
; FROM THE CMD LINE BUFFER, STRIP OFF PARITY, AND CONVERT ANY
; LOWERCASE TO UPPERCASE.
; ON ENTRY:
;   B HAS REMAINING BYTE COUNT
;   HL POINTS TO BUFFER
; ON EXIT
;   A=CHR
;   B HAS BEEN DECREMETED
;   HL HAS BEEN ADVANCED
;   Z=1 MEANS END OF BUFFER WITHOUT DATA (A IS INVALID)
;   CARRY=0 IF Z=1
;**************************************************************
GETCHR:	MOV	A,B	;ANY MORE CHRS IN BUFFER?
	ORA	A	;ALSO CLEARS CARRY
	RZ

	DCR	B	;BUMP BYTE COUNT

	MOV	A,M	;GET NEXT CHARACTER
	INX	H	;BUMP BUFFER POINTER
	ANI	7FH	;STRIP PARITY

	CPI	'a'	;CONVERT TO UPPERCASE
	RC		;DONE IF LOWER THAN a, Z ALSO CLEARED
	CPI	'z'+1
	JNC	GTCHR1
	ANI	5FH	;REMOVE LOWERCASE-NESS
GTCHR1:	ORA	A	;CLEAR Z FLAG
	RET		;RETURN WITH Z CLEARED

;**************************************************************
; SUBROUTINE GET VALUE: GET HEXDECIMAL VALUE FROM THE BUFFER
; ON ENTRY:
;   A = OPTION CHARACTER
;   B HAS REMAINING COM BUFFER BYTE COUNT + 1
;   HL POINTS TO COM BUFFER
; ON EXIT:
;   DE=VALUE IN BINARY (STRING LENGTH IF /B)
;   B HAS BEEN DECREMETED AS NEEDED
;   HL HAS BEEN ADVANCED AS NEEDED
;   CARRY SET MEANS BAD DIGIT
;   A TRASHED 
;**************************************************************
GETVAL:	LXI	D,0	;ACCUMULATE RESULT IN DE

GVLOOP:	CALL	GETDIG	;GET ONE DIGIT SHIFTED INTO DE
	RC		;ERROR RETURN
	JNZ	GVLOOP
	RET

;************************************************
; LOCAL SUBROUTINE: GET ANOTHER HEX DIGIT INTO DE
; ON EXIT:
;   DE HAS NEW DIGIT
;   Z SET MEANS END OF DIGITS
;   CARRY SET MEANS BAD DIGIT
;   HL,B ADVANCED FOR COMMAND BUFFER
;   A TRASHED
;************************************************
GETDIG:	CALL	GETCHR	;ALSO CLEARS CARRY
	RZ		;Z SET FOR NO CHARACTER
	CPI	' '	;SPACE MEANS END, BUT NOT ERROR
	RZ
	CPI	'/'	;IS NEXT PARAM CRAMMED AGAINST THIS?
	JZ	BACKUP	;OK, BUT BACKUP

	CALL	HEXCON	;CONVERT HEX TO BINARY
	RC		;CARRY SET FOR BAD VALUE
	
;COMPUTE DE=D*16 + A
	RLC		;PUSH NEW GIGIT INTO HIGH NIBBLE
	RLC
	RLC
	RLC

	PUSH	B
	MVI	B,4

SHFLUP:	RAL		;SHIFT NIBBLE INTO DE
	MOV	C,A	
	MOV	A,E
	RAL
	MOV	E,A
	MOV	A,D
	RAL
	MOV	D,A
	MOV	A,C
	DCR	B
	JNZ	SHFLUP

	INR	B	;CLEAR Z FLAG, DON'T TOUCH CARRY
	POP	B

	RET		;RESULT IN D, CARRY SET ON OVERFLOW

BACKUP:	DCX	H	;UNDO GETCHR
	INR	B
	XRA	A	;SET Z FLAG FOR NO NEW DIGIT
	RET		;RETURN WITH RESULT IN D

;**************************************************************
; SETUP ERROR HANDLERS
;**************************************************************
BVERR:	LXI	D,BVMSG		;BAD PARAMETER VALUE
	JMP	PRTEX1

UNRERR:	LXI	D,UNRMSG	;UNRECOGNIZED PARAMETER

PRTEX1:	CALL	PCRSTR		;PRINT CRLF THEN STRING
	
	MOV	A,C		;NOW PRINT THE OFFENDING CHR
	CALL	PRTCHR
	JMP	HLPEXT

INPERR:	LXI	D,ERRMSG	;GENERAL BAD INPUT MESSAGE
	CALL	PCRSTR		;PRINT CRLF THEN STRING
;	FALL INTO HLPEXT

HLPEXT:	LXI	D,HLPMSG	;PRINT HELP STRING IN PARTS
	CALL	PCRSTR

	LXI	D,ANYMSG	;'PRESS ANY KEY FOR MORE'
	CALL	PCRSTR

	MVI	C,CONIF
	CALL	BDOS		;GET ANY CONSOLE CHARACTER

	CPI	CTRLC
	JZ	CREXIT

	LXI	D,HLPMS2	;SECOND HALFOF HELP MESSAGE
	JMP	PRTEXT

;**************************************************************
; COMMAND LINE INPUT TABLE
; EACH ENTRY IS AN ASCII PARAMETER, THEN THE ADDRESS OF THE
; MEMORY LOC WHERE THAT PARAMETER IS SAVED. THE TABLE MUST BE
; IN ALPHABETIC ORDER AND IS TERMINATED WITH 0FFH	
;**************************************************************
OPTTAB:	DB	'G'	;GO ADDRESS FOR EOF RECORD
	DW	GOADDR
	DB	'I'	;NULLS BETWEEN LOADER AND PGM RECORDS
	DW	INULLS
	DB	'J'	;NULLS BETWEEN PROGRAM AND GO RECORD
	DW	JNULLS
	DB	'K'	;NUMBER NULLS AFTER GO RECORD
	DW	KNULLS
	DB	'L'	;NUMBER OF LEADER CHARACTERS
	DW	NLEDER

	DB	0FFH	;END OF TABLE MARKER

;**************************************************************
; $-TERMINATED STRINGS THAT GET WIPED OUT BY BUFFERS
;**************************************************************
UNRMSG:	DB	'Unrecognized: /$'
BVMSG:	DB	'Bad value for /$'
MNMSG:	DB	'Bad file name$'
ERRMSG:	DB	'Bad input$'
BANNER:
 DB CR,LF
 DB       ' ***************************************'
 DB CR,LF,' *  Altair Tape File Creation Utility  *'
 DB CR,LF,' *             Vers. '
 DB VERSION+'0'
 DB '.',VERSMIN/10+'0',(VERSMIN MOD 10)+'0','              *'
 DB CR,LF
 DB ' *         By Martin Eberhard          *'

	IF UNDERC
 DB CR,LF
 DB ' * WARNING: VERSION UNDER CONSTRUCTION.*'
	ENDIF
	IF DEBUG
 DB CR,LF
 DB ' *  ----->>>> DEBUG MODE ON <<<<-----  *'
	ENDIF

 DB CR,LF
 DB ' ***************************************'
 DB CR,LF,'$'

HLPMSG:
 DB 'Tape Structure:',CR,LF,CR,LF
 DB ' TAPE SECTION       OPTION  COMMENT',CR,LF
 DB ' [Banner]           use {}  Human-readable text'
 DB CR,LF
 DB ' [Leader Chrs]      use /L  Start paper tape'
 DB ' reader in this leader',CR,LF
 DB ' [Checksum Loader]  use /C  Loaded & executed by'
 DB ' bootstrap loader',CR,LF
 DB ' [nulls]            use /I  Delay for checksum'
 DB ' loader to start',CR,LF
 DB ' [Program File]             Program to be loaded'
 DB ' by Checksum Loader',CR,LF
 DB ' [nulls]            use /J  Nulls before GO Record'
 DB CR,LF
 DB ' [GO Record]        use /G  Causes execution at'
 DB ' GO address',CR,LF
 DB ' [nulls]            use /K  Nulls at tape at end'
 DB CR,LF,'$'

ANYMSG:	DB	'Press any key for more:$'

HLPMS2:
 DB CR,LF
 DB 'Usage: MAKEALT <TF>.TAP=<PF>.<PE> <Options list>',CR,LF
 DB '  <TF>.TAP = Output filename, must be .TAP',CR,LF
 DB '  <PF>.<PE> = Program filename, <PE>={HEX, BIN, TAP}'
 DB CR,LF
 DB '    (<PF>.TAP just replaces or removes the Checksum'
 DB ' Loader on a tape.)',CR,LF
 DB 'Options:',CR,LF
 DB '  {<Text>}',CR,LF
 DB '       Specifies banner text, within curley braces,'
 DB ' max 7F chrs',CR,LF
 DB '  /C<CF>.HEX',CR,LF
 DB '       <CF>.HEX = Checksum Loader filename, must be'
 DB ' .HEX if present.',CR,LF
 DB '       If no Checksum Loader then a null loader will'
 DB ' be installed',CR,LF
 DB '       (20 bytes of 0Dh).',CR,LF
 DB '  /G<Hex address> (.BIN default=0)',CR,LF
 DB '       Specifies the GO-address for .BIN and .HEX files'
 DB CR,LF
 DB '       This address overides any GO address in a'
 DB ' <PF>.HEX file',CR,LF
 DB '       /G has no effect when the Program File is .TAP'
 DB CR,LF
 db '  /L<hex value> (default=FF, max=FF)',CR,LF
 DB '       Specifies how many Leader Chrs before Checksum Loader'
 DB CR,LF
 DB '  /I<hex value> (default=20, max=FF)',CR,LF
 DB '       Specifies how many nulls before Program File'
 DB CR,LF
 DB '  /J<hex value> (default=0C, max=FF)',CR,LF
 DB '       Specifies how many nulls before GO Record'
 DB CR,LF
 DB '  /K<hex value> (default=FF, max=FF)',CR,LF
 DB '       Specifies how many nulls at tape end$'
;**************************************************************
; UNINITIALIZED RAM BUFFERS OVERWRITE SETUP CODE
;**************************************************************
	ORG	LSTACK

INTBUF	DS	255		;INTEL HEX MAX IS 255 BYTES
ALTBUF	DS	255+5		;255 DATA BYTES + 5 OVERHEAD
ALTBFE	EQU	$		;ALTBUF END ADDRESS
PG0BUF	DS	255+5		;PAGE 0 DATA BUFFER

WRBUF	DS	128*DBSIZ	;DMA BUFFER FOR WRITES
RDBUF	DS	128*DBSIZ	;DMA BUFFER FOR READS

; RDBUF SHOULD BE LAST, IN CASE BANNER IS LONG
;--------------------------------------------------------------
	END	100H


