;==============================================================
;                 IMG2HEX by Martin Eberhard
;                      a PTDOS Utility
;
; Creates an Intel hex file from a PTDOS Image file
;
; Usage:

;   IMG2HEX ?
;
; prints a help screen
;
;  IMG2HEX <ifile>,<ofile>[,bang<bsize>][,T=<type>]
;
; <ifile> must be an Image file.
; <ofile> may be a device driver.
;
; Block size:
;
; <bsize> sets the disk block size for <ofile>. If absent then
; HEX2IMG will use its default block size.
;
; File Type:
;
; <outfile>'s file type is set to this ASCII character. If
; absent, the file type will be the default file type, 'H'
;==============================================================
; NOTES
;
; Written to assemble with Digital Research's ASM.
;
; Page references in the comments refer to the PTDOS ver. 1.5
; USer's Manual unless they state otherwise.
;
; Intel Hex Format
;
; (After the colon, each character is an ASCII hex digit.)
;
; :CCAAAATT<DATA>SS
;
;    : = (ASCII colon) Start-of-record marker
;   CC = Total number of data bytes
; AAAA = Memory load address for the 1st record data byte.
;        Subsequent data bytes in the record are written to
;        sequential memory addresses.
;   TT = Record type. The following record types are supported:
;         00 = Normal data record
;            Note: a type 00 record with no data (CC=00) also
;            serves as an end-of-file record.
;         01 = End-of-file record. (CC must be 00.)
;            Note: AAAA is the execution address for the file.
;<DATA>= CC data bytes, 2 hex digits each.
;   SS = Record checksum. When all hex bytes of the record
;        (including this checksum) are added together, the
;        result will be 00.  
; 
;==============================================================
; Revision History
;
; 1.00  25 February 2015  M.Eberhard
;   Created
; 1.01  2 March 2015  M. Eberhard
;   Improved PTDOS error reporting
;==============================================================
FALSE	equ	0
TRUE	equ	not FALSE

VERBOS	equ	FALSE		;prints lots of stuff

;----------------
;ASCII Characters
;----------------
CR	equ	0Dh		;carriage return
LF	equ	0Ah		;Linefeed
QUOTE	equ	27H		;single quote
MODE	equ	80h		;Sol-20 Mode Select key
ESC	equ	1Bh		;Escape

;---------------
;Program Equates
;---------------
STKSIZ	equ	32		;bytes of reserved stack space
DNLEN	equ	10		;max PTDOS name, including drive
DFLTYP	equ	'H'+80h		;default hex file type
DBLKSZ	equ	4C0h		;defualt hex file block size
MAXBLK	equ	9C0h		;max allowed block size
PROATR	equ	0		;default protection attributes
HBYTES	equ	16		;data bytes per hex record

;-----------------------------
;PTDOS definitions from PTDEFS
;-----------------------------
;1) MAIN ENTRY POINTS  (EP AREA)

SYS	EQU	0BCBCh
RB	EQU	SYS-3	;READ SINGLE BYTE
WB	EQU	SYS-6	;WRITE SINGLE BYTE
UTIL	EQU	SYS-9	;UTILITY HANDLER
RESET	EQU	SYS-12	;RESET TO SYSTEM
ERRL0	EQU	SYS-15	;ERROR LEVEL 0 RETURN FROM DRIVER
ERRL1	EQU	SYS-18	;ERROR LEVEL 1 RETURN FROM DRIVER
ERRL2	EQU	SYS-21	;ERROR LEVEL 2 RETURN FROM DRIVER
SYSGLO	EQU	SYS-23	;POINTER TO SYSTEM GLOBAL AREA
CONIN	EQU	SYS-26	;CONSOLE INPUT ONE CHARACTER
CONOUT	EQU	SYS-29	;CONSOLE OUTPUT ONE CHARACTER
CONTST	EQU	SYS-32	;CONSOLE INPUT READY TEST
PSCAN	EQU	SYS-35	;PARAMETER SCANNER ENTRY
INTDN	EQU	SYS-38	;INTERRUPT DONE ENTRY POINT
INTDK	EQU	SYS-41	;INTERRUPT PROCESSING ENTRY

; 2) SYS CALL OPERATIONS  (CODE FOR OPERATION BYTE)

CREOP	EQU	0	;CREATE FILE  (DE -> PARMS)
OPEOP	EQU	1	;OPEN FILE  (DE -> NAME, HL = BUFF OPT)
KILOP	EQU	2	;KILL FILE  (DE -> NAME)
RBLOP	EQU	3	;READ BLOCK OF DATA
WBLOP	EQU	4	;WRITE BLOCK OF DATA
SPAOP	EQU	5	;MOVE FILE CURSOR
EOFOP	EQU	6	;ENDFILE A FILE
CLOOP	EQU	7	;CLOSE A FILE
CHTOP	EQU	8	;CHANGE TYPE OF FILE
CHAOP	EQU	9	;CHANGE ATTRIBUTES OF A FILE
CHNOP	EQU	10	;CHANGE NAME OF A FILE
INFOP	EQU	11	;GET INFO ABOUT A FILE
SUNOP	EQU	12	;SET DEFAULT UNIT
RETOP	EQU	13	;RETURN TO CI
RESOP	EQU	14	;RESET
ABTOP	EQU	15	;ABORT, THEN RETURN TO CI
SEKOP	EQU	16	;SET FILE CURSOR TO ARBITRARY POINT (SEEK
RNDOP	EQU	17	;MAKE FILE RANDOMLY ACCESSIBLE
CAOP 	EQU	18	;CLOSE ALL FILES (OR ALL ON A UNIT)
CTLOP	EQU	19	;CONTROL/STATUS OF DRIVER
SREOP	EQU	20	;SHORT RESET TO CI
DRDOP	EQU	21	;DELIMITED READ
DWROP	EQU	22	;DELIMITED WRITE
DSPOP	EQU	23	;DELIMITED SPACE
RTROP	EQU	24	;RETURN AND SET TRAP

; 3) FILE PROTECTION ATTRIBUTES

PKIL	EQU	1	;DELETE PROTECT
PWRI	EQU	2	;WRITE PROTECTION
PREA	EQU	4	;READ PROTECTION
PFINF	EQU	8	;INFORMATION PROTECT (FILES WON'T LIST)
PATR	EQU	10h	;ATTRIBUTE CHANGE PROTECTION
PNAT	EQU	20h	;NAME AND TYPE CHANGE PROTECTION
PALO	EQU	40h	;DISK ALLOCATION PROHIBITION
PUSR	EQU	80h	;USER ATTRIBUTE

; 4) SYSTEM GLOBAL AREA

SGA	equ	0	;System Global Area base
GLCIF	EQU	SGA	;CI INPUT FILE #
GLCOF	EQU	SGA+1	;CI OUTPUT FILE #
GLUTF	EQU	SGA+2	;UTILITY FILE #
GLECF	EQU	SGA+3	;CI ECHO FILE #
GLUNI	EQU	SGA+4	;DEFAULT UNIT #
GLPRM	EQU	SGA+5	;# OF PERMANENTLY OPEN FILES
GLMXU	EQU	SGA+6	;MAX UNIT NUMBER (= # DRIVES+1)
GLSWI	EQU	SGA+7	;SYSTEM SWITCHES
GLERS	EQU	SGA+9	;SOFT (LEVEL 2) ERROR WORD
GLERM	EQU	SGA+11	;MODERATE (LEVEL 1) ERROR WORD
GLERH	EQU	SGA+13	;HARD (LEVEL 0) ERROR WORD
GLTRP	EQU	SGA+15	;CI RETURN TRAP WORD
GLPRO	EQU	SGA+17	;USER MEMORY PROTECT WORD
GLFLG	EQU	SGA+19	;CHARACTER WAITING FLAG
GLBYT	EQU	SGA+20	;THE CHARACTER
GLRCH	EQU	SGA+21	;FILE #0 READ CHARACTER ROUTINE POINTER
GLWCH	EQU	SGA+23	;FILE #1 WRITE CHARACTER ROUTINE POINTER
GLTCH	EQU	SGA+25	;CONSOLE TEST CHARACTER READY ROUTINE PTR
GLNCT	EQU	SGA+27	;NULL COUNT FOLLOWING CR
GLVER	EQU	SGA+28	;SYSTEM VERSION
GLDAT	EQU	SGA+29	;DATE (MM,DD,YY)
GLNAM	EQU	SGA+32	;DISK NAME (8 ASCII CHARACTERS)
GLPAS	EQU	SGA+40	;PASSWORD
GLBD1	EQU	SGA+48	;WAIT FOR INTERRUPT ROUTINE ADDRESS
GLBD2	EQU	SGA+50	;RESERVED FOR FUTURE USE
GLIF1	EQU	SGA+52	;INTERRUPT ENABLE FLAG
GLIF2	EQU	SGA+53	;DISK INTERRUPT PROCESSING COMPLETE FLAG
GLLOW	EQU	SGA+54	;LOW ADDRESS OF BUFFER AREA
GLECH	EQU	SGA+56	;ECHO DISABLE SWITCH
GLLOK	EQU	SGA+57	;DISK WRITE LOCK SWITCH
GLUPS	EQU	SGA+58	;UPSHIFT SWITCH
GLRBC	EQU	SGA+59	;READ-BACK CHECK SWITCH
GLBIO	EQU	SGA+60	;BINARY CONSOLE I/O SWITCH
GLLOG	EQU	SGA+61	;LOG FILE SWITCH
GLVRB	EQU	SGA+62	;VERBOSE SWITCH
GLFTR	EQU	SGA+63	;SWITCH RESERVED FOR FUTURE USE

; 6) CXBUF AND OLBUF

CXBUF	EQU	0BCC0H	;COMMAND EXECUTION BUFFER
OLBUF	EQU	0BDC0H 	;OVERLAY BUFFER

; 7) STATUS BITS IN USER DEVICE WORD

SBPRO	EQU	1	;SETTABLE PROMPT MESSAGE
SBINT	EQU	2	;INTERRUPT DRIVEN
SBATB	EQU	4	;DOES ABSOLUTE TABS
SBFF	EQU	8	;DOES FORM-FEEDS
SBSTB	EQU	10h	;SETTABLE TABS
SBTAB	EQU	20h	;DOES TAB'S (CONTROL-I)
SBIAT	EQU	40h	;INTERACTIVE
SBDUP	EQU	80h	;DUPLICATIVE

; 8)  ERROR NUMBERS

ERNEX	EQU	1	;NONEXISTENT FILE
ERAEX	EQU	2	;FILE ALREADY EXISTS
ERUFN	EQU	3	;UNASSIGNED FILE NUMBER
ERPRO	EQU	4	;PROTECTION VIOLATION
ERNIF	EQU	5	;FILE NOT TYPE IMAGE
ERMOP	EQU	6	;ILLEGAL OPERATION ON MULTIPLY OPEN FILE
ERRAC	EQU	7	;RANDOM ACCESS TO NON-RANDON FILE
ERRDV	EQU	8	;RANDOM ACCESS TO DEVICE FILE
ERZBC	EQU	9	;ZERO BLOCK COUNT IN DEVICE IMAGE FILE

ERIOP	EQU	10	;ILLEGAL PTDOS OPERATION
ERIBF	EQU	11	;ILLEGAL BUFFER ADDRESS
ERIBS	EQU	12	;ILLEGAL BLOCK SIZE
ERINM	EQU	13	;ILLEGAL CHARACTER IN NAME
ERICU	EQU	14	;ILLEGAL CHARACTER IN UNIT
ERIUN	EQU	15	;ILLEGAL UNIT
ERNTL	EQU	16	;NAME TOO LONG
ERMEM	EQU	17	;USER MEMORY PROTECT VIOLATION
ERSMP	EQU	18	;SYSTEM MEMORY PROTECT VIOLATION
ERUCN	EQU	19	;UNIT CONFLICT
ERIUP	EQU	20	;ILLEGAL UTILITY OPERATION
ERDRI	EQU	21	;DRIVER ERROR
ERIDA	EQU	22	;ILLEGAL DRIVER ACCESS
ERNCT	EQU	23	;DRIVER NOT PARTICIPATING IN THIS CTRL

EREOF	EQU	24	;END OF FILE ENCOUNTERED (OR BOF)
ERSEK	EQU	25	;SEEK ADDRESS OUT OF RANGE

ERTOP	EQU	26	;TOO MANY FILES OPEN
ERMOV	EQU	27	;MEMORY OVERFLOW
ERNID	EQU	28	;NO FILE ID'S LEFT
ERDFL	EQU	29	;DISK FULL
ERDIR	EQU	30	;DIRECTORY FULL

ERFSB	EQU	32	;FILE STRUCTURE BAD
ERFIC	EQU	33	;FILE ID CONFLICT
ERBSC	EQU	34	;BLOCK SIZE CONFLICT
ERSCC	EQU	35	;SECTOR CONFLICT
ERCFS	EQU	36	;CAN'T FIND SECTOR
ERCRC	EQU	37	;CRC ERROR OR READ/WRITE ABORTS
ERCFT	EQU	38	;CAN'T FIND TRACK
ERRBC	EQU	39	;READ-BACK-CHECK FAILED
ERLOK	EQU	40	;DISK IS WRITE LOCKED
ERXXX	EQU	41	;CATESTROPHIC ERROR
ERSYN	EQU	42	;COMMAND SYNTAX ERROR
ERNAX	EQU	43	;NAME EXPECTED
ERNUX	EQU	44	;FILE NUMBER EXPECTED
ERBCX	EQU	45	;BYTE COUNT EXPECTED
ERADX	EQU	46	;ADDRESS EXPECTED
ERVAX	EQU	47	;VALUE EXPECTED
EROPX	EQU	48	;OPTIONAL PARAMETER EXPECTED
ERINA	EQU	49	;ILLEGAL FILE NAME
ERIFI	EQU	50	;ILLEGAL FILE NUMBER
ERIAD	EQU	51	;ILLEGAL ADDRESS
ERIBC	EQU	52	;ILLEGAL BYTE COUNT
ERIVA	EQU	53	;ILLEGAL VALUE
ERIOS	EQU	54	;ILLEGAL OPTION SPECIFIER
ERITY	EQU	55	;ILLEGAL TYPE
ERIAT	EQU	56	;ILLEGAL ATTRIBUTES
ERRED	EQU	57	;DRIVE NOT READY

; 9) PARAMETER SCANNER OPERATIONS

PSCO	EQU	0	;CREATE IF NONE EXIST AND OPEN
PSOP	EQU	1	;OPEN FILE
PSFCO	EQU	2	;FORCE CREATE AND OPEN FILE
PSC	EQU	4	;CREATE FILE IF NONE EXIST
PSN	EQU	5	;READ NAME TO REG DE
PSFC	EQU	6	;FORCE CREATE FILE
PSOPT	EQU	8 	;READ ALL CHRS UNTIL , ; cr 0
PSV	EQU	85H	;CONVERT VALUE INTO REG DE
PSONE	EQU	0A5H	;READ SINGLE BYTE

;NOTE: ABOVE VALUES + 40H = USER INPUT BUFFER IN REG HL

PSCN	EQU	0D5H	;CONVERT MY VALUE TO DE, BASE IN REG B

; 10) UTILITIES

UXOP	EQU	0	;EXPLAIN ERROR
UXOP1	EQU	1	;DATA FOR UXOP
UXOP2	EQU	2	;DITTO, PART 2
UCAT	EQU	3	;CATALOG ROUTINE

;======================
;= Start of Execution =
;======================
	org	0
;-----------------------------------------------------------
;Set up a local stack and then initialize,
;using code that gets wiped out by the data buffer.
;-----------------------------------------------------------
	lxi	h,STACK
	sphl

;===Init Subroutine=========================================
;Initialization
;Rude jump to HLPEXT if '?' typed instead of input file name
;Rude exit with error message if bogus input
;===========================================================

;----------------------------------------------------------
;Get the input file name from the command line. Check for ?
;----------------------------------------------------------
	lxi	d,IFNAME	;input file name
	call	GOSCAN		;get item
	jz	DFNERR		;mandatory

	dcr	a		;must be at least 1 chr
	cpi	DNLEN		;max PTDOS filename, with drive #
				;..leaves room for a unit number
	jnc	DFNERR		;name too big or too small?

	ldax	d
	cpi	'?'		;request for help?
	jz	HLPEXT

;----------------------------------------------
;Get the output file name from the command line
;----------------------------------------------
	lxi	d,OFNAME	;PTDOS Name goes here
	call	GOSCAN		;get item
	jz	DFNERR		;mandatory

	dcr	a		;must be at least 1 chr
	cpi	DNLEN		;max PTDOS filename, with drive #
				;..leaves room for a unit number
	jnc	DFNERR		;name too big or too small?

;----------------------------------------------------
;Initialize some default variables in the output file
;header. These may soon get overwritten by options.
;----------------------------------------------------
	mvi	a,DFLTYP	;default file type
	sta	OFTYPE

	lxi	h,DBLKSZ	;default block size
	shld	OBSIZE

	mvi	a,PROATR	;file protection attributes
	sta	OPATTR

;------------------------------------------------------------
;Get all subsequent command line items, each will be one of:
; bang <bsize>
; T=<file type>
;Note: if <bsize>=0 then IMG2HEX will compute the "best" size
;------------------------------------------------------------
SCNOPS:	lxi	d,SCNBUF	;put result here
	call	GOSCAN
	jz	PARSDN		;done with the command line?

	lxi	h,SCNOPS	;create return address
	push	h

	ldax	d		;1st chr of this item
	mov	h,a
	cpi	'!'		;set disk block size?
	inx	d
	jz	GETDBS

	ldax	d		;2nd chr of this item
	cpi	'='		;'=' option?
	jnz	OPTERR		;nothing else is legal

	inx	d		;point past '='
	mov	a,h
	cpi	'T'		;File type?
	jnz	 OPTERR		;no other options

;Fall into GETFT

;===Init Subroutine====================
;Parse a 'T=n'
;On Entry:
;  c=byte count
; de=SCNBUF+2, just past the '='
;On Exit:
;  OFTYPE=specified file type, with its
;  msb set, so it is not an image file
;======================================
GETFT:	mov	a,c
	cpi	3		;exactly 3 characters: 'T=n'
	jnz	OPTERR		;..otherwise it is bogus

	ldax	d
	ori	80h		;make it not an Image file
	sta	OFTYPE

	ret

;-------------------------------------------------------------
;Now that the parameters are all scanned, open the input (Hex)
;file. (PTDOS will return with a=file number and b=file type.)
;Remember the input file number in IFNUM
;-------------------------------------------------------------
PARSDN:
 if VERBOS
	call	CILPRT
	db	'Parse done. Opening file','s'+80h
 endif

	lxi	d,IFNAME
	lxi	h,0		;static buffering
	call	SYS
	db	OPEOP		;open file op (page 7-12)
	call	PTDER		;Let PTDOS explain any error

	sta	IFNUM		;remember the file number

	mov	a,b		;file type
	rlc			;msb clear means image file
	jc	IFTERR		;must be an image file

;----------------------------------------------------------
;Create the output file using the header at OFILE, and then
;open the created file, get the file number from PTDOS, and
;remember it in OFNUM. If the file already exists, then
;see if it is a device driver (which need not be opened).
;----------------------------------------------------------

;see if the output file exists, and is a device driver

	lxi	d,OFNAME	;file name is here
	lxi	h,SCNBUF	;destination for info
	call	SYS		;Get the file info
	db	INFOP		;(page 7-10)
	jmp	DOCRE		;doesn't exist?

	lda	SCNBUF+11	;the output file's file type
	inr	a		;FFh means it's a device driver
	jz	DOOPE		;..which is ok
				;..otherwise CREOP will error

;create the output file, unless it's a device driver

DOCRE:	lxi	d,OFILE		;output file header
	call	SYS		;create file
	db	CREOP		;(page 7-7)
	call	PTDER		;Let PTDOS explain any error

;open the output file

DOOPE:	lxi	d,OFNAME	;file name is here
	lxi	h,0		;static buffering (page 5-7)
	call	SYS		;open the disk file
	db	OPEOP		;(page 7-12)
	call	PTDER		;Let PTDOS explain any error

	sta	OFNUM		;remember assigned file number

 if VERBOS
	call	CILPRT
	db	'Converting Image file to he','x'+80h
 endif

;-----------------------------------------------------------
;Initialization complete
;
;  IFNAME = input file name
;           (max 10 characters, including drive number)
;   IFNUM = file number assigned to the input file by PTDOS
;   OFNUM = file number assigned to the output file by PTDOS
; The output file descriptor (OFILE) is set up
;  OFTYPE = user-supplied file type, if one was entered
;  OBSIZE = disk block size
;  OPATTR = PROATR (default protection attributes)
;  OFNAME = ouput file name
;           (max 10 characters, including drive number)
;  Input and output files are open and ready
;-----------------------------------------------------------

;get the size and address of the next Image file segment

SEGLUP:	lda	IFNUM		;file number
	lxi	b,4		;4-byte header
	lxi	d,IHEADR	;where to put it
	call	SYS
	db	RBLOP		;read the header
	call	CHKEOF		;see if err is EOF

	mov	a,c		;EOF found?
	ora	a
	jnz	DOEOF

;add this segment's byte count to the total

	lhld	BYTCNT		;bump total byte count
	xchg
	lhld	ISBCNT		;..by this segment's byte count
	dad	d
	shld	BYTCNT

;build the hex record

	mvi	a,':'
	sta	HRCOL		;install record-start colon

	lhld	ISBADR		;get record address

	lda	SEGCNT		;is this the first segment?
	ora	a
	jnz	NOTR0
	shld	ADDR0		;y: remember its address
	shld	EXEADR		;also default execution addr

NOTR0:	inr	a		;bump segment count
	sta	SEGCNT

 if VERBOS
	push	psw
	call	CILPRT
	db	'Segment:',' '+80h
	pop	psw
	call	PAHEX
 endif

	xchg			;segment address to de

	lhld	ISBCNT		;byte count
	mov	a,h		;zero?
	ora	l
	jz	EXESEG		;y: other type of exe address

;Loop to write all segment bytes as
;16-byte or fewer hex records
; de = address that goes in this record
; hl = byte count for this segment

RECLUP:	push	h		;segment byte count onto stack

	lhld	RECCNT		;bump record count
	inx	h
	shld	RECCNT

	lxi	b,0		;init checksum & byte count

	lxi	h,HRADDR	;point to record address place
	mov	a,d		;big-endian
	call	PUTHEX		;put record address in place
	mov	a,e
	call	PUTHEX

	xra	a		;record type
	call	PUTHEX

;Loop to get and write up to 16 bytes to this record

RBLOOP:	push	h
	push	d
	push	b
	lda	IFNUM
	call	RB		;read one byte (page 6-6)
	call	PTDER		;Let PTDOS explain any error

	pop	b
	pop	d
	pop	h

	call	PUTHEX		;enqueue the data byte

	inx	d		;next record address
	inr	b		;bump record byte count

	xthl			;look at segment byte count
	dcx	h		;bump it too
	mov	a,h		;all bytes of the segment done?
	ora	l
	xthl
	jz	RBLDON

	mov	a,b
	cpi	HBYTES		;full record?
	jnz	RBLOOP		;n: do another byte

RBLDON:	mov	a,b		;install byte count
	push	h
	lxi	h,HRDBC		;data byte count
	call	PUTHEX
	pop	h

	push	d
	call	FINREC		;finish and write the record
	pop	d

;See if this segment has more data

	pop	h
	mov	a,h
	ora	l
	jnz	RECLUP

;Go look for another segment

	jmp	SEGLUP

;-------------------------------------------------------
;Got a segment witn zero data bytes. The 2nd 2 bytes are
;the execution address. (page 5-8). Treat it the same as
;the simpler 2-byte type of execution address.
;-------------------------------------------------------
EXESEG:

 if VERBOS
	call	CILPRT
 db 'EOF assumed because 0-byte segment detecte','d'+80h
 endif

	lhld	ISBADR		;execution address
	jmp	DOEOF2

;----------------------------------------------------------
;Done reading the input file. Create the execution address,
;if one existed in the input Image file.
;On Entry:
; a=b=2 if EOF with execution address
;   Execution address is at IHEADR
; a=b=4 if EOF without execution address
;----------------------------------------------------------
DOEOF:	cpi	2		;execution address?
	jnz	PLAINE		;n: plain EOF

	lhld	IHEADR		;look at the execution address

DOEOF2:	shld	EXEADR		;remember for report
	xchg
	lhld	ADDR0		;same as the 1st address?

	mov	a,h
	xra	d
	mov	b,a
	mov	a,l
	xra	e
	ora	b
	jz	PLAINE		;y: plain EOF

;build an execution address record

 if VERBOS
	call	CILPRT
	db	'Creating type 01 end recor','d'+80h
 endif

	lxi	b,0		;init checksum & byte count

	lxi	h,HRDBC		;byte count goes here
	xra	a		;0-byte record
	call	PUTHEX

	mov	a,d		;given execution address
	call	PUTHEX
	mov	a,e
	call	PUTHEX

	mvi	a,01h		;type 01 record
	call	PUTHEX

	call	FINREC		;finish and send the record
	jmp	ALLDON
	
;Write plain EOF record
; like this: :0000000000	

PLAINE:
 if VERBOS
	call	CILPRT
	db	'Creating type 00 end recor','d'+80h
 endif

	lxi	h,HRDBC
	lxi	b,10*256+'0'

PLAINL:	mov	m,c
	inx	h
	dcr	b
	jnz	PLAINL

;b=0 here
	
	call	FINRC2		;finish and send record

;-----------------------------------------
;Close both the input and the output files
;-----------------------------------------
ALLDON:	call	CLOSE

;----------------
;Announce success
;----------------
	call	ILPRNT
	db	CR,LF+80h

	lda	SEGCNT
	call	PAHEX
	call	ILPRNT
	db	'h segments read',CR,LF+80h

	lhld	RECCNT
	inx	h		;account for EOF record
	call	PHLHEX
	call	ILPRNT
	db	' Intel hex records create','d'+80h

	call	CILPRT
	db	'Start address:',' '+80h
	lhld	ADDR0
	call	PHLHEX

	call	CILPRT
	db	'Execution address:',' '+80h
	lhld	EXEADR
	call	PHLHEX
 
	call	CILPRT
	db	'Total data bytes:',' '+80h
	lhld	BYTCNT
	call	PHLHEX

;----------------------
;Exit normally to PTDOS
;----------------------
	call	SYS		;Return to PTDOS
	db	RETOP		;(page 7-16)

;***Subroutine************************************
;Finish the record and write it to the output file
;On Entry:
;  b = data byte count for this record
;  c = checksum so far
; hl = address for the checksum in the record
;On Exit:
; the hex record was completed and written
; trashes all registers
;*************************************************
FINREC:	xra	a		;compute checksum
	sub	c
	call	PUTHEX

;Fall into FINRC2

;***Subroutine*************************************
;Finish the record and write it to the output file
;On Entry:
;  b = data byte count for this record
; hl = address just past the checksum in the record
;On Exit:
; the hex record was completed and written
; trashes all registers
;**************************************************
FINRC2:	mvi	m,CR		;install CR/LF
	inx	h
	mvi	m,LF

;----------------------------------------------------
;One complete hex record is now in  HRBUF. Send it to
; the output file now.
;----------------------------------------------------
;compute bc=the total number of bytes in the record

	mov	a,b		;data byte count
	add	a		;*2
	adi	13		;all the rest
	
	mov	c,a
	mvi	b,0		;bc = transfer byte count

;Write the record to the output file

	lxi	d,HRBUF		;hex record buffer address
	lda	OFNUM		;output file number
	
	call	SYS		;write block
	db	WBLOP		;(page 7-15)
	call	PTDER		;Let PTDOS explain any error

 if VERBOS
	call	ILPRNT
	db	'.'+80h
 endif

	ret

;***Subroutine****************************************
;Check for EOF when reading segment header
;On Entry:
;  a = PTDOS error code
; bc = number of bytes not transferred > 0
;      (Bytes transfered = 4-bc)
;      2 indicates an execution address was found
;      4 indicates an end without an execution address
;        (the end of a non-executable file)
;On exit:
;  c = 2 (for EOF with execution address)
;  c = 4 (for EOF without execution address)
;trashes psw,b
;otherwise exit via PTDER
;*****************************************************
CHKEOF:	cpi	EREOF		;error due to EOF?
	jnz	PTDER		;n:let PTDOS explain error

 if VERBOS
	push	psw
	call	CILPRT
	db	'EOF detecte','d'+80h
	pop	psw
 endif

	mov	b,a		;save error code

	mov	a,c
	ani	0F9h		;0 if value is 2,4,6
				;(and it can't be 6)
	mov	a,b		;restore error code
	jnz	PTDER		;error if some other value

 if VERBOS
	mov	a,c
	cpi	2
	jnz	VERB1
	call	ILPRNT
	db	', with an execution addres','s'+80h
VERB1:
 endif

	ret			;result in c

;***Subroutine*************************
; Put one hex byte in the record buffer
;On Entry:
;  a = binary byte
;  c = checksum so far
; hl = buffer address
;On Exit:
;  de has been incremented twice
;  checksum in c has been updated
;trashes psw
;************************************** 
PUTHEX:	push	psw		;temp save
	add	c		;compute checksum
	mov	c,a
	pop	psw
	push	psw
	rar			;shift high nibble down
	rar
	rar
	rar
	call	PUTNIB

	pop	psw		;now the low nibble

;Fall into PUTNIB

;---Local subroutine--------------------
;Put one hex nibble in the record buffer
;On Entry:
;  a<3:0>=nibble value
;  hl = buffer address
;On Exit:
;  hl has been incremented
;trashes psw
;---------------------------------------
PUTNIB:	ani	0Fh		;strip nibble
	adi	'0'
	cpi	'9'+1
	jc	PNIB1
	adi	'A'-'9'-1
PNIB1:	mov	m,a
	inx	h
	ret

;***Subroutine*************************************
;Print CR,LF, inline string
;The call to CILPRT is followed by an ASCII string.
;The last string byte has its msb set.
;trashes psw
;**************************************************
CILPRT:	call	ILPRNT
	db	CR,LF+80h

;Fall into ILPRNT

;***Subroutine*************************************
;Print inline string
;The call to ILPRNT is followed by an ASCII string.
;The last string byte has its msb set.
;trashes psw
;**************************************************
ILPRNT:	xthl			;Save hl, get msg addr
	push	b

IPLOOP:	mov	a,m
	ani	7Fh		;strip end marker
	call	CONOUT		;print byte (trashes b)
	mov	a,m		;end?
	inx	h		;Next byte
	ora	a		;msb set?
	jp	IPLOOP		;Do all bytes of msg

	pop	b
	xthl			;Restore hl,
				;..get return address
	ret

;=====Subroutine==========================
;Print hl as 4 hex digits, followed by 'h'
;On Entry
;  hl = value to print
;Trashes psw,b
;=========================================
PHLHEX:	mov	a,h		;high byte 1st
	call	PAHEX
	mov	a,l		;then the low byte
	call	PAHEX

	mvi	a,'h'

	jmp	CONOUT

;=====Subroutine========
;Print a as 2 hex digits
;On Entry
;  a = value to print
;Trashes psw,b
;=======================
PAHEX:	push	psw		;save digit
	rrc			;high nibble 1st
	rrc
	rrc
	rrc
	call	PHXNIB
	pop	psw		;recover low nibble

;fall into PHXNIB

;----------------------------------------
;local subroutine to print one hex nibble
;On Entry:
;  Nibble in low 4 of a
;Trashes a,b
;----------------------------------------
PHXNIB:	ani	0Fh		;get low
	cpi	0Ah		;A-F?
	jc	PHXN1
	adi	'A'-'9'-1
PHXN1:	adi	'0'

	jmp	CONOUT

;***Subroutine*******************************
;Inspect and save a disk block size.
;On Entry:
;  c=byte count
; de=SCNBUF+1, the location of the block size
;On Exit:
; DBSIZE=new block size
;trashes all registers
;********************************************
GETDBS:	mov	a,c		;at least one chr after bang?
	sui	2
	cpi	5		;no more than 4 byte size chrs?
	jnc	DBSERR

	xchg			;address of hex to hl
	lxi	d,SCNBUF+20	;PSCAN needs an output buffer
	mvi	a,PSCN		;PSCAN convert hex to binary
	mvi	b,16		;Tell PSCAN the input is hex
	call	PSCAN		;result de=execution address
	jc	DBSERR		;PSCAN error? (page 6-12)

	xchg			;hl=block size
	shld	OBSIZE
	mov	a,l
	sui	MAXBLK and 0FFh	;16-bit compare
	mov	a,h
	sbi	MAXBLK/256
	jnc	DBSERR		;error if too big

	ret

;===Init Subroutine=======================================
;Get next item from command line
;On Entry:
;  de = buffer address, where PSCAN will write
;  DELIM = last delimiter received
;  (will be ',' or ';' or CR)
;On Exit:
;  If no more input:
;    Z set
;  If normal item found:
;    Z is cleared
;    a=c= number of characters received (which could be 0)
;    DELIM = terminating delimiter
; trashes b,hl
;=========================================================
GOSCAN:	lda	DELIM		;already reach the end
	cpi	CR		;..of this command?
	rz

	cpi	';'		;may be more commands on
	rz			;..the same command line

	push	d		;buffer address
	mvi	a,PSOPT		;Scan one item
	call	PSCAN		;(page 6-10)
	pop	d
	jc	PSERR		;error if too many characters
	sta	DELIM		;remember delimiter
	ora	d		;clear z flag
	mov	a,c		;a=byte count for return
	ret			;carry and z cleared

;***Subroutine********
;Close both disk files
;trashes psw,hl
;*********************
CLOSE:
 if VERBOS
	call	CILPRT
	db	'Closing file','s'+80h
 endif

	lxi	h,IFNUM		;get input file number
	call	CLOSE1

	inx	h		;get output file number

;Fall into CLOSE1

;***Subroutine***************************
;Close one disk file, if it is still open
;On Entry:
;  (hl)=file number
;On Exit:
;  (hl)=0
;****************************************
CLOSE1:	mov	a,m		;get file number
	mvi	m,0		;remember it's closed
	ora	a		;already closed?
	rz			;y:don't do it again

	call	SYS		;close file
	db	CLOOP		;(page 7-5)
	call	PTDER		;Let PTDOS explain any error
	ret

;***Exit*****************************************************
;Get PTDOS to explain a SYS error, close the open file(s),
;and return (with reset) to PTDOS
;(Pages 6-8 and 8-1)
;On Entry:
;  a = error code
;  top of stack = address of routine that generated the error
;************************************************************
PTDER:	sta	ERMCD		;Store error code for UTIL

	pop	h		;Get calling address
	dcx	h		;Back up and get SYS opcode
	dcx	h
	dcx	h
	dcx	h
	mov	a,m		;a=SYS opcode
	sta	COMCD		;Give SYS opcode to UTIL

	lxi	h,LHMSG		;'Called from' message
	mvi	a,82H		;UTIL ctrl word (see page 8-1)
				;Returns when done

	call	UTIL
	db	UXOP		;Explain error
	jmp	UERROR		;UTIL Error return
COMCD:	db	0FFh
ERMCD:	db	0FFh
				;UTIL normally returns here

UERROR:	call	CLOSE		;close the disk file(s)
	jmp	REXIT

LHMSG:	db	'IMG2HEX',0

;===Exit==============
;Input file type error
;=====================
IFTERR:	call	CILPRT
	db	'Input must be an Image fil','e'+80h

	lxi	h,IFNUM
	call	CLOSE1		;close input file
	jmp	HLPEXT

;===Exit==============
;Disk block size Error
;=====================
DBSERR:	call	ILPRNT
	db	'Bad disk block siz','e'+80h
	jmp	HLPEXT

;===Exit=============
;Option Error
;====================
OPTERR:	call	CILPRT
	db	'Bad optio','n'+80h
	jmp	HLPEXT

;===Exit=============
;Disk File Name Error
;====================
DFNERR:	call CILPRT
	db	'Bad disk file nam','e'+80h
	jmp	HLPEXT

;===Exit=============
;Generic PSCAN Error
;====================
PSERR:	call	CILPRT
	db	'Huh','?'+80h

;Fall into HLPEXT

;===Exit=============
;Print help screen
;====================
HLPEXT:	call	CILPRT
;    1234567890123456789012345678901234567890123456789012345678901234
 db '          HEX2IMG Version 1.01 by M. Eberhard'
 db CR,LF
 db 'This creates an Intel hex file from a PTDOS Image file.'
 db CR,LF,LF
 db '  IMG2HEX ifile,ofile[,!bsize][,T=ftype]'
 db CR,LF,LF
 db 'ifile is a PTDOS Image file. ofile will be an Intel hex file'
 db CR,LF
 db 'bsize sets the ofile disk block size (default: 4C0h).'
 db CR,LF
 db 'ftype is a 1-character file type for ofile (default: H)','.'+80h

;Fall into REXIT

;***Exit***********************************************
;Error exit 
;Use reset exit to abolish the rest of the command line
;******************************************************
REXIT:	call	SYS		;Reset-Return to PTDOS
	db	RESOP		;(page 7-16)

;===================================
;= Initialized flags and Variables =
;===================================
DELIM:	db	0	;PSCAN delimiter
RECCNT:	dw	0	;hex record count
SEGCNT:	db	0	;Image file segment count
BYTCNT:	dw	0	;file byte count
IFNUM:	db	0	;Input file number assigned by PTDOS
OFNUM:	db	0	;Output file number assigned by PTDOS
			;(must follow OFNUM)

;=====================================
;= Uninitialized flags and Variables =
;=====================================
ADDR0:	ds	2	;the first record's address
EXEADR:	ds	2	;execution address

;==========================================================
;= Uninitialized data structures                          =
;= These are filled by PSCAN. Note that PSCAN will use 20 =
;= bytes with each call, overwriting the following data   =
;= structure, so their order in memory matters.           =
;==========================================================

;-------------------------
;Image File Segment Header
;-------------------------
IHEADR:
ISBCNT:	ds	2		;segment byte count
ISBADR:	ds	2		;segment memory address

;----------------------------------------
;Input File name from user 
;(PSCAN will overflow IFNAME by 10 bytes)
;----------------------------------------
IFNAME:	ds	10		;Disk file name, including drive				
	ds	1		;room for numm termination
;-------------------------------------------
;Output File Header for CREATE SYS operation 
;Must follow INFILE
;(PSCAN will overflow OFNAME by 10 bytes)
;-------------------------------------------
OFILE:
OFTYPE:	ds	1		;disk file type
OBSIZE:	ds	2		;Disk block size
OPATTR:	ds	1		;Disk protection attributes
OFNAME:	ds	10		;Disk file name, including drive
	ds	1		;room for null termination

;----------------------------------------
;Hex record buffer
;Room for a HBYTES-byte record plus CR LF
;----------------------------------------
HRBUF:
HRCOL:	ds	1		;the record-start colon
HRDBC:	ds	2		;data byte count
HRADDR:	ds	4		;address
HRTYPE:	ds	2		;Record type
HRDATA:	ds	2*HBYTES	;room for HBYTE data bytes
HRCSUM:	ds	2		;checksum (may be sooner)
HRCRLF:	ds	2		;CR-LF (may be sooner)

;==================================
;Bbuffer used during input scanning
;==================================
SCNBUF:	ds	24

;=====
;Stack
;=====
	ds	STKSIZ
STACK:	equ	$

	END


