;***************************************************************
; INTEL HEX LOADER FOR SOLOS/CUTER
;
; BY MARTIN EBERHARD
;
; THIS PROGRAM ADDS A COMMAND 'HL' TO SOLOS OR CUTER. THIS NEW
; COMMAND LOADS INTEL HEX FILES VIA THE SOL (OR 3P+S) SERIAL
; PORT INTO RAM AT THE LOAD ADDRESSES SPECIFIED IN THE HEX FILE.
;
;  VERS     DATE          NOTE
;  1.01   8MAY2011  CREATED, ALLOWS CUTER RELOCATION
;  1.02   8MAY2011  FIXED LOCATION FOR SOL20, RUNS IN USER RAM
;                   INSTALLS CUSTOM COMMAND 'HL'
;  1.03  12MAY2011  CHECK FOR CTRL-C WHILE WAITING FOR CHR
;  1.04  17MAY2011  'E' TOGGLES ECHO
;  1.05  17MAY2011  COUNT & REPORT NUMBER OF RECORDS
;  1.06  17MAY2011  A LITTLE MORE VERBOSE MESSAGES
;  1.07  18MAY2011  TEST FOR EXISTING HL COMMAND, SQUEEZE
;  1.08  22MAY2011  ALLOWS ADDRESS OFFSET VIA RAM VARIABLE
;  1.09  22MAY2011  COMPRESS MESSAGES TO KEEP CODE SHORT
;  2.00  23MAY2011  MOVE INSTALLER TO LOW MEM, RELOCATE CODE
;                   DEFAULT ECHO OFF
;
; TO DO:
; + GET LOAD ADDRESS OFFSET FROM USER? OR WITH HEX RECORD?
;***************************************************************

;***************************************************************
; OPERATION
;
; EXECUTING THIS PROGRAM ONLY INSTALLS THE CUSTOM COMMAND 'HL'
; INTO SOLOS/CUTER. IF SOLOS/CUTER'S CUSTOM COMMAND TABLE IS
; FULL, THE LAST ENTRY WILL BE REPLACED WITH 'HL'.
;
; TO USE THIS NEW COMMAND, TYPE 'HL' AT THE '>' PROMPT. THE
; HEX LOADER WILL RESPOND WITH A RECORD COUNT OF 0, THEN BEGIN
; LOOKING AT THE SERIAL PORT FOR VALID INTEL HEX RECORDS.
; CONTROL WILL RETURN TO SOLOS WHEN AN EOF RECORD IS FOUND, OR
; WHEN AN ERROR IN THE SERIAL DATA IS DETECTED.
;
; UPON COMPLETION OR ERROR, THIS PROGRAM WILL REPORT (IN HEX)
; THE TOTAL NUMBER OF RECORDS RECEIVED, INCLUDING THE ERROR
; RECORD, IF THERE WAS AN ERROR. NO FURTHER RECORDS WILL BE
; PROCESSED AFTER AN ERROR HAS BEEN DETECTED.
;
; TYPING CONTROL-C WILL ABORT THE HEX LOAD AND RETURN CONTROL
; TO SOLOS WITHOUT REPORTING THE RECORD COUNT.
;
; TYPING 'E' WILL TOGGLE ECHOING OF THE SERIAL DATA TO THE
; CONSOLE. THE HEX LOADER WILL RESPOND WITH 'E+' TO INDICATE
; THAT ECHO IS ON, OR 'E-' TO INDICATE THAT ECHO IS OFF. ECHO
; DEFAULTS TO OFF.
;
; THE VDM SCREEN SOMETIMES TAKES QUITE A BIT OF TIME TO SCROLL
; TO THE NEXT LINE. AT 9600 BAUD, YOU WILL NEED AROUND 30 mS
; DELAY AFTER LINE FEEDS TO RECEIVE A FILE RELIABLY WITH ECHO
; ON. WORSE, THERE IS NO WAY FOR SOFTWARE TO DETECT AN OVERRUN
; ERROR FROM THE UART. THE ALTERNATIVE TO INSERTING DELAYS IS
; TO TURN ECHO OFF. (PRESS 'E'.) WITH ECHO OFF, 9600 BAUD IS
; NO PROBLEM.
;
; IF YOU WANT TO LOAD THE FILE AT A DIFFERENT ADDRESS THAN THE
; ADDRESSES SPECIFIED IN THE INTEL HEX FILE, YOU CAN SET AN
; OFFSET AT LOCATION 'OFFST' IN MEMORY (CAB4H FOR NORMAL SOLOS
; OR CUTER), ONCE THE HEX LOADER COMMAND HAS BEEN INSTALLED.
; THE 16-BIT VALUE OF OFFST WILL BE ADDED TO EVERY ADDRESS IN
; THE INTEL HEX FILE. uSE 2'S COMPLIMENT MATH TO SUBTRACT.
;
;***************************************************************
; PROGRAM NOTES
;
; THIS PROGRAM IS WRITTEN TO BE ASSEMBLED BY CP/M'S ASM.COM.
;
; THE 'EXECUTE' ADDRESS FOR THIS PROGRAM SHOULD BE THE SAME AS
; THE LOAD ADDRESS, 0000H.
;
; THIS PROGRAM ASSUMES SOLOS/CUTER RUNS AT ITS NORMAL LOCATION,
; 0C000H. HOWEVER, THE BASE ADDRESS OF SOLOS/CUTER CAN BE
; CHANGED TO ANY 256-BYTE BOUNDARY BY CHANGING THE VALUE OF
; 'BASEA', WHICH IS AT THE FIXED ADDRESS 0004H IN RAM.
;
; WHEN INSTALLATION OF THE HL COMMAND IS DONE, THE INSTALLER
; JUMPS BACK TO SOLOS/CUTER RATHER THAN RETURNING. THIS WAY, IF
; IT IS INVOKED FROM (FOR EXAMPLE) N* DISK, IT WILL JUMP TO
; SOLOS/CUTER, ONCE THE NEW COMMAND HAS BEEN ADDED TO
; SOLOS/CUTER - RATHER THAN RETURNING TO N* DOS.
;
; THE COMPLETE PROGRAM HAS BEEN CRAFTED TO FIT WITHIN 512 BYTES
; IN MEMORY STARTING AT ADDRESS 0000. IT WILL TAKE UP ONLY 2
; SECTORS ON A SINGLE-DENSITY N* DISK.
;
; THE HEX LOADER COMMAND RUNS IN THE 'USER AREA' OF THE 1K RAM
; BUFFER ADJACENT TO SOLOS/CUTER (CALLED 'USARE' IN SOLOS
; SOURCE CODE). THIS AREA BEGINS AT 0CAB4H (WHEN SOLOS/CUTER
; IS AT ITS NORMAL C000H HADDRESS) AND ENDS AT THE END OF RAM,
; 0CBFFH. HOWEVER, SOLOS'S STACK BEGINS AT 0CBFF, AND GROWS
; TOWARD 0 -  SO IF THE LOADER IS TO LARGE, SOLOS'S STACK WILL
; OVERWRITE IT. WITH VERSION 2.00 OF THIS PROGRAM, THE STACK HAS
; ABOUT 88 BYTES, ALLOWING FOR ABOUT 44 PUSHES - PLENTY OF SPACE.
;
; THE CODE TO BE RELOCATED IS AT AN ABSOLUTE ADDRESS SUCH THAT
; THE LOW BYTE OF ADDRESSES REMAIN UNCHAGED DURING RELOCATION.
; YOU MUST CHECK TO SEE THAT THE INSTALLER DOES NOT OVERRUN
; 'LOCAT', THE START OF THE CODE TO BE RELOCATED.
;
; ALL OF THE ADDRESSES TO BE RELOCATED ARE FLAGGED WITH LABELS
; 'RL00', 'RL01', AND ONWARD. THESE ARE ALWAYS THE ADDRESS
; OF THE INSTRUCTION THAT INCLUDES AN ABSOLUTE ADDRESS - THE
; HIGH BYTE OF THE ADDRESS TO BE RELOCATED IS 2 MORE THAN THE
; ADDRESS OF THE LABEL.
;
; SOLOS/CUTER GIVES NO PROGRAM ACCESS TO ITS CUSTOM COMMAND
; TABLE (CALLED CUTAB IN SOLOS SOURCE CODE). THIS PROGRAM
; THEREFORE ACCESSES THE TABLE DIRECTLY, AND ASSUMES A FIXED
; ADDRESS FOR THE TABLE. THE ADDRESS USED IN THIS CODE IS
; CORRECT FOR BOTH SOLOS AND CUTER VERSION 1.3.
;
; SOLOS/CUTER PROVIDES NO METHOD FOR PASSING COMMAND LINE
; PARAMETERS TO CUSTOM COMMANDS. THIS MAKES IT DIFFICULT TO
; ENTER THE ADDRESS OFFSET OR ECHO STATE AS COMMAND LINE
; OPTIONS. PERHAPS WE MIGHT DECIDE TO PARSE THE INPUT BUFFER
; (AT ITS ABSOLUTE ADDRESS) FROM THIS PROGRAM IN THE FUTURE?
; EVEN THIS IS TOUGH: CUTER PARKS THE COMMAND LINE IN A RAM
; BUFFER CALLED 'INLIN', BUT SOLOS SEEMS TO PULL THE COMMAND
; LINE DIRECTLY FROM ITS VIDEO MEMORY.
;
;***************************************************************
; INTEL HEX RECORD FORMAT
;
; AFTER THE INITIAL COLON, EACH CHARACTER IS A HEX DIGIT.
;
; :CCAAAATTdataSS
;
;    : = (ASCII COLON) START-OF-RECORD INDICATOR
;   CC = THE TOTAL NUMBER OF DATA BYTES, IN HEX
; AAAA = THE MEMORY LOAD ADDRESS FOR THE FIRST DATA BYTE
;        SUBSEQUENT BYTES ARE WRITTEN TO SEQUENTIAL ADRESSES
;   TT = RECORD TYPE. THE FOLLOWING TYPES ARE SUPPORTED:
;        00 = NORRMAL DATA RECORD
;        01 = END-OF-FILE. EOF RECORD MAY CONTAIN DATA.
; data = CC DATA BYTES (2 HEX DIGITS EACH)
;   SS = RECORD CHECKSUM. WHEN ALL HEX BYTES OF THE RECORD ARE
;        ADDED TOGETHER (INCLUDING THE SS CHECKSUM BYTE) THE
;        RESULT SHOULD BE 0. ANYTHING ELSE IS A CHECKSUM ERROR.
;
; NOTE: THE RECORD ':0000000000', WHICH IS A 0-BYTE NORMAL DATA
; RECORD, IS ALSO ACCEPTED AS AN END-OF-FILE MARKER. IN FACT,
; ANY 0-BYTE NORMAL DATA RECORD, REGARDLESS OF ADDRESS (BUT WITH
; CORRECT CHECKSUM) WILL BE ACCEPTED AS AN END-OF-FILE MARKER.
;
; ANY RECORD TYPE BESIDES 00 AND 01 IS INTERPRETED AS AN ERROR.
;
; NOTE: ANY CHARACTERS THAT OCCUR BEFORE THE INITIAL COLON (OF
; ANY RECORD) WILL BE IGNORED. THIS ALLOWS FOR CARRIAGE RETURNS
; AND LINE FEEDS TO MAKE THE HEX FILE PRETTY, AS WELL AS ANY
; COMMENTS, PROVIDED THE COMMENTS DO NOT INCLUDE ANY COLONS. 
;***************************************************************
SOLOS	EQU	0C000H	;SOLOS BASE ADDRESS

; LOCATIONS IN SOLOS/CUTER 1K RAM BLOCK, OFFSET FROM SOLOS BASE

CUTAB	EQU	083CH	;CUSTOM CMD TABLE, OFFSET FROM SOLOS
USARE	EQU	0AB4H	;USER AREA IN RAM, OFFSET FROM SOLOS

; CUTER/SOLOS ENTRY POINTS, OFFSET FROM SOLOS BASE ADDRESS

INIT	EQU	01H	;COLD-START SOLOS
RETRN	EQU	04H	;RETURN CONTROL TO SOLOS
FOPEN	EQU	07H	;OPEN TAPE FILE
FCLOS	EQU	0AH	;CLOSE TAPE FILE
RDBYT	EQU	0DH	;READ TAPE BYTE
WRBYT	EQU	10H	;WRITE TAPE BYTE
RDBLK	EQU	13H	;READ TAPE BLOCK
WRBLK	EQU	16H	;WRITE TAPE BLOCK
SOUT	EQU	19H	;WRITE CHR IN B TO CURRENT PSEUDO PORT
AOUT	EQU	1CH	;WRITE CHR IN B TO PSEUDO PORT IN A
SINP	EQU	1FH	;READ CURRENT PSEUDO PORT INTO A
AINP	EQU	22H	;READ FROM PSEUDO PORT IN A INTO A

MAXCU	EQU	6	;MAX # OF CUSTOM COMMAND ENTRIES

; CUTER/SOLOS PSEUDO PORT DEFINITIONS

KBDPP	EQU	0	;KEYBOARD PSEUDO PORT
VDMPP	EQU	0	;VDM PSEUDO PORT
SERPP	EQU	1	;SERIAL PSEUDO PORT
PARPP	EQU	2	;PARALLEL PSEUDO PORT
USRPP	EQU	3	;USER-WRITTEN ROUTINE PSEUDO PORT

;ASCII EQUATES

CTRLC	EQU	03H	;CONTROL-C
CR	EQU	0DH	;ASCII CARRIAGE RETURN
LF	EQU	0AH	;ASCII LINE FEED
COLON	EQU	3AH	;ASCII :
CRLF	EQU	0D0AH	;CR LF
HLCMD	EQU	0484CH	;ASCII H L

;***************************************************************
; HEXLOAD LOADER PROGRAM
;***************************************************************
LOCAT	EQU	USARE AND 0FFH	;PAGE 0 ASSEMBLY ADDRESS
	
ORG	0

MAIN:	JMP	MAKHL
;***************************************************************
;SOLOS BASE ADDRESS - CAN BE CHANGED TO RELOCATE THIS CODE
;***************************************************************
BASEA:	DB	SOLOS/256

;***************************************************************
; RELOCATE TABLES
; THE FIRST ENTRY OF EACH TABLE IS THE COUNT OF ADDRESSES TO
; RELOCATE IN THE TABLE.ALL SUBSEQUENT ENTRIES ARE THE LOW BYTE
; OF THE ADDRESS WITHIN THE CODE. AT THIS ADDRESS IS THE HIGH
; BYTE OF AN ADDRESS THAT NEEDS TO BE RECOCATED.
; THE SECOND ENTRY MUST BE THE LOWEST ADDRESS OF ANY ADDRESS IN
; THE TABLE.
;***************************************************************
RTABL:	DB	RLTE1-RTABL		;BYTE COUNT	
	DB	(RL01+2) AND 0FFH
	DB	(RL02+2) AND 0FFH
	DB	(RL03+2) AND 0FFH
	DB	(RL04+2) AND 0FFH
	DB	(RL05+2) AND 0FFH
	DB	(RL06+2) AND 0FFH
	DB	(RL07+2) AND 0FFH
	DB	(RL08+2) AND 0FFH
	DB	(RL09+2) AND 0FFH
	DB	(RL10+2) AND 0FFH
	DB	(RL11+2) AND 0FFH
	DB	(RL12+2) AND 0FFH
	DB	(RL13+2) AND 0FFH
	DB	(RL14+2) AND 0FFH
	DB	(RL15+2) AND 0FFH
	DB	(RL16+2) AND 0FFH
	DB	(RL17+2) AND 0FFH
	DB	(RL18+2) AND 0FFH
	DB	(RL19+2) AND 0FFH
	DB	(RL20+2) AND 0FFH
	DB	(RL21+2) AND 0FFH
	DB	(RL22+2) AND 0FFH
	DB	(RL23+2) AND 0FFH
	DB	(RL24+2) AND 0FFH
	DB	(RL25+2) AND 0FFH
	DB	(RL26+2) AND 0FFH
	DB	(RL27+2) AND 0FFH
	DB	(RL28+2) AND 0FFH
	DB	(RL29+2) AND 0FFH
	DB	(RL30+2) AND 0FFH
	DB	(RL31+2) AND 0FFH
	DB	(RL32+2) AND 0FFH
	DB	(RL33+2) AND 0FFH
	DB	(RL34+2) AND 0FFH
	DB	(RL35+2) AND 0FFH
	DB	(RL36+2) AND 0FFH
	DB	(RL37+2) AND 0FFH
	DB	(RL38+2) AND 0FFH
	DB	(RL39+2) AND 0FFH
	DB	(RL40+2) AND 0FFH
	DB	(RL41+2) AND 0FFH
RLTE1:	DB	(RL42+2) AND 0FFH

;RELOCATE TABLE FOR THE INSTALLER - MUST FOLLOW RLTAB

RLTB2:	DB	RLTE2-RLTB2		;NUMBER OF ENTRIES
	DB	(RL43+2) AND 0FFH
	DB	(RL44+2) AND 0FFH
	DB	(RL45+2) AND 0FFH
	DB	(RL46+2) AND 0FFH
RLTE2:	DB	(RL47+2) AND 0FFH

;***************************************************************
; RELOCATE AND INSTALL HL COMMAND
; FIRST, RELOCATE THE ADDRESSES IN THE CODE IN LOW MEMORY
; THEN RELOCATE THE ADDRESSES IN THIS PROGRAM THAT ACCESS
; SOLOS/CUTER OR THE CODE AFTER IT GETS MOVED.
;***************************************************************
MAKHL: 	LXI	H,RTABL
	CALL	RELOC		;RELOCATE THE CODE
	CALL	RELOC		;RELOCATE THE INSTALLER

;***************************************************************
; MOVE THE CODE INTO PLACE
; SOURCE ADDRESS=LOCAT
; DEST ADDRESS=USARE
; ON ENTRY: B WAS SET UP BY RELOC
;***************************************************************
	LXI	H,LOCAT		;SOURCE
	MOV	C,L 		;DEST LOW BYTE

	PUSH	B		;SAVE DEST ADDRESS FOR LATER

	MVI	D,HLEND-LOCAT	;BYTE COUNT, MUST BE<=256

MVLUP:	MOV	A,M
	STAX	B
	INX	H
	INX	B
	DCR	D
	JNZ	MVLUP

;***************************************************************
; INSTALL HEXLOAD COMMAND IN CUSTOM COMMAND TABLE
; NOTE THAT THE ADDRESS OF SOLOS'S CUSTOM COMMAND TABLE GOT
; RELOCATED TOO.
;***************************************************************

; SEARCH FOR NULL ENTRY IN TABLE. IF THE FIRST 5 ENTRIES ARE
; FULL, THEN INSTALL 'HL' IN THE 6TH (LAST) ENTRY, OVERWRITING
; ANY COMMAND THAT MAY HAVE BEEN THERE. IF THE HL COMMAND
; ALREADY EXISTS IN THE TABLE, THEN REPLACE IT.

RL43:				;MUST BE LOWEST RELOCATED ADDRESS
INSTL:	LXI	H,CUTAB-(USARE AND 0FF00H)-4	;CUST CMD TABLE ADDR
	MVI	B,MAXCU		;MAX CUST CMD ENTRIES
	LXI	D,HLCMD		;2 ASCII CHR COMMAND
	
INST0:	INX	H		;NEXT ENTRY
INST1:	INX	H
	INX	H
	INX	H

	DCR	B		;ANY MORE ENTRIES?
	JZ	INST2		;FULL? OVERWRITE THE LAST ENTRY

	MOV	A,M		;GET FIRST TABLE ENTRY
	ORA	A
	JZ	INST2		;FOUND AN EMPTY ENTRY?

	CMP	D		;FIRST BYTE MATCH?
	JNZ	INST0		;NO: KEEP LOOKING

	INX	H		;CHECK SECOND BYTE
	MOV	A,M
	CMP	E		;SECOND BYTE MATCH?
	JNZ	INST1		;NO: HL ALREADY BUMPED

	DCX	H		;UNBUMP POINTER

; INSTALL HL COMMAND INTO CUSTOM COMMAND TABLE AT HL

INST2:	MOV	M,D
	INX	H
	MOV	M,E
	INX	H
	MVI	M,HEXLD AND 0FFH	;LOW BYTE OF ADDRESS

RL44:			;LABEL HERE SO ITS 2 BYTES EARLY
	INX	H
	MVI	M,(HEXLD/256)	;HIGH BYTE OF ADDRESS

;***************************************************************
; PRINT SIGN-ON MESSAGE
; NOTE THAT THIS USES SOME OF THE RELOCATED CODE
;***************************************************************
		
	LXI	H,SOMSG		;SIGN-ON MESSAGE
	CALL	PRNTF

; PRINT ADDRESS OF OFFST USING INSTALLED CODE

	POP	H		;RECOVER BASE ADDRESS

RL45:	CALL	HEX16		;PRINT 4 HEX DIGITS

; FINISH SIGN-ON MESSAGE

	LXI	H,SOMS2		;SECOND HALF OF MESSAGE
	CALL	PRNTF

; JUMP TO SOLOS - DON'T RETURN. (IF WE RETURN, THEN WE WILL
; RETURN TO N*DOS (NOT CUTER) IF WE INSTALL VIA N* DISK.)

RL46:	JMP	RETRN-(USARE AND 0FF00H)

;***************************************************************
; SUBROUTINE TO PRINT NULL-TERMINATED STRING
; THIS SUBROUTINE MUST NOT BE CALLED BEFORE RELOC HAS BEEN USED
; TO CORRECT ITS CALL ADDRESS TO CUTER/SOLOS'S SOUT ROUTINE.
;***************************************************************
PRNTF:
PRTUP:	MOV	A,M
	ORA	A		;NULL TERMINATION?
	RZ			;RETURN TO SOLOS

	MOV	B,A
RL47:	CALL	SOUT-(USARE AND 0FF00H)
	INX	H
	JMP	PRTUP

;***************************************************************
; SUBROUTINE TO RELOCATE CODE USING RELOCATE TABLE
; ON ENTRY, HL POINTS TO THE DESIRED RELOCATE TABLE
; THE FIRST TABLE ENTRY IS THE COUNT OF ADDRESSES TO RELOCATE
; EACH TABLE ENTRY POINTS TO AN ADDRESS IN THE CODE THAT WILL BE
; RELOCATED. 
;   * EACH ENTRY IS THE LOW BYTE OF AN ADDRESS. THE ASSUMPTION
;     IS THAT THE ENTIRE CODE TO BE RELOCATED IS LESS THAN 256
;     BYTES LONG. IF THE TABLE ENTRY IS LESS THAN THE ADDRESS OF
;     THE FIRST ITEM TO BE RELOCATED (RL00), THEN THE HIGH BYTE
;     OF THE ADDRESS IS 1 INSTEAD OF 0.
;   * EACH ENTRY POINTS TO THE HIGH BYTE OF AN ADDRESS IN THE
;     CODE - THE LOW BYTE DOES NOT GET RELOCATED. (IT IS ALREADY
;     CORRECT BECAUSE OF THE 'ORG LOCAT' BELOW.) THE HIGH BYTE
;     IN THE UNRELOCATTED CODE GETS ADDED TO 'BASEA' TO GET THE
;     RELOCATED ADDRESS.
; THE SECOND TABLE ENTRY MUST BE THE LOWEST ADDRESS THAT WILL
; GET RELOCATED.
; ON EXIT:
;    B = (BASEA) + USARE/256
;    HL POINTS 1 PAST THE LAST TABLE ENTRY
;***************************************************************

; GET BYTE COUNT FROM TABLE

RELOC:	MOV	C,M	;NUMBER OF ADDRESSES TO RELOCATE
	INX	H

; GET LOWEST ADDRESS FROM TABLE

	MOV	E,M	;LOWEST ADDRESS TO BE RELOCATED

; COMPUTE OFFSET FOR RELOCATE - THIS IS THE BASE ADDRESS OF
; SOLOS PLUS THE HIGH-BYTE OFFSET TO USARE.

	LDA	BASEA
	ADI	USARE/256
	MOV	B,A		;B HOLDS OFFSET HIGH BYTE

; GET TABLE ENTRY - ADDRESS OF LOW BYTE OF ADDRESS TO FIX

RCLUP:	MOV	A,M		;LOW BYTE
	INX	H

	MVI	D,0		;HIGH BYTE

; SEE IF WE CROSSED THE PAGE BOUNDARY. THIS ASSUMES THE
; CODE TO BE RELOCATED IS LESS THAN 256 BYTES LONG.

	CMP	E	;LOWEST ADDRESS TO GET RELOCATED
	JNC	RCLP1
	INR	D	;ADDRESS IN 2ND PAGE
RCLP1:	

; RELOCATE ADDRESS HIGH BYTE
		
	PUSH	D	;SAVE LOWEST RELOCATE ADDRESS
	MOV	E,A	;DE HAS ADDRESS NOW
	LDAX	D	;HIGH BYTE ADDRESS FROM CODE
	ADD	B	;ADD IN HIGH BYTE OF OFFSET
	STAX	D
	POP	D

	DCR	C
	JNZ	RCLUP
	RET

;***************************************************************
; THIS IS THE ACTUAL HEXLOAD PROGRAM, BEFORE IT GETS RELOCATED
; THE LOW BYTE OF ADDRESSES ARE THE SAME BEFORE AND AFTER THE
; CODE GETS RELOCATED.
;***************************************************************
ORG	LOCAT	;ORG IN LOW MEMORY - WILL GET RELOCATED

;***************************************************************
; RAM STORAGE
; THESE ARE PLACED FIRST SO THAT THEIR ADDRESS WILL REMAIN FIXED
; WITH DIFFERENT VERSIONS OF THIS PROGRAM.
;***************************************************************
OFFST:	DW	0		;HEX LOAD OFFSET MUST BE HERE.
RCNT:	DW	0		;COUNT OF RECORDS

;***************************************************************
;ENTRY POINT FOR ACTUAL HEX LOAD
;
; REGISTER USAGE:
; B: SCRATCH
; C: RECORD BYTE COUNT
; D: RECORD CHECKSUM
; E: RECORD TYPE; MSB SET ENABLES ECHO
; HL: MEMORY ADDRESS
;***************************************************************
HEXLD:	LXI	H,0
RL01:	SHLD	RCNT	;CLEAR 16-BIT RECORD COUNT

	MOV	E,H	;ENABLE ECHO

RL02:	CALL	PRNTR	;PRINT RECORD COUNT=0

; EAT ALL CHRS UNTIL WE GET RECORD START COLON
RL03:
GETCO:	CALL	GETCH
	CPI	':'
RL04:	JNZ	GETCO

; BUMP RECORD COUNT

RL05:	LHLD	RCNT
	INX	H	;16-BIT INCREMENT
RL06:	SHLD	RCNT

; GET RECORD BYTE COUNT INTO C & RESTART CHECKSUM

RL07:	CALL	GETBY
	MOV	C,B	;BYTE COUNT
	MOV	D,B	;RESET CHECKSUM IN D

; GET RECORD ADDRESS INTO HL

RL08:	CALL	GETBY
	MOV	H,B	;HIGH BYTE OF ADDRESS
RL09:	CALL	GETBY
	MOV	L,B	;LOW BYTE OF ADDRESS

; OFFSET ADDRESS, PUT RESULT IN HL

	PUSH	D	;PRESERVE CHECKSUM, TOGGLE STATE
	XCHG		;DE=ADDRESS FRM FILE
RL10:	LHLD	OFFST
	DAD	D	;HL=ADDRESS WITH OFFSET
	POP	D

; GET RECORD TYPE IN E, PRESERVING MSB=ECHO OFF
; NOTE THAT E MUST HAVE BEEN 00H OR 80H FROM PRIOR RECORD

RL11:	CALL	GETBY

	MOV	A,E	;REMEMBER ECHO STATE
	ORA	B	;THIS RECORD TYPE
	MOV	E,A

; TEST FOR 0-BYTE RECORD, WHICH IS AN ALTERNATE EOF

	MOV	A,C	;CHECK FOR 0-BYTE RECORD
	ORA	A
RL12:	JNZ	BYTLP

	INR	A	;ALL 0-BYTE RECS ARE TYPE 1
	ORA	E	;PUT EOF BACK
	MOV	E,A

RL13:	JMP	GCKSM

; LOOP TO GET DATA INTO MEMORY AT HL

RL14:
BYTLP:	CALL	GETBY
	MOV	M,B
	INX	H
	DCR	C
RL15:	JNZ	BYTLP

; GET AND CHECK RECORD CHECKSUM

RL16:
GCKSM:	CALL	GETBY
	MVI	C,'C'	;CHECKSUM ERROR MSG
RL17:	JNZ	ERROR	;NZ MEANS CHECKSUM ERROR

; ALL DONE WITH THIS RECORD - CHECK RECORD TYPE

	MOV	A,E
	ANI	7FH	;STRIP OFF ECHO BIT, TEST 0
RL18:	JZ	GETCO	;YES: GO GET ANOTHER RECORD

	DCR	A	;RECORD TYPE 1 IS EOF
RL19:	JZ	PRNTR	;DONE: PRINT REC COUNT, RET TO SOLOS

; BAD RECORD TYPE

	MVI	C,'T'	;RECORD TYPE ERROR MSG

;	FALL INTO ERROR, RETURN TO SOLOS

;***************************************************************
; SUBROUTINE TO PRINT ? PLUS ERROR CHR IN C TO CONSOLE, 
; FOLLOWED BY RECORD COUNT
; TRASHES BC, HL	
;***************************************************************
ERROR:	MVI	B,'?'
RL20:	CALL	PRTBC

; FALL INTO PRNTR TO PRINT RECORD COUNT

;***************************************************************
; PRINT THE RECORD COUNT MESSAGE
;***************************************************************
RL21:
PRNTR:	CALL	PCRLF	;INITIAL CRLF

	LXI	B,'R'*256+COLON

RL22:	CALL	PRTBC	;ACTUAL MESSAGE

;PRINT RECORD COUNT (OR VERSION NUMBER DURING INIT)

RL23:	LHLD	RCNT
RL24:	CALL	HEX16

; FALL INTO PCRLF

;***************************************************************
; SUBROUTINE TO PRINT CR LF CONSOLE
;***************************************************************
PCRLF:	LXI	B,CRLF

; FALL INTO PRTBC TO PRINT CRLF, THEN RETURN VIA SOLOS

;***************************************************************
; SUBROUTINE TO PRINT B THEN C TO CONSOLE
;***************************************************************
RL25:
PRTBC:	CALL	SOUT-(USARE AND 0FF00H)
	MOV	B,C
RL26:	JMP	SOUT-(USARE AND 0FF00H)

;***************************************************************
; SUBROUTINE TO OUTPUT HL AS HEX 16 BIT VALUE
;***************************************************************
RL27:
HEX16:	CALL	HEX8	;H FIRST
	MOV	H,L	;THEN L
;
HEX8:	MOV	A,H	;GET THE CHARACTER
	RRC		;MOVE THE HIGH FOUR DOWN
	RRC
	RRC
	RRC
RL28:	CALL	HEX4	;PUT THEM OUT
	MOV	A,H	;THIS TIME THE LOW FOUR

HEX4:	ANI	0FH	;FOUR ON THE FLOOR
	ADI	48	;WE WORK WITH ASCII HERE
	CPI	58	;0-9?
RL29:	JC	OUTH	;YUP
	ADI	7	;MAKE IT A LETTER
OUTH:	MOV	B,A	;OUTPUT IT FROM REGISTER 'B'
RL30:	JMP	SOUT-(USARE AND 0FF00H)

;***************************************************************
; SUBROUTINE TO GET 2 HEX DIGITS FROM THE SERIAL PORT,
; COMBINE THEM INTO A BYTE, AND ADD RESULT TO CHECKSUM
; ON ENTRY:
;   D = CHCKSUM SO FAR
; ON EXIT:
;   B=BYTE OF DATA
;   A=D=NEW CHECKSUM VALUE
;   Z FLAG SET IF CHECKSUM IS 0
;   ALL OTHER REGISTERS PRESERVED
;***************************************************************
RL31:
GETBY:	CALL	GETHX	;GET HIGH NIBBLE
	RLC		;SHIFT HIGH NIBBLE IN PLACE
	RLC
	RLC
	RLC
	MOV	B,A
RL32:	CALL	GETHX	;GET LOW NIBBLE

	ORA	B	;COMBINE NIBBLES
	MOV	B,A	;SAVE RESULT FOR RETURN
	ADD	D	;COMPUTE CHECKSUM
	MOV	D,A
	RET

; LOCAL SUBROUTINE TO GET A HEX DIGIT IN A<3:0>

RL33:
GETHX:	CALL	GETCH
	SUI	'0'	;REMOVE ASCII BIAS
	CPI	10
	RC		;IF 0-9 THEN WE'RE DONE

	SUI	'A'-'9'-1 ;SUBTRACT GAP TO ASCII A
	CPI	10	;BETWEEN ASCII 9 AND A?
RL34:	JC	AERR	;YES: ERROR
	CPI	10H	;ABOVE ASCII F?
	RC		;NO: DONE

;ASCII CHARACTER ERROR - NOT A VALID HEX DIGIT

AERR:	POP	B	;REPAIR STACK: CALL GETHX
	POP	B	;REPAIR: CALL GETBY
	MVI	C,'A'	;ASCII ERROR MSG
RL35:	JMP	ERROR	;PRINT ERROR MESSAGE & EXIT

;***************************************************************
; SUBROUTINE TO GET ONE CHR FROM SERIAL PORT, IGNORING NULLS,
; STRIPPING PARITY, AND CHECKING FOR CONTROL-C OR 'E' FROM USER.
; ECHO CHR TO SOLOS CONSOLE IF ECHO IS ENABLED
; ON ENTRY:
;    MSB OF E IS THE CURRENT ECHO STATE - 0 MEANS ECHO
; ON EXIT:
;    CHR IS IN A
;    MSB OF E TOGGLED IF 'E' TYPED
;    ABORT TO SOLOS IF CTRL-C TYPED
;    ALL REGISTERS PRESERVED
;***************************************************************
GETCH:	PUSH	B
RL36:
GETC1:	CALL	CHKBD	;SEE IF USER TYPED ANYTHING

	MVI	A,SERPP	;SELECT SERIAL PSEUDO PORT
RL37:	CALL	AINP-(USARE AND 0FF00H)	;A=0 AND Z SET IF NO CHR

	ANI	7FH	;STRIP PARITY, TEST NULL, NO CHR
RL38:	JZ	GETC1

; GOT A CHARACTER. ECHO IT IF ALLOWED

	MOV	B,A	;SOLOS WANTS CHR IN B

	MOV	A,E	;ECHO ENABLED?
	RLC

RL39:	CC	SOUT-(USARE AND 0FF00H)	;PRINT IF MSB OF E=1
	MOV	A,B	;RECOVER CHR W/ PARITY STRIPPED
	POP	B
	RET

; LOCAL SUBROUTINE TO DEAL WITH POTENTIAL KEYBOARD INPUT

RL40:
CHKBD:	CALL	SINP-(USARE AND 0FF00H)	;CHECK FOR KBD CHR
	RZ		;NO KBD CHR: DONE

	ANI	7FH	;STRIP PARITY

	CPI	CTRLC	;CONTROL-C TYPED?
RL41:	JZ	RETRN-(USARE AND 0FF00H)
			;YES: SOLOS WILL FIX STACK

	CPI	'E'	;ECHO TOGGLE?
	RNZ

; TOGGLE ECHO

	MOV	B,A	;SAVE ECHO CHR FOR MSG

	MOV	A,E	;TOGGLE ECHO
	XRI	80H
	MOV	E,A		

;PRINT TOGGLE MESSAGE

	ANI	80H	;JUST THE ECHO BIT
	XRI	80H	;BECAUSE SET MEANS ECHO
	RLC		;PUT ECHO BIT IN CARRY AND LSB
	ACI	'+'	;ADD 2 IF NO ECHO: MAKES '-'

	MOV	C,A
RL42:	JMP	PRTBC	;PRINT MESSAGE THEN RETURN
HLEND:
	
;***************************************************************
; SIGN-ON MESSAGE, NOT PART OF RELOCATED CODE
;***************************************************************
SOMSG:	DW	CRLF
	DB	'HEX LOADER 2.00'
	DW	CRLF
	DB	' HL to start'
	DW	CRLF
	DB	' ^C quits'
	DW	CRLF
	DB	' E toggles echo'
	DW	CRLF
	DB	' Set offset at '
	DB	0

SOMS2:	DB	'H'
	DW	CRLF
	DB	0
	
	END
