;==============================================================
;                 GETTAPE by Martin Eberhard
;
; PTDOS Utility to read a Solos/Cuter tape file into a PTDOS
; image file.
;
; NOTE: ASM does not allow exclaimation points in comments
; so we use <bang> here.
;
; Usage:
;   GETTAPE ?
;
; prints a help screen
;
;GETTAPE [tfile][/unit][,dfile][<bang>bsize][,E=N/hhhh][T=type]
;
; tfile is the tape file name. If no tfile name is provided,
; then the first tape file encountered will be used.
;
; unit is the tape unit number, either 1 or 2, and defaults
; to 1.
;
; dfile is the disk file name. If no dfile name is provided,
; then the disk file will have the same name as the tape file.
;
; Block size:
; (bang is anctually an exclamation point.) bsize specifies the
; PTDOS disk block size, and defaults to the constant DBLKSZ.
;
;Execution address:
;  E=N     forces the disk Image file to be non-executable
;          (meaning that it does not have an execution
;          address at its end).
;  E=hhhh  forces the Image file to be executable, with the
;          provided hex address installed at the end of the
;          Image file.
;  If no E= option is present, then the Image file will be
;  executable only of the tape file is executable, and the
;  Image file execution address comes from the tape file.
;
;File Type:
;  T=type sets the disk Image file type to the specified single
;  ASCII character. If no T= option, then the Image file type
;  will be the same as the tape file type.
;==============================================================
; 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.
;
; The data buffer overlays all of the code that is not needed
; once it is time to read from the tape. This frees all but
; about 600 bytes of the RAM below PTDOS for buffer space.
;
; SOLOS/CUTER TAPE FORMAT
;
; File Header format:
;   Bytes     Function     Notes
;    10+     Leader Nulls  00h
;     1      Sync Byte     01h
;     5      File Name     ASCII
;     1      File Type     msb set means not executable
;     2      Length        Number of data bytes in file
;     2      Load Address
;     2      Exec Address
;     3      Spare
;
; Block format (as many blocks as needed for all data bytes):
;
;    256     Data          The last block will have...
;                          ...(Length AND 0FFh) bytes
;     1      CRC
;  
;==============================================================
; Revision History
;
; 0.00  15 February 2015  M.Eberhard
;   Created, never tested
; 1.00  16 February 2015  M.Eberhard
;   * Use local tape-read routine, rather than calling Solos
;   * Error-exit if tape file is to large
;   * Overlay buffers on code to increase buffer space
;   * Support tape drive option on user input
; 1.01 24 Feb 2015 M. Eberhard
;  Fix PSCAN call bug. Return instead of Reset on normal exit.
;  Use local stack (not PTDOS stack). Add "T=" option to set
;  tape file type, use tape's file type otherwise. Allow user
;  to set block size. Improved command parsing. More verbose
;  message when done.
; 1.02 9 March 2015  M. Eberhard
;  Improved PTDOS error reporting
;==============================================================

;----------------
;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
;---------------
PROATR	equ	0		;Disk file protect attributes
DBLKSZ	equ	4C0h		;Disk file block size
MAXBLK	equ	9C0h		;max allowed block size
DNLEN	equ	10		;max PTDOS name, including drive
THLEN	equ	16		;Tape header byte count
TNLEN	equ	5		;Tape file name max byte count
ABTKEY	equ	MODE		;user abort key (Mode/Select)
STKSIZ	equ	32		;local stack size

;----------------------------------------------
;Sol-20 and CUTS standard Tape I/O Port Equates
;----------------------------------------------
TAPPT	equ	0FAH		;Tape status port
TDATA	equ	0FBH		;Tape data
TFE	equ	8		;Tape framing error
TOE	equ	10h		;Tape overrun error
TDR	equ	40h		;Tape receive data ready
TTBE	equ	80h		;Tape transmit buffer empty

;Control bits for TAPPT

TAPE1	EQU	80H		;Turn on tape drive 1
TAPE2	EQU	40H		;Turn on tape drive 2

;-----------------------------
;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 just below PTDOS, and then initialize,
;using code that gets wiped out by the data buffer.
;-----------------------------------------------------------
	lhld	SYSGLO		;find PTDOS's lowest address
	lxi	d,GLLOW
	dad	d		;hl=GLLOW address
	mov	a,m
	inx	h
	mov	h,m
	mov	l,a		;hl=PTDOS's lowest address

	sphl			;..is the top of the stack

	call	INIT		;hl= (GLLOW)

;---------------------------------------------------------------
;   UTNAME = user-supplied tape file name if provided
;            0 or 1 otherwise
;   TDRIVE = 80h for tape drive 0, 40h for tape drive 1
;   EXEADR = user-supplied execution address, or 0
;   EXEFLG = 0FFh if no 'E=' option from user
;          = 1 if user-supplied execution address
;          = 0 if 'E=N' option from user
; The disk header for CREATE (DSKHDR) is mostly set up (page7-7)
;   DFTYPE = user-supplied file type, if T= option
;          = 0 otherwise
;   DBSIZE = disk block size, either default or user-supplied
;   DPATTR = default protection attributes
;   DFNAME = user-supplied disk file name if provided, else 0.
;            (max 10 characters, including drive number,
;            null-terminated)
;---------------------------------------------------------------
;Read tape file. We must do this before opening the disk file
;because we might need to get the file name from the tape.
;RTAPE will fill in TFNAME and DFTYPE as needed.
;---------------------------------------------------------------
	call	RTAPE		;read the tape file

	mvi	a,0		;don't mess up flags
	out	TAPPT		;turn tape off
	
	jc	TAPERR		;Tape error

;----------------------------------
;Create the disk file, open it, and
;get the file number from PTDOS
;----------------------------------
	lxi	d,DSKHDR
	call	SYS		;create file
	db	CREOP		;(page 7-7)
	call	PTDER		;Let PTDOS explain any error

	lxi	d,DFNAME	;0-terminated 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	FILNUM		;remember assigned file number

;-------------------------------------------------------------
;Create an Image Disk file from the tape file in memory by
;installing a 4-byte header before the data, and (if this file
;is executable), a 2-byte execution address after the data.
;A PTDOS Image File has 4 bytes at the beginning (page 5-8):
; 2 bytes of block size
; 2 bytes of load address
;-------------------------------------------------------------
	lhld	TLADDR		;tape file load address
	shld	IMGADR		;goes in Image file header

	lhld	TFSIZE		;tape file byte count
	shld	BYTCNT		;goes in Image file header

;Compute bc=Image file byte count, accounting for the header

	mov	b,h		;bc gets disk file byte count
	mov	c,l
	inx	b		;account for image file header
	inx	b
	inx	b
	inx	b		;bc=total disk file byte count

;Install execution address at end only if file is executable

	lda	EXEFLG		;did user say not executable?
	ora	a
	jz	EXEDON		;not executable: it is done

	lhld	EXEADR		;get user address
	jp	GOTEXE		;1 means use given address

	lhld	TEADDR		;execution address from tape
	lda	TFTYPE		;tape file type

	cma			;msb set means not executable
	ani	80h		;msb clear means not executable
	sta	EXEFLG		;0 for not-exec, 80h for exec
	jz	EXEDON

GOTEXE:	xchg			;de=execution address
	lxi	h,BYTCNT	;compute end address
	dad	b
	mov	m,e		;install execution address
	inx	h		;..after the data
	mov	m,d

;account for the exec address in the Image file byte count

	inx	b		;account for execution address
	inx	b		;..in the disk file byte count
EXEDON:

;-----------------------------------------------
;Ask PTDOS to write the entire tape file to disk
;On Entry:
;  bc = total number of bytes to write
;  Image file starts at IMGBUF
;-----------------------------------------------
	lxi	d,IMGBUF	;Image file start address
	lda	FILNUM		;file number from PTDOS
	call	SYS		;write the entire buffer
	db	WBLOP		;(page 7-15)
	call	PTDER		;Let PTDOS explain any error

;-------------------
;Close the disk file
;-------------------
	call	CLOSE

;----------------------------------
;Announce success and exit to PTDOS
;----------------------------------
	lda	EXEFLG		;executable?
	ora	a		;nz means yes
	jnz	ANNC1

	call	ILPRNT
	db	'non','-'+80h

ANNC1:	call	ILPRNT
	db	'excutable image file created:',' '+80h

	lxi	h,DFNAME	;print disk file name
	mov	a,m		;..from disk header

PNLOOP:	call	CONOUT
	inx	h
	mov	a,m
	ora	a		;null-terminated in header
	jnz	PNLOOP

	call	CILPRT
	db	'Loa','d'+80h
	lhld	IMGADR
	call	PADDR

	lda	EXEFLG		;is the file executable?
	ora	a		;z means no
	jz	ANNC2	

	call	CILPRT
	db	'Execut','e'+80h
	lhld	EXEADR
	call	PADDR
ANNC2:

	call	SYS		;Normal return to PTDOS
	db	RETOP		;(page 7-16)

;==============================================================
;= Subroutines that are needed during and after the tape read =
;==============================================================

;***Subroutine***************************
;Read an entire tape file
;On Entry:
;  UTNAME = user-supplied tape file name
;         = 0 to read first file found
;On Exit:
;  Tape header is in TAPHDR
;  file data is in DATBUF
;  carry set if error occured
;  Tape units are off
;trashes all registers
;****************************************
RTAPE:	call	TPINIT		;find the correct file
	rc			;UART error in header?

;TAPHDR = complete tape header from tape
;de = tape file byte count
;hl = DATBUF, the data destination
;tape is running and ready to be read

;Read in data in 256-byte blocks, each with a CRC at
; the end. The last block may be less than 256 bytes.

RBLOOP:	xra	a		;Clear for later tests
	mov	b,a		;Assume 256-byte block
	ora	d		;More than 256 bytes left?
	jnz	DCRC2		;Y: reduce by 256

	ora	e		;Is entire count 0?
	rz			;y: return with carry clear

	mov	b,e		;This block is the remainder
	mov	e,d		;and the count will be 0
	inr	d		;done, compensate for dcr d

DCRC2:	dcr	d		;Reduce count by 256

	call	RDBLOC		;Read b bytes
	rc			;Abort if error
	stc
	rnz			;abort if CRC error too

	jmp	RBLOOP		;Block OK - read another

;***Subroutine******************
;Read one tape block
;On Entry:
;  b = block size (0 means 256)
; hl = data destination address
;On Exit:
;  Carry set if tape UART error
;  Z cleared if CRC error
;  Z set if all ok
;******************************
RDBLOC:	mvi	c,0		;Initialize the block CRC

RHED2:	call	TAPIN		;Get a tape byte
	rc			;UART error?

	mov	m,a		;store it
	inx	h		;bump address

	sub	c		;Compute the CRC
	mov	c,a		;..the way Solos does it
	xra	c
	cma
	sub	c
	mov	c,a		;CRC Result back to c

	dcr	b		;got all the bytes?
	jnz	RHED2		;n: get another byte

;Get and test the block's CRC byte

	call	TAPIN		;Get CRC byte from tape
	xra	c		;clr carry, set Z if match
	ret			;return with Z set or cleared
				;even if UART error

;***Subroutine*******************************
;Get next tape byte. Abort if user aborts
;On Exit:
;  Carry set if UART framing error or overrun
;trashes a
;********************************************
TAPIN:	call	TWAIT		;Wait for next tape byte
	in	TAPPT		;Tape status
	ani	TFE+TOE		;Any UART data error?

	in	TDATA		;Get the data regardless
	rz			;return if no errors

	stc			;set error flag
	ret

;***Subroutine*********************************
; Wait for next tape byte. Abort if user aborts
;trashes psw
;**********************************************
TWAIT:	in	TAPPT		;Tape status port
	ani	TDR		;wait for data ready
	rnz

	call	CONTST		;any user input?
	jz	TWAIT		;n: keep waiting

	call	CONIN		;y: get the character
	ani	7Fh		;strip parity
	cpi	ABTKEY and 7Fh	;user abort?
	jnz	TWAIT		;n: keep waiting

	jmp	UABORT		;y: abort

;***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,b
;**************************************************
ILPRNT:	xthl			;Save hl, get msg addr

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

	xthl			;Restore hl,
				;..get return address
	ret

;***Subroutine******
;Close the disk file
;trashes psw,hl
;*******************
CLOSE:	lxi	h,FILNUM
	mov	a,m		;get PTDOS 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

;***Subroutine*************************************
;Print ' address', then hl as hex
;The last string byte has its msb set.
;On Entry:
;  hl = binary address to be printed in hex
;trashes psw,b
;**************************************************
PADDR:	call	ILPRNT
	db	' address:',' '+80h

;Fall into PHLHEX

;***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

;========================================================
;= Exits that are needed during and after the tape read =
;========================================================

;***Exit************************
;Tape read error
;*******************************
TAPERR:	call ILPRNT
	db	'Tape read error',CR,LF+80h
	jmp	REXIT

;***Exit************************
;User abort during tape read
;*******************************
UABORT:	xra	a
	out	TAPPT		;tape off

	call	ILPRNT
	db	'Abor','t'+80h

;Fall into REXIT

;***Exit************
;Reset-Exit to PTDOS
;*******************
REXIT:	call	SYS		;Reset return to PTDOS
	db	RESOP		;(page 7-16)

;***Exit*****************************************************
;Get PTDOS to explain a SYS error, close the open file,
;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,GTMSG		;'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
	jmp	REXIT

GTMSG:	db	'GETTAPE',0

;===================================
;= Initialized flags and Variables =
;===================================
EXEADR:	dw	0	;execution address
EXEFLG:	db	0FFh	;0FFh means do what the tape file says
			;1 means executable, use given address
			;0 means force not executable
FILNUM:	db	0	;File number assigned by PTDOS
DELIM:	db	0	;PSCAN delimiter
TDRIVE:	db	80h	;default to drive 1
BUFMAX:	dw	0	;max bytes allowed in buffer

;===============================
;= Initialized data structures =
;===============================

;----------------------------
;User-Supplied Tape File Name
;----------------------------
UTNAME:	db	0,0,0,0,0

;------------------------------------------
;PTDOS disk header for CREATE SYS operation
;------------------------------------------
DSKHDR:
DFTYPE:	db	0		;disk file type
DBSIZE:	dw	DBLKSZ		;Default disk block size
DPATTR:	db	PROATR		;Disk protection attributes
DFNAME:	dw	0,0,0,0,0	;Disk file name, including drive				
	db	0		;null termination for string
;-----------------------
;Solos/Cuter Tape Header
;-----------------------
TAPHDR:
TFNAME:	db	0,0,0,0,0	;Tape file name
	db	0		;This byte must be 0
TFTYPE:	db	0		;Tape file type
TFSIZE:	dw	0		;Tape file size
TLADDR:	dw	0		;Tape file load address
TEADDR:	dw	0		;Tape file execute address
TSPARE:	db	0,0,0		;spare bytes

;==========================================================
;= Image file buffer and received file data buffer        =
;= Note that the Image file written to disk will have 4   =
;= additional bytes preceeding DATBUF. Once the data have =
;= all been read, an execution address may get appended.  =
;==========================================================
IMGBUF:
BYTCNT:	dw	0		;hex file byte count
IMGADR:	dw	0		;hex file start address
DATBUF:	equ	$

;============================================
;= The following code all gets wiped out by =
;= the data buffer when the tape gets read. =
;============================================

;===Init Subroutine=============================================
;Initialization
;On Entry:
;  hl= (GLLOW), the lowest address of PTDOS
;On Exit:
;   UTNAME = user-supplied tape file name if provided
;            0 or 1 otherwise
;   TDRIVE = 80h for tape drive 0, 40h for tape drive 1
;   EXEADR = user-supplied execution address, or 0
;   EXEFLG = 0FFh if no 'E=' option from user
;          = 1 if user-supplied execution address
;          = 0 if 'E=N' option from user
; The disk header for CREATE (DSKHDR) is mostly set up (page7-7)
;   DFTYPE = user-supplied file type, if T= option
;          = 0 otherwise
;   DBSIZE = disk block size, either default or user-supplied
;   DPATTR = default protection attributes
;   DFNAME = user-supplied disk file name if provided, else 0.
;            (max 10 characters, including drive number,
;            null-terminated)
;Rude jump to HLPEXT if '?' typed instead of tape file name
;Rude exit with error message if bogus input
;===============================================================

;-------------------------------------------------------
;Get PTDOS's lowest address, and use it to compute the
;buffer size, adjusting for the bese address of the
;buffer, and leaving room for the stack and for the
;(optional) 2 bytes of execution address. Remember the
;size for checking buffer overflow before the tape load.
;-------------------------------------------------------
INIT:	lxi	d,-DATBUF-2-STKSIZ
	dad	d		;16-bit subtract
	shld	BUFMAX

;------------------------------------------------
;Scan the command line for file names and options
;------------------------------------------------
SCNOPS:	lda	DELIM		;already reach the end
	cpi	CR		;..of this command?
	jz	PARSDN

	cpi	';'		;may be more commands on
	jz	PARSDN		;..the same command line

	mvi	a,PSOPT		;Scan one item
	lxi	d,SCNBUF	;put result here

	push	d		;buffer address
	call	PSCAN		;c=number of chrs(page 6-10)
	pop	d		;de=buffer address
	jc	PSERR		;error if too many characters

	sta	DELIM		;remember delimiter

	ldax	d		;1st chr of this item
	cpi	'?'		;Help?
	jz	HLPEXT

	lxi	h,SCNOPS	;create return address
	push	h

	cpi	'!'		;set disk block size?
	inx	d
	jz	GETDBS

	ldax	d		;2nd chr of this item
	dcx	d
	cpi	'='		;'=' option?
	jz	GETEQO

	lxi	h,UTNAME
	mov	a,m		;tape file name set?
	ora	a
	jz	GETTFN		;n: maybe a tape file name

	lxi	h,DFNAME	;disk file name?
	mov	a,m
	ora	a
	jz	GETDFN		;n: maybe a disk file name

	jmp	OPTERR
PARSDN:

;Initialization is complete. Ready to read the tape

	call	ILPRNT
	db	'Start tape now',CR,LF+80h

	ret

;===Init Subroutine==========================
;Inspect and save a 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+5	;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	DBSIZE
	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==================
;Parse an 'x=<something>' type option
;On Entry:
;  c=byte count
; de=SCNBUF, the location of the x
;====================================
GETEQO:	ldax	d
	inx	d
	inx	d		;point past '='
	cpi	'T'		;File type?
	jz	GETFT

	cpi	'E'		;execution address?
	jnz	 OPTERR

;Fall ito GETEA

;===Init Subroutine=======================
;Parse an 'E=N' or 'E=hhhh'
;On Entry:
;  c=byte count
; de=SCNBUF+2, jsut past the '='
;On Exit:
; EXEFLG = 1 if an address was provided
;        = 0 of the file is non-executable
;trashes all registers
;=========================================
GETEA:	mov	a,c
	sui	3		;min 3 chrs
	cpi	3+1		;max 6 characters: 'E=ABCD'
	jnc	OPTERR		;..otherwise it is bogus

	ldax	d		;is it 'N'?
	sui	'N'		;'N' means force no execution
	jz	SETEXE		;y: this file is not executable
				;n: hl points to hex value

	xchg			;address of hex to hl
	lxi	d,SCNBUF+7	;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	OPTERR		;(page 6-12)

	xchg			;hl=execution address
	shld	EXEADR

	mvi	a,1		;force file to be executable

SETEXE:	sta	EXEFLG		;set/clear executableness flag
	ret

;===Init Subroutine=============
;Parse a 'T=N'
;On Entry:
;  c=byte count
; de=SCNBUF+2, jsut past the '='
;On Exit:
;  DFTYPE=specified file type
;===============================
GETFT:	mov	a,c
	cpi	3		;exactly 3 characters: 'T=n'
	jnz	OPTERR		;..otherwise it is bogus

	ldax	d
	ani	7Fh		;make it an Image file
	sta	DFTYPE

	ret

;===Init Subroutine==================================
;Validate and install tape file name,
;which may also include a tape unit number
;On Entry:
;  c=byte count
; de=SCNBUF, the location of the name
;On Exit:
; TDRIVE set if unit nnumber was provided
; UTNAME has the given name, if provided
; UTNAME = 1 if no name provided (just a unit number)
;trashes psw,c,de,hl
;====================================================
GETTFN:	mov	a,c		;can there be a drive #?
	sui	2		;must be at least 2 chrs
	jc	TNAM1		;..or there can't be a /n

	add	e		;point to chr before the last one
	mov	e,a
	mvi	a,0		;catch carry
	adc	d
	mov	d,a		;de=address of penultimate chr

	ldax	d
	cpi	'/'
	jnz	TNAM1		;n: just check name length

	inx	d		;ultimate chr
	ldax	d		;get drive number

	sui	'1'		;un-ASCII
	cpi	2		;only 1 and 2 allowed
	jnc	TUERR

	rrc			;compute tape control value
	rrc			;put lsb in bit 6
	xri	40h		;invert that bit
	adi	40h		;tape 1=80h, tape 2=40h
	sta	TDRIVE		;remember tape number
				;(Solos manual, page 20)
	dcr	c		;remember we found a tape #
	dcr	c
	
TNAM1:	lxi	h,UTNAME	;name destination
	inr	m		;in case 0 bytes or just /n

	mov	a,c
	ora	a		;no name or just /n?
	rz			;y: no tape file name

	cpi	TNLEN+1		;max file name size
	jnc	TFNERR

	jmp	MOVNAM

;===Init Subroutine==================
;Validate and install disk file name
;On Entry:
;  c=byte count
; de=SCNBUF, the location of the name
;On Exit:
; DFNAME has the given name
;trashes psw,c,de,hl
;====================================
GETDFN:	mov	a,c
	cpi	DNLEN+1		;max PTDOS filename, with drive #
				;..leaves room for a unit number
	jnc	DFNERR

	lxi	h,DFNAME

;Fall into MOVNAM

;===Init Subroutine======
;Move file name
;On Entry:
;  c=byte count >0
;  hl=destination address
;  name is at SCNBUF
;On Exit:
;  name is in destination
;trashes psw,c,de,hl
;========================
MOVNAM:	lxi	d,SCNBUF

;Fall into MOVBLK

;===Init Subroutine=======
;Move a block of data
;On Entry:
;  c = byte count
; de = source address
; hl = destiantino address
;trashes psw,c,de,hl
;=========================
MOVBLK:	ldax	d		;copy source
	mov	m,a		;..to destination
	inx	d
	inx	h
	dcr	c
	jnz	MOVBLK

	ret

;===Init Subroutine========================================
;Find and read the correct tape header
;On Entry:
;  TDRIVE = 80h for drive 1, 40h for drive 2
;  UTNAME = desired tape file name
;           0 means use first file found
;On Exit:
;  Carry set if UART error in header
;  TAPHDR = complete tape header from tape
;  de = tape file byte count
;  hl = DATBUF, the data destination
;  tape is running and ready to be read
;  DFTYPE (in DSKHDR) is set up for SYS's CREATE (page 7-7)
;Rude error exit if the file is too big
;==========================================================
TPINIT:	lda	TDRIVE		;tape unit (assume 1200 baud)
	out	TAPPT		;Get that tape moving

;Delay for a bit so the tape can get up to speed

	mvi	b,3		;2.3 second delay

DELAY:	lxi	d,0		;3/4 seconds

DLOP1:	dcx	d		;(5)
	mov	a,d		;(5)
	ora	e		;(4)
	jnz	DLOP1		;(10)

	dcr	b
	jnz	DELAY

	in	TDATA		;Clear tape UART flags

;Hunt for a valid tape header (where the CRC is right)
;at 1200 baud, we have about 7.5 ms (1500 8080 clock times)
;after the tape header and before the next byte is available.

PTAP1:	call	RHEAD		;Go read the header
	rc			;abort with carry if UART error
	jnz	PTAP1		;Keep looking if CRC error

;Found a valid tape header. Now compare the tape file name
;to the desired file name, and try again if it doesn't match

	lxi	b,TNLEN*256+TNLEN ;tape name max length, twice

	lxi	h,UTNAME	;user-supplied tape file name
	mov	a,m		;did the user supply a name?
	cpi	2		;0 or 1 means no
	jc	RTAP2		;n: then read the first file

	lxi	d,TFNAME	;y: is it the one we found?

DHLOP:	ldax	d
	cmp	m
	jnz	PTAP1		;no match: Look again

	inx	h		;next byte
	inx	d
	dcr	b

	jnz	DHLOP		;check all bytes

;If the user did not supply a disk file name, then
;install the tape file name as the disk file name
;in the PTDOS disk file header (DSKHDR).
;c=TNLEN here

RTAP2:	lxi	d,TFNAME	;input file name is here
	lxi	h,DFNAME	;disk file name in DSKHDR

	mov	a,m		;the 1st chr of disk file name
	ora	a		;was a disk name provided?
	cz	MOVBLK		;n: use the tape file name name

;Make sure the tape file is not too big for the buffer

	lhld	BUFMAX		;max allowed bytes in buffer
	xchg			;de = available space
	lhld	TFSIZE		;tape file byte count

	mov	a,e		;16-bit compare
	sub	l
	mov	a,d
	sbb	h
	jc	OVRERR		;file overrun

	xchg			;tape file byte count to de

;Install the tape file type as the disk file type, unless
;overridden by the user

	lxi	h,DFTYPE
	mov	a,m		;already set?
	ora	a		;clears carry
	jnz	DTSET		;y: leave it

	lda	TFTYPE		;n: use tape's file type
	ani	7Fh		;make it an image file
	mov	m,a		;..clears carry too
DTSET:

;Finish setting up the disk transfer pointers,
;and return with carry cleared

	lxi	h,DATBUF	;destination address to hl
	ret

;===Init Subroutine=====================
;Read the tape header into TAPHDR
;On Exit:
;  Header is in TAPHDR
;  Carry set if tape UART error
;  Z cleared if CRC error
;  Z set if all ok
;trashes a,bc,hl
;=======================================
RHEAD:	mvi	b,10		;Find 10 nulls in a row

RHEA1:	call	TWAIT		;wait for next byte
	in	TDATA		;get tape byte
	ora	a		;null?
	jnz	RHEAD		;n: start over
				;ignore UART errors

	dcr	b		;found a null
	jnz	RHEA1		;Loop until 10 in a row

;Wait for the sync character = at least 10 nulls
;followed by an 01

SOHL:	call	TAPIN
	rc			;Abort if UART error
	cpi	1		;Sync character?
	jc	SOHL		;Still a null. Keep waiting
	jnz	RHEAD		;Not the start chr - start over

;Get the header

	lxi	h,TAPHDR	;header buffer
	mvi	b,THLEN		;Number of header bytes

	jmp	RDBLOC		;go read the header

;===Exit=============
;File overrun Error
;====================
OVRERR:	xra	a
	out	TAPPT		;tape off

	call	ILPRNT
	db	'Insufficient buffer spac','e'+80h
	jmp	REXIT

;===Exit=============
;Generic PSCAN Error
;====================
PSERR:	call	ILPRNT
	db	'Huh';'?'+80h
	jmp	HLPEXT

;===Exit=============
;Tape unit Error
;====================
TUERR:	call	ILPRNT
	db	'Bad tape unit numbe','r'+80h
	jmp	HLPEXT

;===Exit=============
;Option Error
;====================
OPTERR:	call	ILPRNT
	db	'Bad optio','n'+80h
	jmp	HLPEXT

;===Exit==============
;Disk block size Error
;=====================
DBSERR:	call	ILPRNT
	db	'Bad disk block siz','e'+80h
	jmp	HLPEXT

;===Exit=============
;Disk File Name Error
;====================
DFNERR:	call ILPRNT
	db	'Bad disk file nam','e'+80h
	jmp	HLPEXT

;===Exit=============
;Tape File Name Error
;====================
TFNERR:	call	ILPRNT
	db	'Bad tape file nam','e'+80h

;Fall into HLPEXT

;===Exit=============
;Print help screen
;====================
HLPEXT:	call	CILPRT
;    1234567890123456789012345678901234567890123456789012345678901234
 db '          GETTAPE Version 1.02 by M. Eberhard'
 db CR,LF
 db 'Creates a PTDOS Image file from a Solos/Cuter tape file.'
 db CR,LF,LF
 db 'GETTAPE [tfile][/tunit][,dfile][,!bsize][,E={N/hhhh}][,T=ftype]'
 db CR,LF,LF
 db 'tfile is the tape file (default: the first file found). tunit'
 db CR,LF
 db 'is the tape unit (1 or 2, default: 1). dfile is the disk file'
 db CR,LF
 db '(default: the tape file name). bsize is the disk block size'
 db CR,LF
 db '(default: 4C0h). E=N makes a non-executable image file. E=hhhh'
 db CR,LF
 db 'makes an Image file that executes at the given hex address.'
 db CR,LF
 db 'If no E= option, then the Image file is executable only if the'
 db CR,LF
 db 'tape file is, with the tape file',QUOTE
 db 's execution address. ftype'
 db CR,LF
 db 'sets the Image file type (default: the tape file',QUOTE
 db 's type)','.'+80h

	jmp	REXIT

;=====================
;Junk buffer for PSCAN
;=====================
SCNBUF:	equ	$

	END


