;PROGRAM:  DUTIL
;AUTHOR:  RICHARD CONN
;DERIVATION:  DUTIL is derived from DU Version 7.5
;VERSION:  1.1
;DATE:  21 NOV 81
;PREVIOUS VERSIONS:  1.0 (1 JAN 81)

VERS	EQU	11	; DUTIL Version Number

;
;  DUTIL is derived from --
;	  DU.ASM  V7.5	Revised 1/23/81
;	DISK UTILITY - By Ward Christensen
;
;  Principal Authors of DU V7.5 are --
;	WLC   KBP   RGF   BRR
;
;  Key comments from DU V7.5 and DUTIL follow --
;
;This version of DU is compatible with CP/M 1.4 and 2.x
;and does not require alteration for various hardware
;configurations.  It adjusts itself automatically to
;the correct number of sectors, tracks, directory size,
;etc.  It has been tested on 5-1/4" and 8" floppy, and
;10 megabyte hard disk systems.
;
;Because of the automatic adaption feature, no conditional
;assembly options are included.  The only alteration that
;needs to be done is to use DDT to set the byte at 103h
;to zero for systems using a 2 mHz clock or non-zero for
;4 mHz clock.  This only affects the time delay used in
;the 'sleep' command.
;
;For DUTIL, the additional value of PAGSIZ at 104h should
;be set for the size of the display (in lines) on the user's
;CON: device.  Under DUTIL, all output is paged, and this
;determines the page limit.
;
;*************************************************
;* 						 *
;*   This program has been heavily modified	 *
;* to allow it to work without modification	 *
;* on most versions of CP/M 1.4 and, hopefully,	 *
;* all versions of CP/M 2.x.			 *
;*   If you have difficulty getting this program *
;* to run, AND if you are using CP/M 2.x, AND	 *
;* if you know your BIOS to be bug-free, leave	 *
;* a message on Technical CBBS of Dearborn,	 *
;* Michigan (313)-846-6127 with a description	 *
;* of the problem and a summary of your hard-	 *
;* ware configuration.				 *
;*   One known possible problem involves the	 *
;* system tracks on some systems, and results	 *
;* from the system sectors being skewed. There	 *
;* is NO way for a program executing under CP/M	 *
;* to know about this.  This program assumes the *
;* standard convention of no skew being used on	 *
;: the system tracks. This usually isn't a prob- *
;* lem because the SYSGEN program can be used to *
;* get the system from the disk so that	it can	 *
;* be modified.					 *
;*   This program should work under standard	 *
;* versions of CP/M 1.4.  The only requirement	 *
;* is that the BIOS "SETSEC" routine not modify	 *
;* the sector number passed to it in the B 	 *
;* register.  Again, system tracks with skewed	 *
;* sectors will be a problem.			 *
;*   If you add any features or make any useful	 *
;* changes to this program, please modem a copy	 *
;* to the above CBBS, so the currency of the	 *
;* program can be maintained.			 *
;* 						 *
;* 		Ron Fowler			 *
;* 						 *
;*************************************************
;
;  The last few revision notes for note are --
;
;01/23/81 Changed SETSEC to ignore high-order result of
;	  SECTRN if SPT<256.  This fixes some translation
;	  problems where the BIOS leaves garbage in H. (BRR)
;
;01/15/81 Changed labels to be no more than 6 characters
;	  long.  Moved stack.  Cleaned up file.  (KBP)
;
;01/13/81 Updated help messages for '#' and 'N' commands.
;	  Modified sign-on message.  (RGF)
;
;01/12/81 Fixed problem with sector translation under
;	  CP/M 1.4.  (RGF)
;

;
;System equates
;
BASE	EQU	0	;SET TO 4200H FOR HEATH OR TRS-80 ALTCPM
;
;CP/M Key Areas
;
FCB	EQU	BASE+5CH	;CP/M FCB
BDOS	EQU	BASE+5		;CP/M BDOS ENTRY POINT
TBUFF	EQU	BASE+80H	;CP/M TEMPORARY DISK I/O BUFFER
TPA	EQU	BASE+100H	;CP/M TRANSCIENT PROGRAM AREA
;
;CP/M BDOS Function Codes
;
PRINT	EQU	9
GVERS	EQU	12
RESETDK EQU	13
SELDK	EQU	14
SRCHF	EQU	17	;SEARCH FIRST
SUSER	EQU	32
GETDSK	EQU	25
GETDPB	EQU	31
;
;CP/M 1.4 Offsets and Some Key Values
;
TRNOFF	EQU	15	;CP/M 1.4 OFFSET FROM BASE
			;OF BDOS TO SECTRAN ROUTINE
SKWOFF	EQU	1AH	;CP/M 1.4 OFFSET TO SKEW TABLE
S2OFF	EQU	14	;OFFSET INTO FCB FOR S2 BYTE
DPBOFF	EQU	3AH	;CP/M 1.4 OFFSET TO DPB WITHIN BDOS
S2MASK	EQU	0FH	;MASK FOR EXTENDED RC BITS OF S2
DPBLEN	EQU	15	;SIZE OF CP/M 2.x DISK PARM BLOCK
;
;Define ASCII characters
;
CR	EQU	0DH	;CARRIAGE RETURN
LF	EQU	0AH	;LINE FEED
TAB	EQU	09H	;TAB
BS	EQU	08H	;BACKSPACE



;
;Beginning of Program
;
	ORG	TPA
	JMP	START	;JUMP OVER CLOCK BYTE AND I.D.
;
CLOCK:
	DB	2	;<---Put Processor Speed Here (1=1MHZ, 2=2MHZ, etc)
PAGSIZ:
	DB	24	;<---Put CRT Screen Size Here (24 Lines default)
	DB	'DUTIL.COM from DU.COM ver 7.5 1/23/81 by RLC'
;
START:
	LXI	H,0	;GET PTR TO CP/M STACK
	DAD	SP	;HL=SP
	SHLD	DUTSTK	;SAVE IT
;
	LXI	SP,DUTSTK	; SET STACK
;
	MVI	C,GVERS	;GET CP/M VERSION NR
	CALL	BDOS
	MOV	A,H	;COMBINE THE TWO BYTE...
	ORA	L	;...VERSION NR FOR A FLAG
	STA	VER2FL	;SAVE IT
;
;Set up local jumps to BIOS
;
	LHLD	BASE+1	;WARM BOOT POINTER
	LXI	D,3	;READY FOR ADD
	DAD	D	
	SHLD	VCONST+1	;CON: Status
	DAD	D
	SHLD	VCONIN+1	;CON: Input
	DAD	D
	SHLD	VCONOT+1	;CON: Output
	DAD	D
	SHLD	VLIST+1		;LST: Output
	DAD	D	;Skip PUNCH
	DAD	D	;Skip RDR
	DAD	D
	SHLD	VHOME+1		;Home Disk
	DAD	D
	SHLD	VSELDK+1	;Select Disk
	DAD	D
	SHLD	VSETRK+1	;Set Track
	DAD	D
	SHLD	VSTSEC+1	;Set Sector
	DAD	D
	SHLD	SETDMA+1	;Set DMA Address
	DAD	D
	SHLD	VREAD+1		;Read Block From Disk
	DAD	D
	SHLD	VWRITE+1	;Write Block To Disk
	LDA	VER2FL
	ORA	A
	JZ	DOCPM1
	DAD	D	;Skip LISTST
	DAD	D
	SHLD	VSCTRN+1	;CP/M 2.x Sector Translation Table
	JMP	HELLO
;
;Set up CP/M 1.4 Parameters
;
DOCPM1:
	LHLD	BDOS+1
	MVI	L,0	 ;BDOS ON PAGE BOUNDARY
	PUSH	H
	LXI	D,TRNOFF	;CP/M 1.4 SECTRAN ROUTINE OFFSET
	DAD	D
	SHLD	VSCTRN+1	;CP/M 1.4 Sector Translation Table
	POP	H
	LXI	D,SKWOFF ;CP/M 1.4 SKEW TABLE OFFSET
	DAD	D
	SHLD	SECTBL	 ;SET UP SKEW TABLE POINTER
;
;Initialization Complete -- Print Signon Message and Begin Command Processing
;
HELLO:
	CALL	GETSTP	;SET UP CP/M PARAMETERS
	CALL	INITP	;INITIALIZE BUFFER PARAMETERS
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	CR,LF,'DUTIL - Disk Utility, Version '
	DB	VERS/10+'0','.',(VERS MOD 10)+'0'
	DB	CR,LF,'	Derived From DISK UTILITY ver 7.5, '
	DB	'Universal Version',CR,LF
	DB	CR,LF
	DB	'Type ? for Help'
	DB	CR,LF,0
	LXI	H,TBUFF	;TO INPUT BUFF
	MOV	A,M
	ORA	A
	JZ	PRMPTR	;NO INITIAL COMMAND FROM COMMAND LINE
;
;Got initial command, set it up
;
	MOV	B,A	;SAVE LENGTH
	DCR	B	;JUST A SPACE?
	JZ	PRMPTR	;GOTO COMMAND PROCESSOR IF SO
	LXI	D,INBUF	;PT TO INLINE BUFFER
	INX	H	;SKIP LEN
	INX	H	;SKIP ' '
	MOV	A,M	;GET FIRST CHAR
	CPI	'/'	;IF SLASH, PRINT INITIAL HELP (TOOLSET CONVENTION)
	JZ	IHELP	;PRINT INITIAL HELP INFO
	CALL	MOVE	;COPY INPUT LINE INTO INLINE BUFFER
	MVI	A,CR	;STORE ENDING <CR>
	STAX	D
	LXI	H,INBUF	;PT TO FIRST BYTE OF INLINE BUFFER
	JMP	PRMPTI	;PROCESS AS THOUGH IT WAS TYPED
;
;Input Command Line From User at Console
;
PRMPTR:
	XRA	A	;A=0
	STA	QFLAG	;Set Not Quiet
	CALL	SINBUF	;Save old INBUF into PINBUF
	CALL	RDBUF	;Read Input Line
	CALL	EXMAC	;Expand Macros
;
;Begin Processing Command Line in INBUF
;  At this point, HL points to next character to process
;
PRMPTI:
	MVI	A,0FFH	;SET INFINITE LOOP COUNT
	STA	TOGO	;LOOP COUNT FOR "/"
	STA	TOGO+1
;
;Minor Command Loop; This is the entry point for each individual command in
;  a Command Line; Commands may be separated by semicolons in this manner
;
PROMPT	EQU	$
SETSTK:
	LXI	SP,DUTSTK	;RESET STACK
	XRA	A	;ZERO 2-UP PRINT FOR DUAL-COLUMN PRINT
	STA	TWOUP	;..SWITCH
	MVI	A,1
	STA	FTSW	;TELL SEARCH NOT TO INCR
	PUSH	H
	LXI	H,TBUFF	;SET NO-READ INPUT BUFFER ADDRESS
	SHLD	BUFAD	;FOR RDBYTE
	POP	H
	CALL	CTLCS	;ABORT?
	JZ	PRMPTR	;..YES, READ BUFFER
;
;Do we have to position in directory after find?
;
	LDA	FINDFL
	ORA	A
	JNZ	POSDIR	;POSITION IN DIRECTORY
;
;Begin Command Evaluation -- Check for EOL and Capitalize
;
	MOV	A,M	;GET NEXT CHAR IN COMMAND LINE
	INX	H	;POINT TO FOLLOWING CHAR
	CPI	CR	;END OF LINE PHYSICALLY?
	JZ	PRMPTR	;INPUT NEW COMMAND LINE IF SO
	CPI	';'	;END OF LINE LOGICALLY?
	JZ	PROMPT	;PROCESS NEXT ELEMENT IF SO
	CALL	UPCASE	;CAPITALIZE COMMAND
	STA	DUMTYP	;TYPE OF DUMP (A,D,H)
;
;Command dispatcher
;  If command not found, abort with error message
;  If command file, process command with HL pting to next command char and
;    A containing command letter
;
	PUSH	H	;SAVE HL
	MOV	B,A	;COMMAND IN B
	LXI	H,CMDTBL	;SCAN COMMAND TABLE FOR USER COMMAND
CMDLP:
	MOV	A,M	;GET COMMAND
	ORA	A	;0=END OF TABLE
	JZ	WHAT
	CMP	B	;COMPARE COMMAND
	JZ	CMDGO
	INX	H	;PT TO ADR
	INX	H
	INX	H	;PT TO NEXT CMND
	JMP	CMDLP
CMDGO:
	INX	H	;PT TO ADDRESS LOW
	MOV	E,M
	INX	H	;PT TO ADDRESS HIGH
	MOV	D,M
	MOV	A,B	;COMMAND BACK INTO A
	POP	H	;RESTORE HL
	PUSH	D	;PLACE ADDRESS ON STACK
	RET		;"RUN COMMAND"
;
;Macro Expansion Routine -- Expand Macros
;
EXMAC:
	LXI	H,INBUF	;PT TO INPUT LINE
	LXI	D,CTEMP	;BUILD INTO TEMPORARY BUFFER
EXMAC1:
	MOV	A,M	;GET CHAR
	CPI	'0'	;SKIP IF LESS THAN '0'
	JC	EXMAC2
	CPI	'9'+1	;CHECK FOR RANGE
	JNC	EXMAC2
	INX	H	;PT TO NEXT CHAR
	PUSH	H	;SAVE PTR TO NEXT CHAR IN LINE
	SUI	'0'	;CONVERT TO BINARY (0-9)
	MOV	B,A	;RESULT IN B
	MVI	C,0
	LXI	H,MTABL	;PT TO BASE OF MACROS
	DAD	B	;PT TO MACRO
	CALL	COPYM	;COPY MACRO INTO LINE
	DCX	D	;BACK UP OVER <CR>
	POP	H	;GET PTR TO NEXT CHAR IN COMMAND LINE
EXMAC2:
	MOV	A,M	;GET CHAR
	STAX	D	;PUT CHAR
	INX	H	;PT TO NEXT
	INX	D
	CALL	MTEST	;TEST FOR END OF BUFFER
	CPI	CR	;DONE?
	JZ	EXMAC3
	CPI	';'	;LOGICAL EOL?
	JNZ	EXMAC2
	JMP	EXMAC1	;PROCESS NEXT COMMAND
EXMAC3:
	LXI	H,CTEMP	;COPY COMMAND LINE BACK
	LXI	D,INBUF	;INTO INBUF
	CALL	COPYCR	;COPY TO <CR>
	LXI	H,INBUF	;PT TO INBUF
	RET		;EXPANSION COMPLETE
;
;Copy Macro Into Command Line Buffer
;
COPYM:
	MOV	A,M	;GET CHAR
	STAX	D	;PUT CHAR
	INX	H	;PT TO NEXT
	INX	D
	CALL	MTEST	;CHECK FOR LIMIT
	CPI	CR	;END OF MACRO?
	JNZ	COPYM
	RET
;
;Test for Buffer Full
;
MTEST:
	PUSH	H	;SAVE HL
	PUSH	PSW	;SAVE A
	LXI	H,CTEMPX	;CHECK FOR END OF BUFFER
	MOV	A,H	;GET PAGE
	CMP	D	;CHECK PAGE
	JZ	MACERR
	POP	PSW	;GET A
	POP	H	;GET HL
	RET
;
;Macro Command Expansion Error
;
MACERR:
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	CR,LF,'Error -- Macro Expanded Command Line too Long',0
	JMP	PRMPTR	;NEW COMMAND
;
;Save INBUF into PINBUF for later processing by '@' command
;
SINBUF:
	LXI	H,INBUF		;PT TO INBUF
	LXI	D,PINBUF	;PT TO PINBUF (PREVIOUS INBUF)
;
;Copy (HL) to (DE) until <CR> Encountered
;
COPYCR:
	MOV	A,M	;GET CHAR
	STAX	D	;PUT CHAR
	INX	H	;PT TO NEXT
	INX	D
	CPI	CR	;DONE?
	JNZ	COPYCR
	RET

;
;Command Not Found Error
;
WHAT:
	XRA	A	;TURN OFF QUIET FLAG
	STA	QFLAG
	POP	H	; RESTORE HL
	CALL	ILPRT
	DB	'Invalid Command -- ',0
	MOV	A,B	;GET COMMAND LETTER
	CALL	TYPE	;PRINT IT
	JMP	PRMPTR
;
;Memory full error
;
MEMFUL:
	XRA	A	;TURN OFF QUIET FLAG
	STA	QFLAG
	CALL	ILPRT
	DB	'+++ Out of memory +++'
	DB	CR,LF,0
	JMP	PRMPTR
;
;COMMAND:  @
;Repeat Previous Command Line
;
PCMD:
	MOV	A,M	;GET NEXT CHAR
	CPI	CR	;SHOULD BE <CR>
	JZ	PCMD1
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	CR,LF,'Warning:  Remainder of Command Line after "@" Deleted',0
PCMD1:
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	CR,LF,'Command --',CR,LF,0
	LXI	H,PINBUF	;GET PREVIOUS COMMAND
	LXI	D,INBUF		;COPY INTO INBUF
PCMD2:
	MOV	A,M	;GET CHAR
	STAX	D	;PUT CHAR
	INX	H	;PT TO NEXT
	INX	D
	CPI	CR	;END OF LINE?
	PUSH	PSW	;SAVE FLAG
	CALL	TYPE	;PRINT CHAR
	POP	PSW	;GET FLAG
	JNZ	PCMD2
	MVI	A,LF	;<LF>
	CALL	TYPE
	LXI	H,INBUF	;RESTART COMMAND PROCESSING
	JMP	PRMPTI	;INCLUDE LOOP CAPABILITY
;
;COMMAND:  :
;Define or Print Macro
;:n<text> Defines Macro n, 0<=n<=9; ::n Prints Macro n, 0<=n<=9
;
MAC:
	MOV	A,M	;GET NEXT CHAR
	CALL	UPCASE	;CAPITALIZE
	CPI	'P'	;PRINT MACRO?
	JNZ	MACROD	;IF NOT, DEFINE MACRO
	INX	H	;PT TO MACRO NUMBER
	MOV	A,M	;GET IT
	CALL	UPCASE	;CAPITALIZE
	CPI	'@'	;PRINT PREVIOUS COMMAND?
	JZ	PCPR
	PUSH	PSW	;SAVE A
	XRA	A	;A=0
	STA	QFLAG	;NOT QUIET
	CALL	ILPRT
	DB	'Macro Definitions --',0
	POP	PSW	;GET A
	CPI	'A'	;PRINT ALL MACROS?
	JZ	AMACPR
	CALL	MNUM	;CHECK FOR VALID NUMBER AND RETURN # IN D
	INX	H	;PT TO CHAR AFTER MACRO NUMBER
	CALL	MACPR	;PRINT MACRO WHOSE NUMBER IS IN D
	JMP	PROMPT
;
;Print Previous Command
;
PCPR:
	INX	H	;PT TO CHAR AFTER '@'
	LXI	D,PROMPT	;SET UP RET ADR
	PUSH	D	;RETURN ADR ON STACK
	PUSH	H	;SAVE PTR
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	'Previous Command Line Definition --'
	DB	CR,LF,'@: ',0
	LXI	H,PINBUF	;PT TO PREVIOUS COMMAND
	JMP	MPRINT		;USE MACRO PRINT FACILITY
;
;Print All Macros
;
AMACPR:
	INX	H	;PT TO CHAR AFTER 'A'
	MVI	D,0	;SET FOR FIRST MACRO
AMPRL:
	CALL	MACPR	;PRINT MACRO WHOSE NUMBER IS IN D
	INR	D	;INCREMENT MACRO NUMBER
	MOV	A,D	;GET VALUE
	CPI	10	;DONE?
	JNZ	AMPRL
	JMP	PROMPT	;CONTINUE PROCESSING
;
;Print Macro Whose Number (0-9) is in D
;
MACPR:
	PUSH	H	;SAVE PTR
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT	;PRINT HEADER
	DB	CR,LF,0
	MOV	A,D	;GET NUMBER
	ADI	'0'	;CONVERT TO ASCII
	CALL	TYPE	;PRINT
	CALL	ILPRT
	DB	': ',0
	LXI	H,MTABL	;PT TO TABLE OF MACROS
	MVI	E,0	;PAGE OFFSET OF ZERO; MACRO NUMBER ALREADY IN D
	DAD	D	;PT TO MACRO
MPRINT:
	MOV	A,M	;GET CHAR
	INX	H	;PT TO NEXT
	CPI	CR	;END OF MACRO?
	PUSH	PSW	;SAVE FLAG
	CALL	TYPE	;PRINT CHAR
	POP	PSW	;GET FLAG
	JNZ	MPRINT
	MVI	A,LF	;<LF>
	CALL	TYPE
	POP	H	;GET PTR TO NEXT CHAR
	RET
;
;Check char in A for valid Macro Number (0-9), print error message if
; not, return number in D if so
;
MNUM:
	SUI	'0'	;CONVERT TO 0-9
	JC	MNERR	;ERROR IF LESS
	CPI	10	;RANGE?
	JNC	MNERR
	MOV	D,A	;RESULT IN D
	RET
MNERR:
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	CR,LF,'Invalid Macro Number Specified in Command',0
	JMP	PRMPTR	;NEW COMMAND
;
;Define Macro
;
MACROD:
	CALL	MNUM	;CHECK NUMBER AND RETURN IN D
	INX	H	;PT TO CHAR AFTER MACRO NUMBER
	PUSH	H	;SAVE PTR
	LXI	H,MTABL	;PT TO MACRO TABLE
	MVI	E,0	;SET EVEN PAGE
	DAD	D	;PT TO MACRO ENTRY IN HL
	XCHG		;... IN DE
	POP	H	;PT TO MACRO TEXT
	CALL	COPYCR	;COPY TO <CR>
	JMP	PRMPTR	;NEW COMMAND
;
;COMMAND:  !
;Delay for user input
;
UWAIT:
	CALL	WAIT	; USE WAIT ROUTINE
	JMP	PROMPT
;
;COMMAND:  #
;Print disk statistics
;
STATS:
	PUSH	H	;SAVE POINTER TO NEXT COMMAND
	CALL	ILPRT
	DB	'===============================',CR,LF
	DB	'     -- Disk Information --	',CR,LF
	DB	'-------------------------------',CR,LF
	DB	CR,LF,'Disk Drive:		',0
	LDA	DRIVE
	ADI	'A'	;CONVERT TO ASCII
	CALL	TYPE	;PRINT DRIVE LETTER
	CALL	ILPRT
	DB	CR,LF,'Tracks:			',0
	LHLD	MAXTRK	;PRINT NUMBER OF TRACKS
	INX	H
	CALL	DEC
	CALL	ILPRT
	DB	CR,LF,'Sectors/Track:		',0
	LHLD	SPT	;PRINT NUMBER OF SECTORS/TRACK
	CALL	DEC
	CALL	ILPRT
	DB	CR,LF,'Group Size:		',0
	LDA	BLM	;PRINT SIZE OF A GROUP
	INR	A
	MOV	L,A
	MVI	H,0
	CALL	DEC
	CALL	ILPRT
	DB	' Blocks/Group'
	DB	CR,LF,'Total Groups:		',0
	LHLD	DSM	;PRINT TOTAL NUMBER OF GROUPS ON A DISK
	CALL	DEC
	CALL	ILPRT
	DB	CR,LF,'Directory Entries:	',0
	LHLD	DRM	;PRINT NUMBER OF DIRECTORY ENTRIES
	INX	H
	CALL	DEC
	CALL	ILPRT
	DB	CR,LF,'System Tracks:		',0
	LHLD	SYSTRK	;PRINT NUMBER OF SYSTEM TRACKS
	CALL	DEC
	CALL	ILPRT
	DB	CR,LF
	DB	'===============================',CR,LF,0
	POP	H	;RESTORE POINTER TO NEXT COMMAND
	JMP	PROMPT
;
;COMMAND:  N
;The following command resets the disk
;system thru CP/M, and may be usable for
;changing the disk density or format.
;This can only be done if your BIOS resets
;the auto-density select parameters at
;every track-zero access.
;
NEWDSK:
	PUSH	H	;SAVE POINTER TO NEXT LETTER
	MVI	C,RESETDK	;BDOS RESET DISK FUNCTION
	CALL	BDOS
	LDA	DRIVE	;RESELECT CURRENT DRIVE
	MOV	C,A
	POP	H
	CALL	SELECT
	JMP	PROMPT
;
;COMMAND:  Q
;Quite mode
;
QUIET:
	STA	QFLAG	;NOW QUIET (FLAG IS NON-ZERO)
	JMP	PROMPT
;
;COMMAND:  /
;Repeat buffer contents
;
REPEAT:
	CALL	DECIN	;NN SPECIFIED?
	MOV	A,D
	ORA	E
	JZ	NNN	;NO -- SET FOR INFINITE LOOP OR SIMPLE REPEAT
	LHLD	TOGO	;LOAD LOOP FLAG
	INX	H	;TEST FOR FIRST TIME
	MOV	A,H
	ORA	L	;WAS IT 0FFFFH?; IF SO, WE HAVE NEW VALUE
	JNZ	NNN	;NO: COUNTING
	XCHG		;GET COUNT
	SHLD	TOGO	;SET COUNT
;
NNN:
	LHLD	TOGO	;GET CURRENT COUNT
	XCHG		;DE=CURRENT COUNT, HL=COUNT LIMIT
	LXI	H,INBUF	;PT TO FIRST CHAR FOR REPEAT
	INX	D	;TEST FOR 0FFFFH
	MOV	A,D	;IF 0FFFFH, INX D MADE DE=0
	ORA	E
	JZ	PROMPT	;CONTINOUS LOOP IF 0FFFFH
	DCX	D	;COUNT DOWN
	DCX	D	;MAKE UP FOR PREV INX D
	XCHG
	SHLD	TOGO	;SET NEW COUNT (1 LESS THAN BEFORE)
	MOV	A,H	;ALL DONE?
	ORA	L
	XCHG		;GET BACK INBUF PTR IN HL
	JNZ	PROMPT	;KEEP GOING IF NOT YET ZERO
	JMP	PRMPTR	;ALL DONE
;
;COMMAND:  U
;Set CP/M 2.x user number
;
USER:
	LDA	VER2FL	;CP/M 2.X?
	ORA	A
	JZ	WHAT	;ERROR IF NOT
	CALL	DECIN	;GET REQUESTED USER NO.
	MOV	A,E
	CPI	32	;VALID?
	JNC	WHAT
	MOV	A,D	;HIGH-ORDER BYTE MUST BE ZERO FOR VALID NUMBER
	ORA	A
	JNZ	WHAT
	MOV	A,E	;SAVE USER NUMBER
	STA	UNUM
	MVI	C,SUSER	;SET USER NUMBER
	PUSH	H		;SAVE CHAR POINTER
	CALL	BDOS		;SET USER NO.
	POP	H
	JMP	PROMPT
;
;COMMAND:  P
;Toggle print flag
;
PRNTFF:
	LDA	PFLAG	;TOGGLE PRINT FLAG
	XRI	1
	STA	PFLAG
	JMP	PROMPT
;
;COMMAND:  Z
;Sleep routine, in seconds
;
SLEEP:
	CALL	DECIN	;GET COUNT IF ANY
	MOV	A,E	;ANY?
	ORA	A
	JNZ	SLEPLP
	MVI	E,1	; 1 SEC DEFAULT
;
SLEPLP:
	LDA	CLOCK	; GET CLOCK SPEED
	MOV	D,A
;
SLEEP1:
	LXI	B,41700	; APPROX 1 SEC @ 1MHz
;
SLEEP2:
	DCX	B	;COUNT DOWN FOR 1 MHz [5 CYCLES]
	MOV	A,B	;[5 CYCLES] <-- TOTAL TIME: 24 CYCLES
	ORA	C	;[4 CYCLES] <-- (24 MU-SECS AT 1MHz)
	JNZ	SLEEP2	;[10 CYCLES]
	PUSH	D
	CALL	CTLCS	;ABORT?
	POP	D
	JZ	PRMPTR
	DCR	D	;COUNT DOWN FOR CLOCK SPEED
	JNZ	SLEEP1
	DCR	E	;COUNT DOWN NUMBER OF REQUESTED SECONDS
	JNZ	SLEPLP
	JMP	PROMPT
;
;Check for control-C or S
;
CTLCS:
	CALL	CONST	;CHAR AVAILABLE?
	ORA	A
	JNZ	GETC
	ORI	1	;NO CHAR, RETURN NZ
	RET
;
GETC:	CALL	CONIN	;INPUT CHAR
	ANI	1FH	;ALLOW ASCII
	CPI	'S'-40H	;WAIT FOR NEXT CHAR IF ^S OR S OR s
	CZ	CONIN
	CPI	'C'-40H	;CHECK FOR ^C OR C OR c
	RET		;0 SET IF CTL-C
;
;Initialize Memory Buffers
;
INITP:
	XRA	A	;A=0
	STA	GLFLAG	;CLEAR GROUP LOADED FLAG (GROUP NOT LOADED)
	STA	HEXAD	;CLEAR ADDRESS
	STA	HEXAD+1
	STA	PFLAG	;SET NO PRINT
	STA	SAVEFL	;SET NO SAVE DONE
	STA	WRFLG	;MAY NOT WRITE
	STA	DIRPOS	;SET NO DIRECTORY POSITION
	STA	FINDFL	;SET NO POSITION
	INR	A	;A=1
	STA	FTSW	;SET SEARCH WITHOUT INCREMENT
	STA	NOTPOS	;NOT POSITIONED
	LXI	H,0	;HL=0
	SHLD	MFPTR	;SET NO MULTI FILE PTR
	SHLD	CURTRK	;SET TRACK 0
	INX	H	;HL=1
	SHLD	CURSEC	;SET LOGICAL SECTOR 1
	SHLD	PHYSEC	;SET PHYSICAL SECTOR 1
	MVI	A,CR	;CLEAR PREVIOUS COMMAND
	STA	PINBUF	;SET PREVIOUS COMMAND TO NIL
	LXI	H,MTABL	;CLEAR MACRO TABLE
	MVI	B,10	;10 ENTRIES
INITP1:
	MVI	M,CR	;STORE <CR>
	INR	H	;PT TO NEXT PAGE
	DCR	B	;COUNT DOWN
	JNZ	INITP1
	RET
;
;Set up flags, etc, at initialization
;Find our way at initialization
;
GETSTP:
	MVI	A,CR	;INITIALIZE INPUT BUFFER
	STA	INBUF	;EMPTY BUFFER
	LDA	VER2FL	;VERSION 2 OR BETTER?
	ORA	A	;0=1.X
	JZ	GSTP1
	MVI	C,SUSER	;GET USER NUMBER
	MVI	E,0FFH	;GET USER
	CALL	BDOS
	STA	UNUM	;SET USER NUMBER
GSTP1:
	MVI	C,GETDSK
	CALL	BDOS	;GET CURRENT DISK
	MOV	C,A	;WE HAVE TO SELECT
	JMP	SELECT	;TO GET THE DPH
;
;COMMAND:  L
;Log in the selected disk
;
LOGIN:
	CALL	DOLOG
	JMP	PROMPT
;
DOLOG:
	MOV	A,M	;DISK REQUESTED?
	LXI	D,0
	CPI	CR	;NO REQUEST OF PHYSICAL EOL
	JZ	LGNODK
	CPI	';'	;NO REQUEST IF LOGICAL EOL
	JZ	LGNODK
	CALL	UPCASE	;CAPITALIZE
	INX	H	;POINT TO NEXT CHAR
	SUI	'A'	;CONVERT TO 0-15
	MOV	C,A	;DISK NUMBER IN C
;
;Select Disk Whose Number is in C (A=0, B=1, etc)
;
SELECT:
	PUSH	H	;SAVE PTR TO NEXT COMMAND LETTER
	MOV	A,C
	STA	DRIVE	;REMEMBER LATER WHERE WE ARE
;
VSELDK: CALL	$-$	;ADDR FILLED IN BY 'INIT'
	LDA	VER2FL
	ORA	A	;IF NOT CP/M 2.x ...
	JZ	SELSKP	;..THEN SKIP THIS JUNK
	MOV	A,H
	ORA	L
	JZ	WHAT	;SELECT ERROR
	MOV	E,M	;GET THE SECTOR TABLE PNTR
	INX	H
	MOV	D,M
	INX	H
	XCHG
	SHLD	SECTBL	;SET THE SECTOR TABLE PTR
	LXI	H,8	;OFFSET TO DPBPTR
	DAD	D
	MOV	A,M	;PICK UP DPB POINTER
	INX	H	;  TO USE
	MOV	H,M	;  AS PARAMETER
	MOV	L,A	;  TO LOGIT
;
SELSKP:
	CALL	LOGIT
	LHLD	SYSTRK	;RESET TRACK AND SECTOR
	XCHG		;  TO DIRECTORY
	CALL	SETTRK	;  ON EVERY
	LXI	D,1	;  LOGIN
	CALL	SETSEC	;  CHANGE
	LHLD	PHYSEC	;THIS LOGIC WILL TELL
	MOV	A,H	;  IF FIRST SEC
	ORA	L	;  IS PHYSICAL 0
	STA	FIRST0
	CALL	CLCSUB	;CALCULATE WHAT GROUP/GRPDISP WE ARE IN
	POP	H	;GET PTR TO NEXT LETTER
;
LGNODK:
	CALL	NORITE	;SET NO DISK I/O DONE (NO POSITION)
	RET
;
;Read in the disk directory
;
REDDIR:
	PUSH	H	;SAVE PTR TO NEXT LETTER
	CALL	NORITE	;POSITIONING LOST
	LHLD	SYSTRK	;SAVE CURRENT TRACK
	SHLD	CURTRK
	LXI	H,1	;SET SECTOR 1
	SHLD	CURSEC
	LHLD	DRM	;GET DIR SIZE FROM DPB
	INX	H	;MAKE 1-RELATIVE
	CALL	ROTRHL
	CALL	ROTRHL	;DIVIDE BY 4 (4 NAMES/SECTOR)
	MOV	B,H	;BC=NUMBER OF BLOCKS TO READ
	MOV	C,L
	LXI	D,DIRECT	;DMA ADDR
	XRA	A	;A=0
	STA	GLFLAG	;SAY NO GROUP LOADED
;
;Read Disk Directory Loop
;
RDIRLP:
	PUSH	B	;SAVE REGS
	PUSH	D
	MOV	B,D	;BC=DMA ADDRESS
	MOV	C,E
	LDA	BDOS+2	;CHECK MEM AVAIL
	DCR	A	;ARE WE RNNING INTO BDOS?
	CMP	D
	JC	MEMFUL	;MEMORY FULL ERROR IF SO
	CALL	SETDMA	;SET DMA ADDRESS TO THAT IN BC
	LHLD	CURTRK	;SET TRACK
	XCHG
	CALL	SETTRK
	LHLD	CURSEC	;SET SECTOR
	XCHG
	CALL	SETSEC
	CALL	READ	;READ DIRECTORY BLOCK
	CALL	NXTSEC	;INCREMENT TO NEXT SECTOR
	POP	D
	POP	B
	LXI	H,80H	;ADVANCE TO NEXT DMA ADDRESS
	DAD	D
	XCHG		;DE=NEXT DMA ADDRESS
	DCX	B	;COUNT DOWN DIRECTORY BLOCKS
	MOV	A,B
	ORA	C
	JNZ	RDIRLP
	LXI	B,TBUFF	;RESET DMA ADDRESS TO TBUFF
	CALL	SETDMA
	POP	H	;GET PTR TO NEXT CHAR
	RET
;
;COMMAND:  M
;Map the directory
;
MAP:
	CALL	PAGSET	;SET PAGING COUNTER
	XRA	A
	STA	ONLY1	;SET FLAG FOR ALL GROUPS (NOT ONLY 1)
	CALL	REDDIR	;READ IN DIRECTORY
	MVI	C,0	;INIT START GRP #
	LDA	AL0	;READ DIR GRP BITS
	CALL	COLECT	;COLLECT COUNT OF DIR GRPS..
	LDA	AL1	;..IN REGISTER C
	CALL	COLECT
	MVI	B,0	;BC NOW HAS A DEFAULT START GRP #
	CALL	HEXIN	;GET SPECIFIED GROUP IF ANY
	PUSH	H	;SAVE INBUF PTR
	MOV	A,E	;GET START
	ORA	D	;NOTHING?
	JZ	MAPDF	;..YES, DFLT
	MVI	A,0FFH	;SET FLAG FOR ONLY 1 GROUP
	STA	ONLY1
	MOV	B,D	;GET VALUE IN BC
	MOV	C,E
;
MAPDF:
	CALL	HEXB	;PRINT FIRST GROUP NUMBER
	MVI	A,'-'	;PRINT SEPARATOR
	CALL	TYPE
	MVI	A,' '	;SET NO DUPLICATES
	STA	DUPFLG
	CALL	GETGRP	;GET GRP(C) TO HL
;
MAPCNT:
	INX	B	;NEXT GRP #
	PUSH	H
	LHLD	DSM	;GET HIGHEST GRP #
	INX	H	;PLUS 1 FOR COMPARISON
	MOV	A,L	;WHEN BC REACHES DSM+1..
	CMP	C	;..THEN WE HAVE EXCEEDED..
	JNZ	MAPC1	;..THE DISK CAPACITY..
	MOV	A,H
	CMP	B
;
MAPC1:
	POP	H
	JZ	MAPEND	;..AND WE ARE DONE
	PUSH	H
	CALL	GETGRP	;GET ANOTHER
	POP	D	;SEE IF SAME
	CALL	CTLCS	;ABORT?
	JZ	MAPND2
	MOV	A,D
	CMP	H
	JNZ	MAPDIF
	MOV	A,E
	CMP	L
	JZ	MAPCNT	;SAME, CONTINUE
;
;Different file encountered
;
MAPDIF:
	DCX	B
	CALL	HEXB	;PRINT ENDING GROUP NUMBER
	INX	B
	XCHG
	CALL	MAPNAM	;PRINT FILE NAME
	LDA	ONLY1	;ONLY 1 NAME TO BE PRINTED?
	ORA	A	;0=NO
	JNZ	MAPND1
	JMP	MAPDF
;
;End of map
;
MAPEND:
	DCX	B	;GET LAST
	CALL	HEXB	;PRINT LAST GROUP NUMBER
	CALL	MAPNAM	;PRINT FILE NAME
	CALL	WAIT	;DELAY FOR USER
MAPND1:
	POP	H
	CALL	CRLF	;NEW LINE
;
;End of map - reposition to previous group
;
MAPND2:
	PUSH	H
	LHLD	GROUP	;POINT TO GROUP IN DE
	XCHG
	JMP	POSGP2
;
;Print file name pointed to by HL
;
MAPNAM:
	CALL	SPACE	;LEADING SPACE
	MOV	A,H
	ORA	L	;NONE?
	JZ	NONAME
	MOV	A,M	;SEE IF ALLOC
	CPI	0E5H	;FREE?
	MVI	A,' '	;MARK ALLOCATED
	JNZ	MPNSP1
	MVI	A,'('	;MARK NOT ALLOCATED (ERASED FILE)
;
MPNSP1:
	CALL	TYPE	;PRINT ALLOCATION INDICATOR (SPACE OR '(')
	PUSH	H	;SAVE POINTER
	MOV	A,M
	CALL	HEX	;SHOW USER NUMBER
	CALL	SPACE
	INX	H	;SKIP USER BYTE
	PUSH	B
	MVI	B,8	;PRINT FILE NAME
	CALL	MAPN2
	MVI	A,'.'	;PRINT DECIMAL SEPARATOR
	CALL	TYPE
	MVI	B,3	;PRINT FILE TYPE
	CALL	MAPN2
	LDA	DUPFLG	;DUPLICATE?
	CALL	TYPE	;SPACE OR STAR
	POP	B
	MOV	A,M	;GET EXT
	CALL	HEX	;PRINT EXTENT NUMBER
	POP	H
	MOV	A,M
	CPI	0E5H	;DELETED ENTRY?
	MVI	A,' '	;PRINT ENDING SPACE
	JNZ	MPNSP2
	MVI	A,')'	;PRINT ALLOCATION FLAG
;
MPNSP2:
	CALL	TYPE	;")" IF ERASED FILE OR SPACE IF NOT
	JMP	FLIP
;
NONAME:
	CALL	ILPRT
	DB	'    ++ Free ++      ',0
;
FLIP:
	LDA	TWOUP	;FLIP FLAG FOR TWO ENTRIES PER LINE
	XRI	1
	STA	TWOUP
	JZ	PAGER	;NEW LINE WITH PAGING IF REQUIRED
;
DELIM:
	MVI	A,':'	;PRINT DELIMITER BETWEEN ADJACENT ENTRIES ON LINE
	CALL	TYPE
	JMP	SPACE
;
;Print name pted to by HL, length in B
;
MAPN2:
	MOV	A,M
	ANI	7FH	;STRIP POSSIBLE 2.x ATTRIBUTE BIT
	INX	H
	CPI	' '	;PRINTABLE?
	JC	MAPN2H	;..NO, IN HEX
	CPI	7EH	;7E IS LEADIN ON SOME CRTS
	JC	MAPN2A
;
MAPN2H:
	CALL	BHEX	;PRINT A AS HEX CHARS
	JMP	MAPN2Z
;
MAPN2A:
	CALL	TYPE	;PRINT AS CHAR
;
MAPN2Z:
	DCR	B	;COUNT DOWN
	JNZ	MAPN2
	RET
;
;Find which file group (BC) belongs to
;
GETGRP:
	LHLD	DRM	;MAX DIR ENTRY #
	INX	H	;MAKE 1-RELATIVE
	SHLD	FILECT
	LXI	H,0
	SHLD	MFPTR	;SET MULTI-FILE (MORE THAN ONE USER) PTR
	LXI	H,DIRECT	;PT TO DIRECTORY
	XRA	A	;SAY NO GROUP LOADED
	STA	GLFLAG
;
GETGLP:
	PUSH	H	;SAVE POINTER TO NAME
	MOV	A,M	;PICK UP DN BYTE
	CPI	0E5H	;ERASED?
	JZ	GETGNF
	LXI	D,14	;NOW GET RECORD COUNT
	DAD	D	;  S2 PORTION ..
	MOV	A,M	;  IS 0 IN CP/M 1.4
	ANI	0FH
	MOV	E,A
	INX	H
	MOV	A,M
	ORA	E
	JZ	GETGNF
	MVI	E,16	;FIRST SET FOR 8-BIT GRPS
	LDA	DSM+1
	ORA	A
	JZ	SMALGP
	MVI	E,8	;NOPE, BIG GROUPS
;
SMALGP:
	MOV	D,A	;SAVE GRP SIZE INDICATOR
;
GETGL2:
	INX	H	;POINTING INTO DM FIELD
	CALL	GRPCMP	;COMPARE BC GP # AGAINST 1 DM FLD
	JNZ	NOTGOT	;JUMP IF NOT FOUND
;
;Found the file
;
	PUSH	H	;SAVE GROUP PTR
	LHLD	MFPTR
	MOV	A,H	;ANY ENTRIES?
	ORA	L
	POP	H	;GET PTR
	XTHL		;SAVE ENTRY START AND SAVE PTR
	JZ	MPFRST	;IF ZERO, THEN FIRST ENTRY
	MVI	A,'*'	;SET MULTI FLAG
	STA	DUPFLG
MPFRST:
	SHLD	MFPTR	;SAVE POINTER
	XTHL		;RESTORE ENTRY START AND GET PTR
NOTGOT:
	DCR	E	;COUNT DOWN
	JNZ	GETGL2	;GO TEST SOME MORE
;
GETGNF:
	POP	H	;NOT THIS ONE
	LXI	D,32	;SO GO TO NEXT
	DAD	D
	XCHG
	LHLD	FILECT	;THERE IS LIMIT TO EVERYTHING
	DCX	H
	SHLD	FILECT
	MOV	A,H
	ORA	L
	XCHG		;RE-ALIGN
	JNZ	GETGLP
;
;Set the allocation address, if any
;
	LHLD	MFPTR	;GET ADDRESS
	RET
;
;COMMAND:  <
;Save the current sector
;
SAVE:
	LDA	WRFLG	;READ DONE?
	ORA	A
	JZ	BADW	;NONE TO SAVE
	PUSH	H
	LXI	H,TBUFF	;COPY FROM TBUFF
	LXI	D,SAVBUF	;INTO SAVBUF
	MVI	B,128	;128 BYTES
	CALL	MOVE
	MVI	A,1	;..SHOW
	STA	SAVEFL	;..SAVED EXISTS
	POP	H	;GET PTR TO NEXT CHAR
	JMP	PROMPT
;
;This routine is common to Save Group (RG) and Write Group (WG); it is used
;  to extract the group number, check it, and position DUTIL to it
;  On exit, GROUP = Group Number, GRPDIS = 0, and DUTIL is positioned
;
COMG:
	INX	H	;PT TO CHAR AFTER 'G' OF '<G' COMMAND
	MOV	A,M	;GET CHAR AFTER 'G'
	CALL	UPCASE	;CAPITALIZE
	CPI	';'	;ERROR IF LOGICAL EOL
	JZ	WHAT
	CPI	CR	;ERROR IF PHYSICAL EOL
	JZ	WHAT
	CALL	HEXIN	;GET GROUP NUMBER IN HEX
	PUSH	H	;SAVE PTR TO NEXT CHAR
	LHLD	DSM	;CHECK FOR BOUNDS ERROR
	CALL	SUBDE	;SUBTRACT GROUP NUMBER FROM DSM
	POP	H	;RESTORE PTR
	JC	OUTLIM	;LIMIT ERROR IF CARRY
	XCHG		;SAVE GROUP NUMBER
	SHLD	GROUP
	SHLD	TGRP	;TEMPORARY GROUP NUMBER
	XCHG		;GROUP NUMBER IN DE
	XRA	A	;A=0
	STA	GRPDIS	;SET GROUP DISPLACEMENT
	PUSH	H	;SAVE PTR TO NEXT CHAR
	CALL	GTKSEC	;CONVERT GROUP NUMBER TO TRACK AND SECTOR
	CALL	SETTRK	;SET TRACK
	XCHG
	CALL	SETSEC	;SET SECTOR
	POP	H	;GET PTR TO NEXT CHAR
	RET
;
;This is the Save Group Routine; it copies the indicated group into the save
;  buffer.
;
SAVEG:
	CALL	COMG	;EXTRACT COMMON GROUP INFO -- GROUP NUMBER AND POS
	PUSH	H
	CALL	ILPRT
	DB	'Reading from Group ',0
	LHLD	GROUP	;GET CURRENT GROUP
	MOV	B,H	;VALUE IN BC
	MOV	C,L
	CALL	HEXB	;PRINT AS HEX
	CALL	ILPRT
	DB	CR,LF,0
	POP	H
	MVI	A,0FFH	;SET GROUP LOADED FLAG
	STA	GLFLAG	;GROUP HAS BEEN LOADED
	MVI	A,0	;SET COPY FUNCTION TO SAVE
	STA	CPYFCT	;0=READ, 0FFH=WRITE
;
;Group Copy Routine -- if CPYFCT = 0, Read Group; if CPYFCT = 0FFH, Write Group
;
COPYG:
	PUSH	H	;SAVE PTR TO NEXT CHAR IN COMMAND LINE
	CALL	NORITE	;POSITIONING LOST
	LXI	D,GBUFF	;PT TO BUFFER
	LDA	BLM	;GET NUMBER OF BLOCKS/GROUP
	INR	A	; ADD 1 TO BLM FOR CORRECT COUNT
	MOV	B,A	;COUNT IN B
;
COPYGL:
	PUSH	B	;SAVE COUNT
	PUSH	D	;SAVE PTR TO NEXT BLOCK TO LOAD
	LDA	BDOS+2	;CHECK THAT MEMORY ISN'T FULL
	CMP	D	;COMPARE TO LOAD ADDRESS
	JC	MEMFUL
	MOV	B,D	;SET BC=DE FOR SET DMA
	MOV	C,E
	CALL	SETDMA	;SET ADDRESS TO LOAD
	LDA	CPYFCT	;READ OR WRITE?
	ORA	A	;0=READ
	PUSH	PSW	;SAVE FLAGS
	CZ	READ	;READ BLOCK
	POP	PSW	;GET FLAGS
	CNZ	PWRITE	;WRITE BLOCK (NO CHECK)
	CALL	NXTSEC	;COMPUTE NEXT SECTOR ADDRESS
	LHLD	CURTRK	;GET NEXT TRACK ADDRESS
	XCHG		;... IN DE
	CALL	SETTRK	;SET IT
	LHLD	CURSEC	;GET NEXT SECTOR ADDRESS
	XCHG		;... IN DE
	CALL	SETSEC	;SET IT
	POP	D	;GET PTR TO NEXT BLOCK
	POP	B	;GET COUNTER
	LXI	H,80H	;OFFSET TO NEXT BLOCK
	DAD	D
	XCHG		;DE PTS TO NEXT BLOCK
	DCR	B	;COUNT DOWN
	JNZ	COPYGL	;LOOP UNTIL FINISHED
	LXI	B,TBUFF	;RESET DMA ADDRESS
	CALL	SETDMA
	XRA	A	;A=0
	STA	WRFLG	;SET NO READ DONE
	LHLD	TGRP	;GET GROUP NUMBER
	XCHG		;... IN DE
	POP	H	;GET PTR TO NEXT CHAR
	JMP	POSGRP	;POSITION TO GROUP IN DE AND CONTINUE PROCESSING
;
;COMMAND:  >
;Restore the current sector
;
RESTOR:
	LDA	SAVEFL	;SAVE DONE PREVIOUSLY?
	ORA	A
	JZ	NOSAVE	;NONE TO SAVE
	PUSH	H
	LXI	H,SAVBUF	;COPY FROM SAVBUF
	LXI	D,TBUFF	;INTO TBUFF
	MVI	B,128	;128 BYTES
	CALL	MOVE
	POP	H	;GET PTR TO NEXT CHAR
	JMP	PROMPT
;
;Write Group Loaded in GBUFF to Disk
;
RESTRG:
	LDA	GLFLAG	;GROUP ALREADY LOADED?
	ORA	A	;0=NO
	JZ	RGERR
	CALL	COMG	;GET GROUP NUMBER FROM COMMAND LINE AND POS
	PUSH	H
	CALL	ILPRT
	DB	'Writing to Group ',0
	LHLD	GROUP	;GET GROUP NUMBER
	MOV	B,H	;VALUE IN BC
	MOV	C,L
	CALL	HEXB	;PRINT IN HEX
	CALL	ILPRT
	DB	CR,LF,0
	POP	H
	MVI	A,0FFH	;WRITE FUNCTION
	STA	CPYFCT	;COPY FUNCTION FOR GROUP COPY ROUTINE
	JMP	COPYG	;GROUP COPY ROUTINE
;
RGERR:
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	'++ No "RG" Read Group Command Issued or Loaded Group'
	DB	' Trashed ++'
	DB	CR,LF,0
	JMP	PRMPTR
;
NOSAVE:
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	'++ No "<" Save Command Issued ++'
	DB	CR,LF,0
	JMP	PRMPTR
;
;Move (HL) to (DE) length in B
;
MOVE:
	MOV	A,M
	STAX	D
	INX	H
	INX	D
	DCR	B
	JNZ	MOVE
	RET
;
NORITE:
	XRA	A	;GET 0
	STA	WRFLG	;CAN'T WRITE NOW
	RET
;
;No match in search, try next char
;
SRNOMT:
	POP	H
	CALL	CTLCS	;ABORT?
	JNZ	SEARCH	;..YES
	LXI	H,INBUF
	MVI	M,CR
	JMP	CLCGRP	;SHOW WHERE STOPPED
;
;COMMAND:  =
;Search for character string
;
SEARCH:
 PUSH	H	;SAVE STRING POINTER
;
SRCHL:
	CALL	RDBYTE	;GET A BYTE
	MOV	B,A	;SAVE IT
	MOV	A,M	;CHECK NEXT MATCH CHAR.
	CPI	'<'	;WILL IT BE HEX?
	MOV	A,B	;RESTORE DISK CHAR
	JZ	SRCHL1
	ANI	7FH	;NEXT CHAR IS ASCII...STRIP BIT 7
;
SRCHL1:
	PUSH	PSW
	CALL	GETVAL	;GET SEARCH VALUE
	MOV	B,A
	POP	PSW
	CMP	B	;MATCH?
	JNZ	SRNOMT	;NO MATCH
	INX	H
	MOV	A,M	;DONE?
	CPI	CR	;END OF LINE?
	JZ	SREQU
	CPI	';'	;LOGICAL EOL?
	JNZ	SRCHL
;
;Got match
;
SREQU:
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	'= at ',0
	LDA	BUFAD
	ANI	7FH
	CALL	HEX
	CALL	CRLF
	JMP	CLCGRP
;
;Get value from input buffer
;
GETVAL:
	MOV	A,M	;GET NEXT CHAR
	CPI	'<'	;HEX ESCAPE?
	RNZ		;NO, RETURN
;"<<" means one "<"
	INX	H
	MOV	A,M
	CPI	'<'
	RZ
;Got hex
	PUSH	D
	CALL	HEXIN	;GET VALUE
	CPI	'>'	;PROPER DELIM?
	MOV	A,E	;GET VALUE
	POP	D
	JNZ	WHAT	;ERROR
	RET
;
;Read a byte at a time from disk
;
RDBYTE:
	PUSH	H
	LDA	FTSW	;FIRST READ?
	ORA	A
	JNZ	READ1
	LHLD	BUFAD
	MOV	A,L
	ORA	A	;IN BUFFER?
	JM	NORD	;YES, SKIP READ
;
;Have to read
;
	CALL	NXTSEC	;ADVANCE TO NEXT BLOCK
;
READ1:
	XRA	A
	STA	FTSW	;NOT FIRST READ
	LHLD	CURSEC
	XCHG
	CALL	SETSEC
	LHLD	CURTRK
	XCHG
	CALL	SETTRK
	CALL	READ
	CALL	CLCSUB
	LXI	H,TBUFF
;
NORD:
	MOV	A,M
	INX	H
	SHLD	BUFAD
	POP	H
	RET
;
;COMMAND:  V
;View the file in ASCII starting at
;current sector, stepping thru the disk
;
VIEW:
	LDA	WRFLG
	ORA	A
	JZ	BADDMP
	CALL	DECIN	;GET DISPL IF ANY
	PUSH	H
	MOV	A,E
	ORA	A
	JNZ	VIEWLP
	INR	E	;DFLT=1
;
VIEWLP:
	LXI	H,TBUFF	;TO DATA
;
VEWCHR:
	CALL	CTLCS	;ABORT?
	JZ	VEWEND
	MOV	A,M	;GET NEXT CHAR
	CPI	1AH	;EOF?
	JZ	VEWEOF
	ANI	7FH	;MASK
	CPI	7EH	;ESC CHAR FOR H1500
	JNC	VIEWHX	;SHOW RUBOUT AND TILDE AS HEX
	CPI	' '
	JNC	VIEWPR
	CPI	CR	;CR PASS
	JZ	VIEWPR
	CPI	LF	;LF PASS
	JZ	VIEWPR
	CPI	TAB	;TAB PASS
	JZ	VIEWPR
;
VIEWHX:
	MOV	A,M	;NOT ASCII...PRINT AS <NN>
	CALL	BHEX
	JMP	VIEWNP
;
VIEWPR:
	CALL	TYPE
;
VIEWNP:
	INR	L
	JNZ	VEWCHR
	DCR	E
	JZ	VEWEND
	PUSH	D	;SAVE COUNT
	CALL	NXTSEC
	LHLD	CURSEC
	XCHG
	CALL	SETSEC	
	LHLD	CURTRK
	XCHG
	CALL	SETTRK
	CALL	READ
	POP	D	;RESTORE COUNT
	JMP	VIEWLP
;
VEWEOF:
	CALL	ILPRT
	DB	CR,LF,'	++ EOF ++',CR,LF,0
;
VEWEND:
	POP	H
	CALL	CRLF
	JMP	CLCGRP
;
;COMMAND:  A or D
;Dump in hex or ASCII
;
DUMP:
	LDA	WRFLG
	ORA	A
	JNZ	DUMPOK
;
BADDMP:
	XRA	A
	STA	QFLAG	;NOT QUIET
	CALL	ILPRT
	DB	'++ Can''t dump, no sector read ++',CR,LF,0
;
EXPL:
	XRA	A
	STA	QFLAG	;NOT QUIET
	CALL	ILPRT
	DB	'Use G command following F,',CR,LF
	DB	'or R or S following T',CR,LF,0
	JMP	PRMPTR
;
DUMPOK:
	MOV	A,M	;GET NEXT CHAR
	CPI	';'	;LOGICAL EOL?
	JZ	DUMPDF	;DFLT
	CPI	CR	;PHYSICAL EOL?
	JNZ	DMPNDF
;
;Use default
;
DUMPDF:
	LXI	B,TBUFF
	LXI	D,0FFH
	JMP	DUMP1
;
DMPNDF:
	CALL	DISP
	MOV	B,D
	MOV	C,E
	CPI	CR
	JZ	DUMP1
	CPI	';'
	JZ	DUMP1
	INX	H	;SKIP ','
	CALL	DISP
;
;BC = start, DE = end
;
DUMP1:
	PUSH	H	;SAVE COMMAND POINTER
	MOV	H,B
	MOV	L,C
;
DUMPLP:
	MOV	A,L
	ANI	7FH
	CALL	HEX	;PRINT HEX VALUE
	CALL	SPACE
	CALL	SPACE
	LDA	DUMTYP
	CPI	'A'
	JZ	DUMPAS
	PUSH	H	;SAVE START
;
DHEX:
	MOV	A,M
	CALL	HEX	;PRINT HEX VALUE PTED TO BY HL
	MOV	A,L
	ANI	3
	CPI	3	;EXTRA SPACE EVERY 4
	CZ	SPACE
	MOV	A,L
	ANI	7
	CPI	7	;TWO EXTRA SPACES EVERY 8
	CZ	SPACE
	MOV	A,E
	CMP	L
	JZ	DPOP
	INX	H
	MOV	A,L
	ANI	0FH
	JNZ	DHEX
;
DPOP:
	CALL	CTLCS	;ABORT?
	JZ	PRMPTR
	LDA	DUMTYP
	CPI	'H'
	JZ	DNOAS	;HEX ONLY
	POP	H	;GET START ADDR
;
DUMPAS:
	CALL	ASTER	;PRINT FIRST ASTERISK TO SEPARATE TEXT
;
DCHR:
	MOV	A,M	;GET CHAR
	ANI	7FH
	CPI	' '
	JC	DPER
	CPI	7EH	;TRAP ESC FOR H1500
	JC	DOK
;
DPER:
	MVI	A,'.'	;PRINT PRINTING CHAR
;
DOK:
	CALL	TYPE	;PRINT CHAR
	MOV	A,E
	CMP	L
	JZ	DEND
	INX	H
	MOV	A,L
	ANI	0FH
	JNZ	DCHR
;
DEND:
	CALL	ASTER	;PRINT ENDING ASTERISK
	CALL	CRLF	;NEW LINE
	PUSH	D
	CALL	CTLCS	;ABORT?
	POP	D
	JZ	PRMPTR
	MOV	A,E
	CMP	L
	JNZ	DUMPLP
	POP	H
	JMP	PROMPT
;
DNOAS:
	POP	B
	CALL	CRLF
	MOV	A,E
	CMP	L
	JNZ	DUMPLP
	POP	H
	JMP	PROMPT
;
;COMMAND:  G
;Position
;
POS:
	PUSH	PSW
	MOV	A,M
	CPI	';'	;LOGICAL EOL?
	JZ	POSINQ
	CPI	CR	;PHYSICAL EOL?
	JNZ	POSOK
;
POSINQ:
	POP	PSW
	JMP	INQ
;
POSOK:
	POP	PSW
	CPI	'T'	;TRACK?
	JZ	POSTKD
	CPI	'S'	;SECTOR?
	JZ	POSSCD
	CPI	'G'	;GROUP?
	JZ	POSGPH
	JMP	WHAT	;ERROR OTHERWISE
;
;Position to Track
;
POSTKD:
	CALL	DECIN	;GET NUMBER IN DECIMAL
;
POSTRK:
	PUSH	H
	LHLD	MAXTRK	;CHECK FOR BEYOND END OF DISK
	CALL	SUBDE
	POP	H
	JC	OUTLIM
	CALL	SETTRK	;SET TRACK
	CALL	NORITE	;TRACK DOESN'T READ
	MVI	A,1
	STA	NOTPOS	;SHOW NOT POSITIONED
	JMP	CLCGRP
;
;Position to Sector
;
POSSCD:
	CALL	DECIN	;GET NUMBER IN DECIMAL
	MOV	A,D
	ORA	E
	JZ	WHAT	;DON'T ALLOW SECTOR 0
;
POSSEC:
	PUSH	H
	LHLD	SPT	;CHECK FOR WITHIN RANGE
	CALL	SUBDE
	POP	H
	JC	WHAT
	CALL	SETSEC	;SET SECTOR
	CALL	READ	;READ
	XRA	A
	STA	NOTPOS	;POSITIONED OK
;
;Calculate Group Number/Group Displacement and Print
;
CLCGRP:
	CALL	CLCSUB
	JMP	INQ
;
;Calculate group from track and sector
;  On exit, GROUP = Group Number and GRPDIS = Displacement within Group
;
CLCSUB:
	PUSH	H
	LHLD	SYSTRK
	XCHG
	LHLD	CURTRK
	CALL	SUBDE	;COMPUTE RELATIVE TRACK NUMBER (SKIP SYSTEM TRACKS)
	XCHG
	LHLD	SPT	;MULTIPLY BY NUMBER OF SECTORS/TRACK
	CALL	MULT
	XCHG		;DE=TOTAL NUMBER OF SECTORS IN TRACKS
	LHLD	CURSEC	;GET SECTOR OFFSET FROM BEGINNING OF TRACK
	DCX	H
	DAD	D	;HL=TOTAL NUMBER OF SECTORS WITH OFFSET
	LDA	BLM
	MOV	B,A
	MOV	A,L
	ANA	B
	STA	GRPDIS	;DISPLACEMENT WITHIN GROUP
	LDA	BSH
	MOV	B,A
;
CLCLOP:
	CALL	ROTRHL
	DCR	B
	JNZ	CLCLOP
	SHLD	GROUP	;GROUP NUMBER
	POP	H
	RET
;
;Position in the directory after a find
;(Does not work in CP/M-2.x)
;
POSDIR:
	PUSH	H	;SAVE INBUF
	LHLD	BSH
	XRA	A
	STA	FINDFL	;CANCEL POS REQ
	LDA	DIRPOS	;GET POSITION
	RAR
	RAR
	PUSH	PSW
	ANA	H
	STA	GRPDIS
	POP	PSW
;
POSDLP:
	RAR
	DCR	L
	JNZ	POSDLP
	ANI	1	;GET GROUP
	MOV	L,A	;SETUP FOR POSGP2
	MVI	H,0
	SHLD	GROUP
	XCHG
	JMP	POSGP2	;POSITION TO IT
;
;Position to Group
;
POSGPH:
	CALL	HEXIN	;GET PARAMETER
;
;Position to Group Numbered in DE and Print Position
;
POSGRP:
	PUSH	H
	LHLD	DSM	;CHECK FOR WITHIN BOUNDS
	CALL	SUBDE
	POP	H
	JC	OUTLIM
	XCHG
	SHLD	GROUP	;SET GROUP NUMBER
	XCHG
	XRA	A
	STA	GRPDIS	;SET ZERO DISPLACEMENT
	PUSH	H
;
POSGP2:
	CALL	GTKSEC	;CONVERT GROUP TO SECTOR/TRACK
	CALL	SETTRK	;SET TRACK
	XCHG
	CALL	SETSEC	;SET SECTOR
	CALL	READ	;READ BLOCK
	XRA	A
	STA	NOTPOS	;NOW POSITIONED
	POP	H
	JMP	INQ
;
;Convert Group Number in DE to Sector and Track; also, GRPDIS = Offset in Grp
;  On exit, DE = Track Number, HL = Sector Number
;
GTKSEC:
	MOV	H,D	;HL=GROUP NUMBER
	MOV	L,E
	LDA	BSH	;GET NUMBER OF SECTORS IN GROUP
;
GLOOP:
	DAD	H
	DCR	A
	JNZ	GLOOP
	LDA	GRPDIS	;ADD IN DISPLACEMENT WITHIN GROUP
	ADD	L	;CAN'T CARRY
	MOV	L,A
;
;Divide by number of sectors, quotient=track, remainder=sector
;
	XCHG		;DE=TOTAL NUMBER OF SECTORS
	LHLD	SPT	;GET NUMBER OF SECTORS/TRACK
	CALL	NEG	;HL = -SECTORS/TRACK
	XCHG
	LXI	B,0	;SET TRACK COUNTER TO ZERO
;
DIVLP:
	INX	B	;INCREMENT TRACK COUNT
	DAD	D	;SUBTRACT SECTORS/TRACK FROM SECTORS TOTAL
	JC	DIVLP
	DCX	B	;ADJUST TRACK COUNT
	XCHG
	LHLD	SPT	;ADD SECTORS/TRACK BACK IN TO ADJUST
	DAD	D	;HL=NUMBER OF SECTORS ON LAST TRACK OF GROUP
	PUSH	H
	LHLD	SYSTRK	;ADD IN NUMBER OF SYSTEM TRACKS
	DAD	B
	XCHG		;DE=TRACK NUMBER
	POP	H
	INX	H	;HL=SECTOR NUMBER
	RET
;
;COMMAND:  F
;Find Directory Entry for specified file
;
POSFIL:
	CALL	NORITE
	MVI	A,1
	STA	FINDFL	;SO WE POSITION LATER
	LXI	D,FCB
	XRA	A	;LOGGED IN DISK
	STAX	D
	INX	D
	MVI	B,8
	CALL	MVNAME
	MVI	B,3
	CALL	MVNAME
	LXI	D,FCB
	MVI	C,SRCHF
	PUSH	H
	CALL	BDOS
	INR	A
	JNZ	FLOK
	STA	DIRPOS	;GRP 0 IF NOT FOUND
	CALL	ILPRT
	DB	'++ File Not Found ++',CR,LF,0
	POP	H
	JMP	PROMPT
;
FLOK:
	DCR	A
	STA	DIRPOS	;SAVE POS. IN DIR
	ANI	3
	MOV	L,A
	MVI	H,0
	DAD	H	;X32 BYTES/ENTRY
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	LXI	D,TBUFF
	DAD	D	;HL POINTS TO ENTRY
	LXI	D,32
	XCHG
	DAD	D
	XCHG
	MVI	A,'D'
	STA	DUMTYP
	JMP	DUMPLP	;WHICH POPS H
;
MVNAME:
	MOV	A,M	;GET NEXT CHAR OF FILE NAME/TYPE
	CPI	'.'	;END OF FILE NAME?
	JZ	MVIPAD	;PAD OUT IF SO
	CPI	CR	;END OF ENTRY?
	JZ	PAD	;PAD OUT IF SO
	CPI	';'	;END OF ENTRY?
	JZ	PAD	;PAD OUT IF SO
	CALL	UPCASE	;CAPITALIZE
	STAX	D	;STORE
	INX	H	;PT TO NEXT
	INX	D
	DCR	B	;COUNT DOWN
	JNZ	MVNAME
	MOV	A,M	;CHECK FOR ERROR
	CPI	CR	;OK IF EOL
	RZ
	CPI	';'	;OK IF LOGICAL EOL
	RZ
	INX	H
	CPI	'.'	;OK IF DECIMAL
	RZ
	JMP	WHAT
;
MVIPAD:
	INX	H
;
PAD:
	MVI	A,' '	;PRINT PADDING SPACES
	STAX	D
	INX	D
	DCR	B
	JNZ	PAD
	RET
;
;COMMAND:  +
;Advance to Next Logical Sector
;
PLUS:
	LXI	D,1	;DFLT TO 1 SECT
	MOV	A,M	;GET NEXT CHAR
	CPI	CR	;CR?
	JZ	PLUSGO	;..YES, DFLT TO 1
	CPI	';'
	JZ	PLUSGO
	CALL	DECIN	;GET #
	MOV	A,D
	ORA	E
	JZ	WHAT
;
PLUSGO:
	CALL	NXTSEC	;ADVANCE TO NEXT LOGICAL SECTOR
	DCX	D	;MORE TO GO?
	MOV	A,D
	ORA	E
	JNZ	PLUSGO	;..YES
;
;Ok, incremented to sector.  Setup and read
;
PLUSMI:
	PUSH	H
	LHLD	CURSEC
	XCHG
	CALL	SETSEC	;SET SECTOR
	LHLD	CURTRK
	XCHG
	CALL	SETTRK	;SET TRACK
	POP	H
	CALL	READ	;READ IT
	JMP	CLCGRP	;CALCULATE GROUP AND DISPLAY
;
;COMMAND:  -
;Back up to previous sector
;
MINUS:
	LXI	D,1	;SET DFLT
	MOV	A,M	;GET CHAR
	CPI	CR	;CR?
	JZ	MINGO	;..YES, DFLT=1
	CPI	';'
	JZ	MINGO
	CALL	DECIN	;..NO, GET ##
	MOV	A,D
	ORA	E
	JZ	WHAT
;
MINGO:
	PUSH	H
	LHLD	CURSEC	;BACK UP SECTOR
	DCX	H
	MOV	A,H
	ORA	L
	JNZ	MINOK
	LHLD	CURTRK	;BEYOND SECTOR ZERO, SO BACK UP TRACK
	MOV	A,H
	ORA	L
	JNZ	SEASH
	LHLD	MAXTRK	;WRAP TO END OF DISK
	SHLD	CURTRK
	LHLD	MAXSEC
	JMP	MINOK
;
SEASH:
	DCX	H
	SHLD	CURTRK
	LHLD	SPT	;GET NUMBER OF SECTORS/TRACK
;
MINOK:
	SHLD	CURSEC	;SET NEW CURRENT SECTOR
	POP	H
	DCX	D	;COUNT DOWN ON NUMBER OF TIMES TO BACKUP
	MOV	A,D
	ORA	E
	JNZ	MINGO
	JMP	PLUSMI	;READ BLOCK
;
;Go to next sector
;  On exit, CURSEC = Current Sector and CURTRK = Current Track
;
NXTSEC:
	PUSH	H
	PUSH	D
	LHLD	CURSEC	;INCREMENT CURRENT SECTOR
	INX	H
	XCHG
	LHLD	SPT	;CHECK TO SEE IF BEYOND END OF TRACK
	CALL	SUBDE
	XCHG
	JNC	NEXTOK
	LHLD	CURTRK	;BEYOND END OF TRACK, SO INCR CURRENT TRACK
	INX	H
	XCHG
	LHLD	MAXTRK	;SEE IF BEYOND END OF DISK
	CALL	SUBDE
	JNC	TRASK
	LXI	D,0	;WRAP TO START OF DISK
;
TRASK:
	XCHG
	SHLD	CURTRK	;SET NEW CURRENT TRACK
	LXI	H,1	;SET SECTOR 1
;
NEXTOK:
	SHLD	CURSEC	;SET NEW CURRENT SECTOR
	POP	D
	POP	H
	RET
;
;Tell what group, displacement, track, sector, physical sector
;
INQ:
	CALL	INQSUB
	JMP	PROMPT
;
;Position inquiry subroutine
;Executed via: G S or T (with no operands)
;
INQSUB:
	PUSH	H
	LHLD	SYSTRK	;CHECK IF IN SYSTEM TRACKS
	XCHG
	LHLD	CURTRK
	CALL	SUBDE
	JC	NOGRP
	CALL	ILPRT	;PRINT GROUP NUMBER IF NOT IN SYSTEM TRACKS
	DB	'Group = ',0
	LHLD	GROUP
	MOV	B,H
	MOV	C,L
	CALL	HEXB	;PRINT GROUP NUMBER IN BC
	MVI	A,':'
	CALL	TYPE
	LDA	GRPDIS
	CALL	HEX	;PRINT GROUP DISPLACEMENT IN A
	MVI	A,','
	CALL	TYPE
;
NOGRP:
	CALL	ILPRT	;PRINT TRACK NUMBER
	DB	' Track = ',0
	LHLD	CURTRK
	CALL	DEC	;TRACK NUMBER IN DECIMAL
	CALL	ILPRT	;PRINT SECTOR NUMBER
	DB	', Sector = ',0
	LHLD	CURSEC
	CALL	DEC	;SECTOR NUMBER IN DECIMAL
	CALL	ILPRT	;PRINT PHYSCIAL SECTOR NUMBER
	DB	', Physical Sector = ',0
	LHLD	PHYSEC
	CALL	DEC	;PHYSICAL SECTOR NUMBER IN DECIMAL
	CALL	CRLF
	POP	H
	RET
;
;COMMAND:  C
;Change Contents of Current Block
;
CHG:
	MOV	A,M	;GET TYPE (HEX, ASCII)
	CALL	UPCASE
	PUSH	PSW	;SAVE "H" OR "A"
	INX	H
	CALL	HEXIN	;GET DISP IN HEX
	CALL	DISP1	;VALIDATE DISP TO DE
	INX	H
	LXI	B,0	;SHOW NO 'THRU' ADDR
	CPI	'-'	;TEST DELIM FR. DISP
	JNZ	CHGNTH	;NO THRU
	PUSH	D	;SAVE FROM
	CALL	HEXIN
	CALL	DISP1	;GET THRU
	INX	H	;SKIP END DELIM
	MOV	B,D
	MOV	C,E	;BC = THRU
	POP	D	;GET FROM
	JMP	CHGAH
;
CHGNTH:
	CPI	','
	JNZ	WHAT
;
CHGAH:
	POP	PSW
	CPI	'H'	;HEX?
	JZ	CHGHEX
	CPI	'A'	;ASCII?
	JNZ	WHAT
;
;Change ASCII
;
CHGALP:
	MOV	A,M	;GET CHAR
	CPI	CR
	JZ	PROMPT
	CPI	';'
	JZ	PROMPT
;
;The following print of the deleted byte is commented out; if leading
;  semicolons are removed, deleted bytes will be printed
;
;	LDAX	D	;GET BYTE THAT IS REPLACED
;	CPI	' '
;	JC	CHGAHX
;	CPI	7EH	;DON'T PRINT ESC CHAR FOR H1500
;	JNC	CHGAHX
;	JMP	CHGA2
;
;CHGAHX:
;	CALL	BHEX
;	JMP	CHGA3
;
;CHGA2:
;	CALL	TYPE
;
;End of print of delete bytes
;
CHGA3:
	SHLD	BACK	;IN CASE "THRU"
	CALL	GETVAL	;GET ASCII OR <HEX> VALUE
	STAX	D	;UPDATE BYTE
	INX	H	;PT TO NEXT INPUT CHAR
;
;See if 'THRU' requested
;
	MOV	A,C
	ORA	A
	JZ	CHANTH
	CMP	E	;DONE?..
	JZ	PROMPT	;..YES
	LHLD	BACK
;
CHANTH:
	INR	E
	JNZ	CHGALP
	MOV	A,M
	CPI	CR
	JZ	PROMPT
	CPI	';'
	JZ	PROMPT
	JMP	WHAT
;
;Change hex
;
CHGHCM:
	INX	H
;
CHGHEX:
	MOV	A,M	;GET HEX DIGIT
	CPI	CR
	JZ	PROMPT
	CPI	';'
	JZ	PROMPT
	CPI	','	;DELIM?
	JZ	CHGHCM
	PUSH	D
	SHLD	HEXAD	;IN CASE 'THRU'
	CALL	HEXIN	;POSITIONS TO DELIM
	MOV	A,E	;GET VALUE
	POP	D	;..ADDR
;
;The following comments out the echo of the deleted byte; removing the
;  leading semicolons restores the echo
;
;	PUSH	PSW	;SAVE VALUE
;	LDAX	D	;GET OLD
;	CALL	HEX	;ECHO IN HEX
;	POP	PSW	;GET NEW
;
;End of echo of bytes
;
	STAX	D	;SAVE NEW BYTE
	MOV	A,C	;SEE IF 'THRU'
	ORA	A
	JZ	CHHNTH	;..NO.
	CMP	E	;..YES, DONE?
	JZ	PROMPT
	LHLD	HEXAD	;..NO: MORE
;
CHHNTH:
	INR	E
	JNZ	CHGHEX
	MOV	A,M
	CPI	CR
	JZ	PROMPT
	CPI	';'
	JZ	PROMPT
	JMP	WHAT
;
;COMMAND:  R
;Read Current Block into TBUFF
;COMMAND:  RG
;Read Specified Group into GBUFF
;
DOREAD:
	MOV	A,M	;GET CHAR AFTER R
	CALL	UPCASE	;CAPITALIZE
	CPI	'G'	;READ GROUP?
	JZ	SAVEG	;SAVE GROUP IF SO
	LDA	NOTPOS	;POSITIONED?
	ORA	A
	JNZ	CANTRD
	CALL	READ	;READ BLOCK
	JMP	PROMPT
;
CANTRD:
	XRA	A
	STA	QFLAG	;NOT QUIET
	CALL	ILPRT
	DB	'++ Can''t read - not positioned ++',CR,LF
	DB	'Position by:',CR,LF
	DB	'	Track then Sector, or',CR,LF
	DB	'	Group',CR,LF,0
	JMP	PROMPT
;
;COMMAND:  W
;Write Current Block to Disk
;COMMAND:  WG
;Write Specified Group from GBUFF
;
DORITE:
	MOV	A,M	;GET NEXT CHAR
	CALL	UPCASE	;CAPITALIZE
	CPI	'G'	;GROUP?
	JZ	RESTRG	;DO GROUP WRITE
	CALL	WRITE	;DO WRITE
	JMP	PROMPT
;
;Print Byte in A as Hex Digits
;
BHEX:
	PUSH	PSW
	MVI	A,'<'
	CALL	TYPE
	POP	PSW
	CALL	HEX
	MVI	A,'>'
	CALL	TYPE
	RET
;
;Print Number in BC as Hex Digits
;
HEXB:
	LDA	DSM+1
	ORA	A
	JZ	HEXX
	MOV	A,B
	CALL	HEX
;
HEXX:
	MOV	A,C
;
;Print Byte in A as 2 Hex Digits
;
HEX:
	PUSH	PSW
	RAR		;GET HIGH NYBBLE
	RAR
	RAR
	RAR
	CALL	NIBBL	;PRINT IT
	POP	PSW	;GET LOW NYBBLE
;
NIBBL:
	ANI	0FH	;MASK LOW NYBBLE
	CPI	10	;0-9?
	JC	HEXNU
	ADI	7	;CONVERT TO A-F
;
HEXNU:
	ADI	'0'	;CONVERT TO ASCII
	JMP	TYPE	;PRINT IT
;
;Decimal output routine
;  Print Number in HL as decimal digits (HL<100)
;
DEC:
	PUSH	B
	PUSH	D
	PUSH	H
	LXI	B,-10
	LXI	D,-1
;
DECOU2:
	DAD	B
	INX	D
	JC	DECOU2
	LXI	B,10
	DAD	B
	XCHG
	MOV	A,H
	ORA	L
	CNZ	DEC
	MOV	A,E
	ADI	'0'
	CALL	TYPE
	POP	H
	POP	D
	POP	B
	RET
;
;Print <SP>
;
SPACE:	MVI	A,' '
	JMP	TYPE
;
;Print '*'
;
ASTER:	MVI	A,'*'
	JMP	TYPE
;
;Inline print routine
;  Print Chars ending in 0 pted to by Return Address; return to byte after
;
ILPRT:
	XTHL		;GET PTR AND SAVE HL
;
ILPLP:
	CALL	CTLCS	;ABORT?
	JZ	PRMPTR
	MOV	A,M	;GET CHAR
	CPI	1	;PAUSE? -- ^A
	JNZ	ILPOK
	CALL	CONIN	;WAIT FOR ANY CHAR
	CPI	3	;ABORT?
	JZ	PRMPTR
	JMP	ILPNX
;
ILPOK:
	CALL	TYPE	;PRINT CHAR
;
ILPNX:
	INX	H	;PT TO NEXT
	MOV	A,M	;GET IT
	ORA	A	;DONE?
	JNZ	ILPLP
	INX	H	;PT TO BYTE AFTER ENDING 0
	XTHL		;RESTORE HL AND RET ADR
	RET
;
;DISP calls DECIN, and validates a sector
;displacement, then converts it to an address
;
DISP:
	CALL	DECIN
DISP1:
	PUSH	PSW	;SAVE DELIMITER
	MOV	A,D
	ORA	A
	JNZ	BADISP
	MOV	A,E
	ORA	A
	JM	BADISP
	ADI	80H	;TO POINT TO BUFFER AT BASE+80H
	MOV	E,A
	MVI	D,BASE/256
	POP	PSW	;GET DELIM
	RET
;
BADISP:
	XRA	A
	STA	QFLAG	;NOT QUIET
	CALL	ILPRT
	DB	'++ Bad Displacement (Not 0-7FH) ++'
	DB	CR,LF,0
	JMP	PRMPTR
;
;Input Number from Command Line -- Assume it to be Hex
;  Number returned in DE
;
HEXIN:
	LXI	D,0
	MOV	A,M
	CPI	'#'	;DECIMAL?
	JZ	HDIN	;MAKE DECIMAL
;
HINLP:
	MOV	A,M	;GET CHAR
	CALL	UPCASE	;CAPITALIZE
	CPI	CR	;EOL?
	RZ
	CPI	';'	;EOL?
	RZ
	CPI	','
	RZ
	CPI	'-'	;'THRU'?
	RZ
	CPI	'>'
	RZ
	INX	H	;PT TO NEXT CHAR
	CPI	'0'	;RANGE?
	JC	WHAT
	CPI	'9'+1	;RANGE?
	JC	HINNUM
	CPI	'A'	;RANGE?
	JC	WHAT
	CPI	'F'+1	;RANGE?
	JNC	WHAT
	SUI	7	;ADJUST FROM A-F TO 10-15
;
HINNUM:
	SUI	'0'	;CONVERT FROM ASCII TO BINARY
	XCHG
	DAD	H	;MULT PREVIOUS VALUE BY 16
	DAD	H
	DAD	H
	DAD	H
	ADD	L	;ADD IN NEW DIGIT
	MOV	L,A
	XCHG
	JMP	HINLP
;
HDIN:
	INX	H	;SKIP '.'
;
;Input Number in Command Line as Decimal
;  Number is returned in DE
;
DECIN:
	LXI	D,0
	MOV	A,M	; GET 1ST CHAR
	CPI	'#'	; HEX?
	JNZ	DINLP
	INX	H	; PT TO DIGIT
	JMP	HINLP	; DO HEX PROCESSING
;
DINLP:
	MOV	A,M	;GET DIGIT
	CALL	UPCASE	;CAPITALIZE
	CPI	CR	;EOL?
	RZ
	CPI	';'	;EOL?
	RZ
	CPI	','
	RZ
	CPI	'-'	;'THRU'?
	RZ
	INX	H	;PT TO NEXT
	CPI	'0'	;RANGE?
	JC	WHAT
	CPI	'9'+1	;RANGE?
	JNC	WHAT
	SUI	'0'	;CONVERT TO BINARY
	PUSH	H
	MOV	H,D
	MOV	L,E
	DAD	H	;X2
	DAD	H	;X4
	DAD	D	;X5
	DAD	H	;X10
	ADD	L	;ADD IN DIGIT
	MOV	L,A
	MOV	A,H
	ACI	0
	MOV	H,A
	XCHG		;RESULT IN DE
	POP	H
	JMP	DINLP
;
;Read in a console buffer
;
RDBUF:
	CALL	ILPRT	;PRINT PROMPT
	DB	CR,LF,'DUTIL  ',0
	LDA	DRIVE	;GET DRIVE NUMBER
	ADI	'A'	;CONVERT TO ASCII
	CALL	TYPE
	LDA	VER2FL	;VERSION 2 OR BETTER?
	ORA	A	;0=NO
	JZ	RDBUF1
	MVI	A,'/'
	CALL	TYPE
	LDA	UNUM	;DISPLAY USER NUMBER
	MOV	L,A	;VALUE IN HL
	MVI	H,0
	CALL	DEC	;PRINT IN DECIMAL
RDBUF1:
	CALL	ILPRT	;PRINT PROMPT
	DB	'? ',0
	LXI	D,INBUF-2	;USE CP/M READLN
	MVI	C,10
	CALL	BDOS
	LDA	INBUF-1	;GET CHAR COUNT
	MOV	B,A	;CHAR COUNT IN B
	LXI	H,INBUF	;STORE ENDING <CR>
	ADD	L	;ADD CHAR COUNT TO HL
	MOV	L,A
	MOV	A,H
	ACI	0
	MOV	H,A
	MVI	A,CR	;STORE ENDING <CR>
	MOV	M,A	;SAVE IT
	CALL	TYPE	;ECHO IT
	MVI	A,LF	;ECHO..
	CALL	TYPE	;..LF
	LXI	H,INBUF	;SET PTR TO FIRST CHAR IN LINE
	RET
;
;Set paging flag for page routine
;
PAGSET:
	LDA	PAGSIZ	;GET SIZE OF PAGE
	STA	PAGFLG	;SET FLAG
	RET
;
;Page output
;
PAGER:
	LDA	PAGFLG	;GET FLAG
	CPI	2	;2 LINES LEFT?
	JZ	WAIT	;SAME AS USER DELAY
	DCR	A	;COUNT DOWN
	STA	PAGFLG
	JMP	CRLF
;
;Delay Routine
;
WAIT:	PUSH	H
	CALL	ILPRT
	DB	CR,LF,'Type Any Character to Continue or ^C to Abort - ',0
	POP	H
	CALL	CONIN	;GET RESPONSE
	CPI	'C'-40H	;^C?
	JZ	WAIT1
	CALL	CRLF	;NEW LINE
	CALL	PAGSET	;RESET PAGE COUNT
	RET
WAIT1:
	LDA	IHFLG	;INITIAL HELP?
	ORA	A	;0=NO
	JZ	PRMPTR	;ABORT TO COMMAND PROMPT
	JMP	EXIT1	;ABORT TO CP/M
;
;CRLF Routine
;
CRLF:	MVI	A,CR
	CALL	TYPE
	MVI	A,LF
	JMP	TYPE
;
;Convert to Upper Case
;
UPCASE:
	ANI	7FH	;MASK OUT MSB
	CPI	60H	;LESS THAN SMALL A?
	RC		;RETURN IF SO
	ANI	5FH	;MAKE UPPER CASE
	RET
;
;CON: Status Routine
;
CONST:
	PUSH	B
	PUSH	D
	PUSH	H
VCONST:
	CALL	$-$	;ADDR FILLED IN BY 'INIT'
	POP	H
	POP	D
	POP	B
	RET
;
;CON: Input Routine
;
CONIN:
	PUSH	B
	PUSH	D
	PUSH	H
VCONIN:
	CALL	$-$	;ADDR FILLED IN BY 'INIT'
	POP	H
	POP	D
	POP	B
	RET
;
;Console out with TAB expansion
;  Char in A
;
TYPE:
	PUSH	B	;SAVE REGS
	PUSH	D
	PUSH	H
	MOV	C,A	;FOR OUTPUT ROUTINE
	CPI	TAB
	JNZ	TYPE2
;Tabulate
TYPTAB:
	MVI	A,' '	;PRINT SPACE
	CALL	TYPE
	LDA	TABCOL	;GET COL COUNT
	ANI	7	;DONE?
	JNZ	TYPTAB
	JMP	TYPRET
;
;Filter out control characters to
;prevent garbage during view of file
;
TYPE2:
	CPI	' '
	JNC	TYPEQ
	CPI	CR
	JZ	TYPEQ
	CPI	LF
	JNZ	TYPNCR
;
TYPEQ:
	LDA	QFLAG	;CHECK QUIET FLAG FOR NO MESSAGES
	ORA	A

;
;CON: Output Routine
;
VCONOT:	CZ	$-$	;ADDR FILLED IN BY 'INIT'
;
;Update column used in tab expansion
;
	MOV	A,C	;GET CHAR
	CPI	CR
	JNZ	TYPNCR
	MVI	A,0	;RESET TAB COLUMN IF <CR>
	STA	TABCOL
	JMP	TYPLST
;
TYPNCR:
	CPI	' '	;CTL CHAR?
	JC	TYPLST	;..NO CHANGE IN COL
	LDA	TABCOL	;INCR TAB COUNT
	INR	A
	STA	TABCOL
;
TYPLST:
	LDA	PFLAG	;CHECK FOR PRINTER OUTPUT
	ANI	1
	CNZ	LIST	;FROM C REG
;
TYPRET:
	POP	H	;RESTORE REGS
	POP	D
	POP	B
	RET
;
;LST: Output Routine
;  Char in C
;
LIST:
	PUSH	B	;SAVED REGS
	PUSH	D
	PUSH	H
VLIST:
	CALL	$-$	;ADDR FILLED IN BY 'INIT'
	POP	H
	POP	D
	POP	B
	RET
;
;Home Disk Routine
;
HOME:
	PUSH	H
VHOME:
	CALL	$-$	;ADDR FILLED IN BY 'INIT'
	POP	H
	RET
;
;Set track # in DE
;
SETTRK:
	PUSH	H
	LHLD	MAXTRK	;CHECK FOR WITHIN BOUNDS
	CALL	SUBDE	;IF TRACK # IN DE > MAX, THEN ERROR
	POP	H
	JC	OUTLIM
	XCHG		;RESET CURRENT TRACK
	SHLD	CURTRK
	XCHG
	MOV	B,D	;BC=TRACK NUMBER
	MOV	C,E
	PUSH	H
;
VSETRK:
	CALL	$-$	;ADDR FILLED IN BY 'INIT'
	POP	H
	RET
;
;Set Sector Number in DE
;
SETSEC:
	PUSH	H
	PUSH	D
	LHLD	SYSTRK	;GET NUMBER OF SYSTEM TRACKS
	XCHG
	SHLD	CURSEC	;SET CURRENT SECTOR
	LHLD	CURTRK	;GET CURRENT TRACK
	CALL	SUBDE	;SEE IF WE ARE IN THE SYSTEM TRACKS
	POP	B	;BC=SECTOR NUMBER
	MOV	H,B	;HL=SECTOR NUMBER
	MOV	L,C
	JNC	NOTSYS	;IF NO CARRY FOR SUBDE, WE ARE NOT IN SYSTEM TRACKS
	LDA	FIRST0	;SEE IF FIRST SEC 0
	ORA	A
	JNZ	GSTSEC	;NO, JUMP AWAY
	DCX	H	;YES, SO DECREMENT
	JMP	GSTSEC	;REQUESTED, THEN GO
;
;Not in System Tracks, so Skew Factor is effective
;
NOTSYS:
	LHLD	SECTBL	;GET PTR TO SECTOR TABLE
	XCHG		;... IN DE
	DCX	B	;DECREMENT SECTOR NUMBER BY 1
;
VSCTRN:
	CALL	$-$	;ADDR FILLED IN BY 'INIT'
	LDA	SPT+1	;IF SPT<256 (HI-ORD = 0)
	ORA	A	; THEN FORCE 8-BIT TRANSLATION
	JNZ	VSCTR1	; ELSE KEEP ALL 16 BITS
	MOV	H,A
VSCTR1:
	LDA	VER2FL	;SEE IF VERSION 2.x
	ORA	A	;SET FLAGS
	JNZ	GSTSEC	;JUMP IF CP/M 2.x
	MVI	H,0	;CP/M 1.4 GOOD TO ONLY 8 BITS
	MOV	L,C	;MOST BIOS'S RETURN THE
			;  PHYSICAL SEC # IN REG C
GSTSEC:
	SHLD	PHYSEC	;THIS MAY BE REDUNTANT IN
			; MOST 1.4 VERSIONS, BUT
			; SHOULD CAUSE NO PROBLEMS
	MOV	B,H
	MOV	C,L
;
VSTSEC:
	CALL	$-$	;ADDR FILLED IN BY 'INIT'
	POP	H	;RESTORE PTR TO NEXT CHAR
	RET
;
;Out of Disk Track Limit
;
OUTLIM:
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	'++ Not Within Tracks 0-',0
	PUSH	H
	LHLD	MAXTRK	;PRINT MAX TRACK NUMBER
	CALL	DEC
	POP	H
	CALL	ILPRT
	DB	' ++',CR,LF,0
	CALL	NORITE	;NOT POSITIONED
	JMP	PRMPTR
;
;Set DMA Address
;
SETDMA:
	JMP	$-$	;ADDR FILLED IN BY 'INIT'
;
;Read Next Block into DMA Address
;
READ:
	MVI	A,1	;SET FLAG
	STA	WRFLG
	PUSH	H	;SAVE PTR TO NEXT CHAR
;
VREAD:
	CALL	$-$	;ADDR FILLED IN BY 'INIT'
	ORA	A	;ERROR?
	JZ	READOK
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	'++ READ Failed, Sector may be Invalid ++'
	DB	CR,LF,0
;
READOK:
	POP	H	;GET PTR TO NEXT CHAR
	RET
;
;Write Block in DMA Address to Disk
;
WRITE:
	LDA	WRFLG	;READ ALREADY PERFORMED?
	ORA	A	;ERROR IF NOT
	JNZ	PWRITE
;
BADW:
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	'++ Cannot Write Unless Read Issued ++'
	DB	CR,LF,0
	JMP	EXPL
;
;Do Write
;
PWRITE:
	PUSH	H	;SAVE PTR TO NEXT CHAR
	MVI	C,1	;FORCE WRITE TYPE 1 IN CASE 2.x DEBLOCK USED
;
VWRITE:
	CALL	$-$	;ADDR FILLED IN BY 'INIT'
	ORA	A	;ERROR?
	JZ	WRITOK
	XRA	A	;NOT QUIET
	STA	QFLAG
	CALL	ILPRT
	DB	'++ WRITE Failed ++',CR,LF,0
;
WRITOK:
	POP	H
	RET
;
;Help; HELP is entry point for HELP (?) command, HELP1 is entry point for
; Initial Help Command, and IHELP is entry point for HELP (/) from command
; line
;
IHELP:
	XRA	A
	STA	QFLAG	;NOT QUIET
	CALL	ILPRT
	DB	'Introductory HELP on DUTIL (Disk Utility)',CR,LF
	DB	'  The DUTIL program is designed to provide the user with'
	DB	CR,LF
	DB	'the ability to manipulate information on the disk as easily'
	DB	CR,LF
	DB	'as the DDT and SID utilities allow the user to manipulate'
	DB	CR,LF
	DB	'information in memory.',CR,LF
	DB	'  The following is a summary of the commands available to'
	DB	CR,LF
	DB	'the DUTIL user.  This same list is invoked internally by the'
	DB	CR,LF
	DB	'? Command of DUTIL.  For additional information on disk'
	DB	CR,LF
	DB	'structures and how to use DUTIL in general, refer to the'
	DB	CR,LF
	DB	'files DUTIL.DOC and DUTIL.HLP.',CR,LF,0
	MVI	A,0FFH	;A=0FFH
	STA	IHFLG	;SET INITIAL HELP
	CALL	WAIT
	JMP	HELP1	;PROCESS NORMALLY
HELP:
	XRA	A	;A=0
	STA	IHFLG	;SET NO INITIAL HELP
HELP1:
	XRA	A
	STA	QFLAG	;NOT QUIET
	CALL	ILPRT
	DB	'=======================================================',CR,LF
	DB	'		   -- Command Summary --		',CR,LF
	DB	'-------------------------------------------------------',CR,LF
	DB	CR,LF
	DB	'Operands in brackets [...] are optional'
	DB	CR,LF,CR,LF
	DB	'@	Repeat Previous Non-@ Command Line'
	DB	CR,LF
	DB	'+[nn]	Step In [nn (decimal)] Sectors;	-[nn]  Step Out '
	DB	'Sectors'
	DB	CR,LF
	DB	'#	Print Disk Parameters for Current Drive'
	DB	CR,LF
	DB	'=xxx	Search for ASCII xxx from Current Sector'
	DB	CR,LF
	DB	'	 Caution: upper/lower case matters.'
	DB	CR,LF
	DB	'	 Use <xx> for hex:'
	DB	CR,LF
	DB	'	  To find "IN 0" use: =<db><0>     or'
	DB	CR,LF
	DB	'	  "(tab)H,0(CR)(LF)" use: =<9>H,0<D><A>'
	DB	CR,LF
	DB	'<	Save Current Sector;	>      Restore Saved Sector'
	DB	CR,LF
	DB	'/[nn]	Repeat [nn (decimal) times];	!      Pause for User'
	DB	CR,LF
	DB	':ntext	Define ''text'' to be Macro n;	n	Perform Macro'
	DB	' n, 0<=n<=9'
	DB	CR,LF
	DB	':Pn	Print Macro n, 0<=n<=9'
	DB	CR,LF
	DB	':Px	Print All Macros if x=A or Print Prev Line if x=@'
	DB	CR,LF,CR,LF,0
	CALL	WAIT
	CALL	ILPRT
	DB	'-------------------------------------------------------',CR,LF
	DB	'A[ff,tt]	ASCII Dump'
	DB	CR,LF
	DB	'C	Change:'
	DB	CR,LF
	DB	'	 CHaddr,byte,byte... (hex)'
	DB	CR,LF
	DB	'	 or CAaddr,data...  (Ascii)'
	DB	CR,LF
	DB	'	 <xx> Allowed for imbedded hex.'
	DB	CR,LF
	DB	'	 or CHfrom-thru,byte  e.g. ch0-7f,e5'
	DB	CR,LF
	DB	'	 or CAfrom-thru,byte'
	DB	CR,LF
	DB	'D[ff,tt]	Dump (Hex and ASCII)'
	DB	CR,LF
	DB	'Fn.t	Find File'
	DB	CR,LF
	DB	'Gnn	CP/M Allocation Group nn (hex)'
	DB	CR,LF
	DB	'H[ff,tt]	Hex Dump'
	DB	CR,LF
	DB	'L	Log in drive;	Lx	Log in drive x'
	DB	CR,LF
	DB	'M[nn]	Map [from group nn (hex)]'
	DB	CR,LF,CR,LF,0
	CALL	WAIT
	CALL	ILPRT
	DB	'-------------------------------------------------------',CR,LF
	DB	CR,LF
	DB	'N	Load New Disk;	P	Toggle Printer Switch'
	DB	CR,LF
	DB	'Q	Quiet Mode (no messages)'
	DB	CR,LF
	DB	'R	Read Current Sector;	RG	Read Specified Group'
	DB	CR,LF
	DB	'Snn	Sector nn (decimal)'
	DB	CR,LF
	DB	'Tnn	Track nn (decimal)'
	DB	CR,LF
	DB	'Unn	Set User nn (decimal) for Find command (CP/M-2 only)'
	DB	CR,LF
	DB	'V[nn]	View [nn (decimal)] ASCII Sectors'
	DB	CR,LF
	DB	'W	Write Current Sector;	WG	Write Specified Group'
	DB	CR,LF
	DB	'X	Exit Program'
	DB	CR,LF
	DB	'Z[nn]	Sleep [nn (decimal) seconds]'
	DB	CR,LF,CR,LF,0
	CALL	WAIT
	CALL	ILPRT
	DB	'-------------------------------------------------------',CR,LF
	DB	CR,LF
	DB	'Command Line is of the form:  DUTIL d/u?',CR,LF
	DB	'	"d" is Logged-In Disk, "u" is Current User',CR,LF
	DB	CR,LF
	DB	'Cancel a function with C or Ctrl-C.'
	DB	CR,LF
	DB	'Suspend output with S or Ctrl-S.'
	DB	CR,LF
	DB	'Separate commands with ";".'
	DB	CR,LF
	DB	'	Example: g0'
	DB	CR,LF
	DB	'	+;d;z2;/'
	DB	CR,LF
	DB	'	 would step in, dump, sleep 2 sec, '
	DB	CR,LF
	DB	'	 and repeat until control-c typed.'
	DB	CR,LF
	DB	'"nn" usage varies with command as follows:',CR,LF
	DB	'	+, -, /, T, S, U, V, Z	nn in Decimal',CR,LF
	DB	'				(use #nn for Hex)',CR,LF
	DB	'	G, M			nn in Hexadecimal',CR,LF
	DB	'				(use #nn for Decimal)'
	DB	CR,LF
	DB	'"ff" and "tt" are in Hexadecimal (use #ff or #tt for Decimal)'
	DB	CR,LF,CR,LF
	DB	'=======================================================',CR,LF
	DB	0
	CALL	WAIT
	CALL	ILPRT
	DB	'=======================================================',CR,LF
	DB	'DUTIL Status Information',CR,LF
	DB	'-------------------------------------------------------',CR,LF
	DB	'Processor Speed:  ',0
	LDA	CLOCK	;GET CLOCK SPEED
	ADI	'0'	;CONVERT TO ASCII
	CALL	TYPE	;PRINT
	CALL	ILPRT
	DB	' MHz',CR,LF
	DB	'Number of Lines on CON:  ',0
	LDA	PAGSIZ	;GET PAGE SIZE
	MOV	L,A	;NUMBER IN HL
	MVI	H,0
	CALL	DEC	;PRINT NUMBER IN DECIMAL
	CALL	ILPRT
	DB	CR,LF,'Group Save Buffer Address:  ',0
	LXI	B,GBUFF	;BC=ADDRESS
	CALL	HEXB	;PRINT AS HEX
	CALL	ILPRT
	DB	' Hex',CR,LF
	DB	'=======================================================',CR,LF
	DB	0
	LDA	IHFLG	;INITIAL HELP?
	ORA	A	;0=NO
	JNZ	EXIT1	;RETURN TO CP/M IF SO
	JMP	PRMPTR	;NEW LINE INPUT IF NOT
;
;COMMAND:  X
;Exit to CP/M
;
EXIT:
	XRA	A
	STA	QFLAG	;NOT QUIET
	CALL	ILPRT	;PRINT
	DB	CR,LF,'Exit to CP/M -- Do you wish to Warm Boot (Y/N/<CR>=N)?'
	DB	' ',0
	CALL	CONIN	;GET RESPONSE
	CALL	UPCASE	;CAPITALIZE
	CPI	'Y'	;YES?
	JZ	BASE	;WARM BOOT IF SO
;
;Quick Exit to CP/M
;
EXIT1:
	LHLD	DUTSTK	;GET CP/M STACK PTR
	SPHL		;SET SP
	RET

;
;********************************
;*				*
;*    Utility Subroutines	*
;*				*
;********************************
;
GRPCMP:
	MOV	A,C
	INR	D
	DCR	D
	JZ	CMP8
	CMP	M
	INX	H
	RNZ
	MOV	A,B
;
CMP8:
	CMP	M
	RET
;
;2's complement HL ==> HL
;
NEG:
	MOV	A,L
	CMA
	MOV	L,A
	MOV	A,H
	CMA
	MOV	H,A
	INX	H
	RET
;
;HL/2 ==> HL
;
ROTRHL:
	ORA	A
	MOV	A,H
	RAR
	MOV	H,A
	MOV	A,L
	RAR
	MOV	L,A
	RET
;
;Collect the number of '1' bits
;in A as a count in C
;
COLECT:
	MVI	B,8	;NUMBER OF BITS
;
COLOP:
	RAL
	JNC	COSKIP
	INR	C
;
COSKIP:
	DCR	B
	JNZ	COLOP
	RET
;
;HL-DE ==> HL
;  Carry Flag is Significant
;
SUBDE:
	MOV	A,L
	SUB	E
	MOV	L,A
	MOV	A,H
	SBB	D
	MOV	H,A
	RET
;
;Quick Kludge multiply
;HL*DE ==> HL
;
MULT:
	PUSH	B
	PUSH	D
	XCHG
	MOV	B,D
	MOV	C,E
	MOV	A,B
	ORA	C
	JNZ	MULCON
	LXI	H,0	;FILTER SPECIAL CASE
	JMP	MLDONE	;  OF MULTIPLY BY 0
;
MULCON:
	DCX	B
	MOV	D,H
	MOV	E,L
;
MULTLP:
	MOV	A,B
	ORA	C
	JZ	MLDONE
	DAD	D
	DCX	B
	JMP	MULTLP
;
MLDONE:
	POP	D
	POP	B
	RET
;
;Routine to fill in disk params
;with every drive change
;
LOGIT:
	LDA	VER2FL
	ORA	A	;IF NOT CP/M 2.x THEN
	JZ	LOG14	;	DO IT AS 1.4
;
;CP/M 2.x
;
	LXI	D,DPB	;   THEN MOVE TO LOCAL
	MVI	B,DPBLEN ;  WORKSPACE
	CALL	MOVE
	JMP	LOGCAL
;
;CP/M 1.4
;
LOG14:
	LHLD	BDOS+1	;FIRST FIND 1.4 BDOS
	MVI	L,0
	LXI	D,DPBOFF ;THEN OFFSET TO 1.4'S DPB
	DAD	D
	MVI	D,0	;SO 8 BIT PARMS WILL BE 16
	MOV	E,M	;NOW MOVE PARMS
	INX	H
	XCHG
	SHLD	SPT
	XCHG
	MOV	E,M
	INX	H
	XCHG
	SHLD	DRM
	XCHG
	MOV	A,M
	INX	H
	STA	BSH
	MOV	A,M
	INX	H
	STA	BLM
	MOV	E,M
	INX	H
	XCHG
	SHLD	DSM
	XCHG
	MOV	E,M
	INX	H
	XCHG
	SHLD	AL0
	XCHG
	MOV	E,M
	XCHG
	SHLD	SYSTRK
;
LOGCAL:
	LXI	H,GRPDIS
	MOV	A,M
	PUSH	PSW
	LDA	BLM
	MOV	M,A
	PUSH	H
	LHLD	DSM
	XCHG
	CALL	GTKSEC
	SHLD	MAXSEC
	XCHG
	SHLD	MAXTRK
	POP	H
	POP	PSW
	MOV	M,A
	RET

;***********************************
;
;  DUTIL Command Table
;
;***********************************
CMDTBL:
	DB	':'
	DW	MAC
;
	DB	'@'
	DW	PCMD
;
	DB	'+'	
	DW	 PLUS
;
	DB	'-'
	DW	MINUS
;
	DB	'='
	DW	SEARCH
;
	DB	'<'
	DW	SAVE
;
	DB	'>'
	DW	RESTOR
;
	DB	'#'
	DW	STATS
;
	DB	'?'
	DW	HELP
;
	DB	'/'
	DW	REPEAT
;
	DB	'!'
	DW	UWAIT
;
	DB	'A'
	DW	DUMP
;
	DB	'C'
	DW	CHG
;
	DB	'D'
	DW	DUMP
;
	DB	'F'
	DW	POSFIL
;
	DB	'G'
	DW	POS
;
	DB	'H'
	DW	DUMP
;
	DB	'L'
	DW	LOGIN
;
	DB	'M'
	DW	MAP
;
	DB	'N'
	DW	NEWDSK
;
	DB	'P'
	DW	PRNTFF
;
	DB	'Q'
	DW	QUIET
;
	DB	'R'
	DW	DOREAD
;
	DB	'S'
	DW	POS
;
	DB	'T'
	DW	POS
;
	DB	'U'	;******CP/M 2.x ONLY******
	DW	USER
;
	DB	'V'
	DW	VIEW
;
	DB	'W'
	DW	DORITE
;
	DB	'X'
	DW	EXIT
;
	DB	'Z'
	DW	SLEEP
;
	DB	0	; End of Table
;*************************************

;
;Temporary storage area
;
	DS	100	;50-ELT STACK
DUTSTK:
	DS	2	;OLD CP/M STACK POINTER; TOP OF DUTIL STACK
BUFAD:
	DS	2	;FORCES INITIAL READ
HEXAD:
	DS	2	;TO RE-FETCH A VALUE
TOGO:
	DS	2	;REPEAT COUNT (FFFF=CONT)
TWOUP:
	DS	1
UNUM:
	DS	1	;NUMBER OF CURRENT USER
ONLY1:
	DS	1	;FLAG TO PRINT ONLY 1 MAP ENTRY (0=NO)
MFPTR:
	DS	2	;MULTI FILE PTR FOR GETGRP
PAGFLG:
	DS	1	;LINE COUNTER FOR PAGING
PFLAG:
	DS	1	;1=PRINT
GROUP:
	DS	2	;GROUP NUMBER
GRPDIS:
	DS	1	;DISPLACEMENT INTO GROUP
SAVEFL:
	DS	1	;SAVE FLAG
CURTRK:
	DS	2	;CURRENT TRACK NUMBER
CURSEC:
	DS	2	;CURRENT SECTOR NUMBER
PHYSEC:
	DS	2	;CURRENT PHYSICAL SECTOR NUMBER
TABCOL:
	DS	1	;TAB COLUMN
GLFLAG:
	DS	1	;GROUP LOADED FLAG; 0=NO
CPYFCT:
	DS	1	;GROUP COPY FUNCTION; 0=READ, 0FFH=WRITE
FILECT:
	DS	2	;FILE COUNT
DIRPOS:
	DS	1	;POSITION IN DIRECTORY
FINDFL:
	DS	1	;1=MUST POSITION AFTER FIND
FTSW:
	DS	1	;SEARCH W/O INCREMENT
NOTPOS:
	DS	1	;INITIALLY NOT POSITIONED
WRFLG:
	DS	1	;MAY NOT WRITE UNTIL '+', '-',
;			 OR 'G' COMMAND
TGRP:
	DS	2	;TEMPORARY GROUP FLAG
QFLAG:
	DS	1	;QUIET? (0=NO)
FIRST0:
	DS	1	;SETS TO 0 IF FIRST SEC # IS 0
DRIVE:
	DS	1	;DRIVE NUMBER
MAXTRK:
	DS	2	;MAX TRACK NUMBER
MAXSEC:
	DS	2	;MAX SECTOR NUMBER
VER2FL:
	DS	1	;CP/M VERSION 2.X FLAG
SECTBL:
	DS	2	;POINTER TO SECTOR SKEW TABLE
;
IHFLG:
	DS	1	;0=NOT AT INITIAL HELP, 0FFH=AT INITIAL HELP
DUPFLG:
	DS	1	;SPACE OR STAR TO INDICATE MULTIPLE USERS
BACK:
	DS	2	;TO BACK UP IN "CA0-7F,X"
DUMTYP:
	DS	1
;
;The disk parameter block
;is moved here from CP/M
;
DPB	EQU	$	;DISK PARAMETER BLOCK (COPY)
SPT:
	DS	2
BSH:
	DS	1
BLM:
	DS	1
EXM:
	DS	1
DSM:
	DS	2
DRM:
	DS	2
AL0:
	DS	1
AL1:
	DS	1
CKS:
	DS	2
SYSTRK:
	DS	2
;
;End of disk parameter block
;
SAVBUF:
	DS	128+2+2
;
;Set INBUF to a Page Boundary
;
	ORG	$/100H*100H+100H-2
	DB	126	; SIZE OF BUFFER FOR CP/M
	DS	1
INBUF:
	DS	400H	;EXTRA SPACE FOR MACRO EXPANSION
PINBUF:
	DS	400H	;PREVIOUS CONTENTS OF INPUT BUFFER
CTEMP:
	DS	400H	;BUILD NEW COMMAND LINE BUFFER
CTEMPX	EQU	$	;END OF CTEMP
;
;Directory read in here; also loaded group area and Macros
;
MTABL:	DS	100H*10			;10 PAGES FOR 10 MACROS
GBUFF	EQU	$
DIRECT	EQU	$
;
	END
