; PROGRAM:  FIND
; AUTHOR:  RICHARD CONN
; VERSION:  1.1
; DATE:  25 July 83
; PREVIOUS VERSIONS: 1.0 (24 JULY 83)

VERS	EQU	11		;version number

;
;	FIND searches through all of the known disks for one or more
; files matching the passed file specification.  AFNs (Ambiguous File Names)
; are permitted.  FIND is invoked by the following command line:
;		FIND afn,afn,afn,... o
; where "afn" refers to the file sought and "o" is none or more of:
;		S - Include System Files
;	FIND may be installed by GENINS to restrict the disks and user
; areas searched.
;

;
; System equates:
;
BOOT	EQU	0000H		;CP/M WARM BOOT JUMP VECTOR
BDOS	EQU	BOOT+05H	;CP/M BDOS CALL JUMP VECTOR
TBUFF	EQU	BOOT+80H	;DISK I/O BUFFER
FCB	EQU	BOOT+5CH	;DEFAULT FILE CONTROL BLOCK
FCB2	EQU	BOOT+6CH	;SECONDARY FILE CONTROL AREA
CR	EQU	'M'-'@'		;CTL-M FOR CARRIAGE RETURN
LF	EQU	'J'-'@'		;CTL-J FOR LINE FEED
CTRLC	EQU	'C'-'@'		;ABORT
CTRLS	EQU	'S'-'@'		;PAUSE
ESIZE	EQU	12		;12 BYTES/DIR ENTRY

;
;	This program is Copyright (c) 1983 by Richard Conn
;	All Rights Reserved
;
;	ZCPR2 and its utilities, including this one, are released
; to the public domain.  Anyone who wishes to USE them may do so with
; no strings attached.  The author assumes no responsibility or
; liability for the use of ZCPR2 and its utilities.
;
;	The author, Richard Conn, has sole rights to this program.
; ZCPR2 and its utilities may not be sold without the express,
; written permission of the author.
;

;
;  Branch to Start of Program
;
	ORG	BOOT+100H
	JMP	START

;
;******************************************************************
;
;  SINSFORM -- ZCPR2 Utility Standard General Purpose Initialization Format
;
;	This data block precisely defines the data format for
; initial features of a ZCPR2 system which are required for proper
; initialization of the ZCPR2-Specific Routines in SYSLIB.
;

;
;  EXTERNAL PATH DATA
;
EPAVAIL:
	DB	0FFH	; IS EXTERNAL PATH AVAILABLE? (0=NO, 0FFH=YES)
EPADR:
	DW	40H	; ADDRESS OF EXTERNAL PATH IF AVAILABLE

;
;  INTERNAL PATH DATA
;
INTPATH:
	DB	0,0	; DISK, USER FOR FIRST PATH ELEMENT
			; DISK = 1 FOR A, '$' FOR CURRENT
			; USER = NUMBER, '$' FOR CURRENT
	DB	0,0
	DB	0,0
	DB	0,0
	DB	0,0
	DB	0,0
	DB	0,0
	DB	0,0	; DISK, USER FOR 8TH PATH ELEMENT
	DB	0	; END OF PATH

;
;  MULTIPLE COMMAND LINE BUFFER DATA
;
MCAVAIL:
	DB	0FFH	; IS MULTIPLE COMMAND LINE BUFFER AVAILABLE?
MCADR:
	DW	0FF00H	; ADDRESS OF MULTIPLE COMMAND LINE BUFFER IF AVAILABLE

;
;  DISK/USER LIMITS
;
MDISK:
	DB	4	; MAXIMUM NUMBER OF DISKS
MUSER:
	DB	31	; MAXIMUM USER NUMBER

;
;  FLAGS TO PERMIT LOG IN FOR DIFFERENT USER AREA OR DISK
;
DOK:
	DB	0FFH	; ALLOW DISK CHANGE? (0=NO, 0FFH=YES)
UOK:
	DB	0FFH	; ALLOW USER CHANGE? (0=NO, 0FFH=YES)

;
;  PRIVILEGED USER DATA
;
PUSER:
	DB	10	; BEGINNING OF PRIVILEGED USER AREAS
PPASS:
	DB	'chdir',0	; PASSWORD FOR MOVING INTO PRIV USER AREAS
	DS	41-($-PPASS)	; 40 CHARS MAX IN BUFFER + 1 for ending NULL

;
;  CURRENT USER/DISK INDICATOR
;
CINDIC:
	DB	'$'	; USUAL VALUE (FOR PATH EXPRESSIONS)

;
;  DMA ADDRESS FOR DISK TRANSFERS
;
DMADR:
	DW	80H	; TBUFF AREA

;
;  NAMED DIRECTORY INFORMATION
;
NDRADR:
	DW	00000H	; ADDRESS OF MEMORY-RESIDENT NAMED DIRECTORY
NDNAMES:
	DB	64	; MAX NUMBER OF DIRECTORY NAMES
DNFILE:
	DB	'NAMES   '	; NAME OF DISK NAME FILE
	DB	'DIR'		; TYPE OF DISK NAME FILE

;
;  REQUIREMENTS FLAGS
;
EPREQD:
	DB	000H	; EXTERNAL PATH?
MCREQD:
	DB	000H	; MULTIPLE COMMAND LINE?
MXREQD:
	DB	0FFH	; MAX USER/DISK?
UDREQD:
	DB	000H	; ALLOW USER/DISK CHANGE?
PUREQD:
	DB	000H	; PRIVILEGED USER?
CDREQD:
	DB	000H	; CURRENT INDIC AND DMA?
NDREQD:
	DB	000H	; NAMED DIRECTORIES?
Z2CLASS:
	DB	0	; CLASS 0
	DB	'ZCPR2'
	DS	10	; RESERVED

;
;  END OF SINSFORM -- STANDARD DEFAULT PARAMETER DATA
;
;******************************************************************
;

;
;  Start of Program
;
START:
;
	LXI	H,0		;SAVE STACK PTR
	DAD	SP
	SHLD	STACK
	LXI	SP,STACK	;SET STACK POINTER
	CALL	GTBIOS		;GET BIOS JUMP TABLE
	CALL 	HELLO		;SIGN ON MESSAGE
	CALL	HELPCHK		;CHECK FOR AND PRINT HELP MESSAGE
	CALL	OPTCHK		;BUILD FILE NAME TABLE AND PROCESS OPTIONS
	CALL	CRLF		;NEW LINE
	CALL	FIND		;DO THE SEARCHES
	CALL	BYE		;SIGN OFF MESSAGE
RETURN:
	LHLD	STACK		;QUIET RETURN
	SPHL
	RET
;
; ** Main Routines **
;

;
; SAY WHO WE ARE
;
HELLO:
	CALL	PRINT
	DB	'FIND, Version '
	DB	(VERS/10)+'0','.',(VERS MOD 10)+'0'
	DB	0
	RET
;
;  CHECK FOR HELP REQUEST
;
HELPCHK:
	LDA	FCB+1		;GET 1ST BYTE OF FILENAME
	CPI	'/'		;HELP?
	JZ	HCK1
	CPI	' '		;MAKE SURE IT IS NON-BLANK
	RNZ			;OK - KEEP GOING
;	
; IF NO FILE NAME IS SPECIFIED, ABORT WITH NOTICE
;
HCK1:
	CALL 	PRINT
	DB	CR,LF
	DB	CR,LF,'	FIND is used to search through all logged-in disks'
	DB	CR,LF,'for any files matching an ambiguous file name (wild'
	DB	CR,LF,'cards ar permitted).  It is invoked as follows:'
	DB	CR,LF,'		FIND afn,afn,afn,... o'
	DB	CR,LF,'where "afn" is the ambigous file name to search for'
	DB	CR,LF,'(as many afns as desired may be specified), and'
	DB	CR,LF,'"o" is none or more of the following options:'
	DB	CR,LF,'		S - Include System Files'
	DB	CR,LF,'Drive prefixes (DU:) are ignored.'
	DB	CR,LF
	DB	0
	JMP	RETURN
;
; CHECKS FOR S OPTION IN COMMAND LINE AND EXTRACTS FILE NAMES INTO TABLE
;
OPTCHK:
	XRA	A	;TURN OFF FLAGS
	STA	SYSTEM	;NO SYSTEM FILES
	STA	FFLAG	;NO FILES FOUND
	STA	ECOUNT	;NO ENTRIES
	STA	FNCOUNT	;NO FILE NAMES
	LXI	H,TBUFF	;SCAN THRU TBUFF, BUILDING A FILE NAME TABLE
	MOV	A,M	;GET CHAR COUNT
	INX	H	;PT TO FIRST CHAR
	PUSH	H	;SAVE PTR
	ADD	L	;PT TO AFTER LAST CHAR
	MOV	L,A
	MVI	M,0	;STORE ENDING ZERO
	POP	H	;GET PTR TO FIRST CHAR
	CALL	SBLANK	;SKIP BLANKS
	LXI	D,FNTAB	;PT TO TABLE
FNLOOP:
	PUSH	D	;SAVE TABLE PTR
	CALL	GETFN	;EXTRACT FILE NAME
	POP	D
	PUSH	H
	LXI	H,11	;PT TO NEXT TABLE ENTRY
	DAD	D
	XCHG
	POP	H
	LDA	FNCOUNT	;INCREMENT COUNT
	INR	A
	STA	FNCOUNT
	MOV	A,M	;GET TERMINATING CHAR
	INX	H	;PT TO NEXT
	CPI	','	;ANOTHER FOLLOWS?
	JZ	FNLOOP
	DCX	H	;POINT BACK TO DELIM
	CALL	SBLANK	;SKIP TO NON-BLANK
OPTCK1:
	MOV	A,M	;GET OPTION
	CALL	DELCHK	;DONE IF DELIM
	RZ
	CPI	'S'	;SYSTEM?
	JZ	OPTCKS
	CALL	PRINT
	DB	CR,LF,'Invalid Option -- ',0
	MOV	A,M
	CALL	TYPE
	JMP	HCK1
OPTCKS:
	MVI	A,0FFH	;SET FLAG
	STA	SYSTEM
	INX	H
	JMP	OPTCK1
GETFN:
	PUSH	D	;FILL TARGET FCB
	MVI	B,11	;11 BYTES
	MVI	A,' '	;SPACE FILL
GETFN0:
	STAX	D	;PUT SPACE
	INX	D
	DCR	B
	JNZ	GETFN0
	POP	D	;PT TO ENTRY AGAIN
	CALL	SCANCOL	;SCAN FOR COLON
	MVI	B,8	;8 CHARS MAX
	CALL	GETFN1	;GET AND FILL ENTRY
	MOV	A,M	;GET CHAR
	CPI	'.'	;DELIM?
	RNZ		;DONE
	INX	H	;PT TO AFTER PERIOD
	MVI	B,3	;3 CHARS MAX AND DO IT AGAIN
GETFN1:
	MOV	A,M	;GET CHAR
	CPI	'.'	;END OF FIELD?
	JZ	GETFN3
	CALL	DELCHK	;CHECK DELIMITER
	RZ
	CPI	'*'	;WILD?
	JZ	GETFNQ
	STAX	D	;STORE CHAR
	INX	H	;PT TO NEXT
	INX	D
	DCR	B	;COUNT DOWN
	JNZ	GETFN1
GETFN2:
	MOV	A,M	;FLUSH CHARS TO DELIM
	CALL	DELCHK	;CHECK FOR DELIMITER
	RZ
	INX	H	;PT TO NEXT
	JMP	GETFN2
GETFN3:
	INX	D	;PT TO AFTER FIELD
	DCR	B	;COUNT DOWN
	JNZ	GETFN3
	RET
GETFNQ:
	MVI	A,'?'	;FILL WITH QUESTION MARKS
	STAX	D
	INX	D
	DCR	B
	JNZ	GETFNQ
	JMP	GETFN2	;SKIP TO DELIM
DELCHK:
	ORA	A	;END OF LINE?
	RZ
	CPI	'.'	;END OF FIELD?
	RZ
	CPI	','	;END OF ENTRY?
	RZ
	CPI	' '
	RET
SBLANK:
	MOV	A,M	;SKIP TO NON-BLANK
	CPI	' '
	RNZ
	INX	H
	JMP	SBLANK
SCANCOL:
	PUSH	D	;SAVE TABLE PTR
	PUSH	H	;SAVE PTR
SCOL1:
	MOV	A,M	;GET CHAR
	INX	H	;PT TO NEXT
	CPI	':'	;COLON?
	JZ	SCOLX
	CALL	DELCHK	;CHECK FOR DELIMITER
	JNZ	SCOL1
SCOL2:
	POP	H	;RESTORE
	POP	D
	RET
SCOLX:
	XCHG		;DE PTS TO AFTER COLON
	POP	H	;GET OLD PTR
	XCHG		;REPLACE IT
	POP	D	;GET TABLE PTR
	RET
;
; LOOK THROUGH DIRECTORY
;
FIND:
	XRA	A		;SELECT FIRST DISK
	STA	FCB
	CALL	CPMCHK		;GET INFO THE FIRST TIME
FIND1:
	RZ			;ABORT IF ERROR
FIND2:
	CALL	NXTSEC		;GET A DIRECTORY SECTOR
	JZ	FIND3		;RETURNS ZERO FLAG IF NO MORE
	CALL	CHKENT		;CHECK IT OUT
	JMP	FIND2		;KEEP IT UP TILL DONE
FIND3:
	CALL	DIRALPHA	;SORT ENTRIES
	CALL	PRFILES		;PRINT SORTED ENTRIES
	LDA	FCB		;NEXT DISK
	INR	A
	STA	FCB
	CALL	NXTDISK		;SELECT NEXT DISK
	JMP	FIND1

;
; SIGN OFF
;
BYE:
	MVI	C,13		;RESET SYSTEM
	CALL	BDOS
	LDA	FFLAG		;GET FILE FOUND FLAG
	ORA	A		;NO FILES FOUND?
	JNZ	RETURN
	CALL	PRINT
	DB	CR,LF,'NO Files Found',0
	JMP	RETURN
;
; CHECKS THE CURRENT 4 DIRECTORY ENTRIES AGAINST ARGUMENT
; IF MATCH, REWRITES SECTOR WITH REACTIVATED 1ST BYTES
;
CHKENT:
	MVI	B,4		;NUMBER OF ENTRIES PER SECTOR
	LXI 	H,TBUFF		;BEGINNING OF BUFFER
CKLUP:
	PUSH	B
	MOV	A,M
	CPI	0E5H		;CHECK FOR UNUSED
	JZ	CKINC
	XRA	A		;A=0
	STA	CLPFLG		;SET FLAG FOR NO ENTRIES FOUND
	LDA	FNCOUNT		;GET NUMBER OF FILE NAMES TO LOOK THRU
	MOV	B,A		;... IN B
	LXI	D,FNTAB		;PT TO TABLE
CKLUP1:
	PUSH	B		;SAVE COUNT
	PUSH 	H		;SAVE BEGINNING ADDRESS
	PUSH	D
	CALL	COMPAR		;COMPARE WITH ARGUMENT AND SAVE IF MATCH
	POP	D
	LXI	H,11		;PT TO NEXT ENTRY
	DAD	D
	XCHG
	POP	H
	POP	B
	DCR	B		;COUNT DOWN
	JNZ	CKLUP1
CKINC:
	POP	B
	LXI	D,32		;LENGTH OF ENTRY
	DAD	D
	DCR	B
	JNZ	CKLUP
	LHLD	DIRMAX
	DCX	H		;REDUCE SECTORS LEFT
	SHLD	DIRMAX
	LHLD	SECTOR		;POINT TO NEXT SECTOR
	INX	H
	SHLD	SECTOR
	XCHG
	LHLD	MAXSEC		;REACHED LIMIT?
	INX	H		;ONE MORE
	MOV	A,H		;CHECK HIGH
	CMP	D
	RNZ
	MOV	A,L		;CHECK LOW
	CMP	E
	RNZ
	LHLD	TRACK		;NEXT TRACK
	INX	H
	SHLD	TRACK
	LXI	H,1		;FIRST SECTOR OF NEXT TRACK
	SHLD	SECTOR
	RET
;
; COMPARE 11 BYTES OF DIRECTORY ENTRY AGAINST ARGUMENT; RNZ IF NOT MATCHED
;  DE PTS TO TABLE ENTRY TO COMPARE TO
;
COMPAR:
	LDA	CLPFLG		;GET FOUND FLAG
	ORA	A		;0=NO
	RNZ
	SHLD	TEMP		;Hold pointer in case of match
	INX	H
	XCHG
	MVI	B,11
CMPR1:
	LDAX	D		;GET DIRECTORY ENTRY CHARACTER
	ANI	7FH		;STRIP ANY FLAGS
	CMP	M
	JZ	CMPR2
	MOV	A,M
	CPI	'?'
	RNZ
CMPR2:
	INX	D
	INX	H		;BUMP TO NEXT CHARACTER
	DCR	B
	JNZ	CMPR1		;LOOP FOR 11 CHARACTERS
	PUSH	D		;SAVE ENTRY PTR
	LDAX	D		;GET EXTENT IN B
	MOV	B,A
	LDA	EXTENT		;GET EXTENT MASK
	CMP	B
	POP	D		;GET ENTRY PTR
	JC	CMPR4		;NO MATCH
	LDA	SYSTEM		;INCLUDE SYSTEM FILES?
	ORA	A		;0=NO
	JNZ	CMPR3
	DCX	D		;BACK UP 2 BYTES
	DCX	D
	LDAX	D		;GET T2
	ANI	80H		;CHECK FOR SYS
	RNZ
CMPR3:
	LHLD	TEMP		;CHECK FOR USER LIMIT
	LDA	MUSER		;GET MAX USER
	CMP	M		;BEYOND MAX?
	JC	CMPR4
	LHLD	FCOUNT		;INCREMENT COUNT
	INX	H
	SHLD	FCOUNT
	LHLD	DSTART		;GET PTR TO NEXT ENTRY
	XCHG
	LHLD	TEMP
	MVI	B,ESIZE		;COPY ENTRY
	CALL	MOVE
	XCHG
	SHLD	DSTART		;PTR TO NEXT ENTRY
	XCHG
	LHLD	BDOS+1		;CHECK FOR MEMORY OVERFLOW
	MOV	A,H
	SUI	10		;BELOW CCP
	CMP	D		;PT BEYOND LIMIT?
	JC	MOVFL
	MVI	A,0FFH		;SET FOUND FLAG
	STA	FFLAG
	XRA	A
	RET			;RETURNS 'ZERO' FLAG SET FOR MATCH
CMPR4:
	MVI	A,0FFH		;NO MATCH
	ORA	A
	RET
MOVFL:
	CALL	PRINT
	DB	CR,LF,'ABORT -- Not Enough Memory for Buffers',0
	JMP	RETURN
;
; CHECK FOR CP/M VERSION AND SET THINGS
;
CPMCHK:
	MVI	C,12		;VERSION NUMBER REQUEST
	CALL 	BDOS
	CPI	20H		;EARLIER THAN 2.2?
	JC	VERERR
;
;  ADVANCE TO NEXT DISK
;
NXTDISK:
	LXI	B,TBUFF		;SET DMA ADDRESS
	CALL	SETDMA
	LDA	MDISK		;GET MAX DISK
	MOV	B,A
	LDA	FCB
	CMP	B
	RZ			;ABORT IF BEYOND MAX
	MOV	C,A
	MVI	B,0
	CALL	SELDSK		;MAKE SURE DRIVE IS
	MOV	A,H		;  SELECTED
	ORA	L
	RZ			;ERROR RETURN
	SHLD	DPH		;SAVE THE ADDRESS
	LXI	D,10		;PT TO DPB
	DAD	D
	MOV	E,M		;GET DPB ADDRESS IN HL
	INX	H
	MOV	D,M
	XCHG
	MOV	E,M		;NUMBER OF SECTORS/TRACK
	INX	H		;AS 2-BYTE QUANTITY IN DE
	MOV	D,M
	INX	H
	XCHG
	SHLD	MAXSEC		;SET MAX SECTORS/TRACK
	XCHG
	INX	H
	INX	H
	MOV	A,M		;GET EXM
	STA	EXTENT
	INX	H		;PT TO DRM
	INX	H
	INX	H
	MOV	E,M		;GET NUMBER OF
	INX	H		;  DIRECTORY ENTRIES
	MOV	D,M
	XCHG
	INX	H		;ACCOUNT FOR - 1
	SHLD	DSTART		;SAVE NUMBER OF DIRECTORY ENTRIES
	CALL	SHFHL2		;SHIFT 'HL' RIGHT 2
	SHLD	DIRMAX		;SAVE NUMBER DIRECTORY SECTORS
	LXI	H,5		;NOW POINT TO SYSTEM
	DAD	D		;  TRACK OFFSET
	MOV	A,M		;PICK UP NUMBER OF
	INX	H
	MOV	H,M
	MOV	L,A
	SHLD	TRACK
	LXI	H,1		;SET SECTOR
	SHLD	SECTOR
	LDA	ECOUNT		;LAST NEW LINE?
	ANI	3
	CNZ	CRLF
	CALL	PRINT
	DB	'Disk ',0
	LDA	FCB
	ADI	'A'
	CALL	TYPE
	CALL	PRINT
	DB	' --',CR,LF,0
	LXI	H,SCRATCH	;PT TO SCRATCH AREA
	SHLD	ORDER		;ADDRESS OF ORDER TABLE
	XCHG
	LHLD	DSTART		;GET NUMBER OF DIRECTORY ENTRIES
	DAD	H		;DOUBLE FOR NUMBER OF BYTES IN ORDER TABLE
	DAD	D		;PT TO FIRST BYTE OF DIRBUF
	SHLD	DIRBUF		;SET PTR
	SHLD	DSTART		;SET LOOP PTR
	LXI	H,0		;SET FILE COUNT
	SHLD	FCOUNT
	XRA	A		;SET COUNT
	STA	ECOUNT
	CMA			;FLIP
	ORA	A		;OK TO CONTINUE
	RET
VERERR:
	CALL	PRINT
	DB	CR,LF,'ABORT -- NOT CP/M 2.x or Later',0
	JMP	RETURN
;
; GET BIOS JUMPS VECTORS FOR EASY REFERENCE
;
GTBIOS:
	LHLD	BOOT+1		;POINTS TO BIOS JUMP TABLE+3
	LXI	D,WBOOT		;WHERE WE WILL KEEP A COPY
	MVI	B,16*3		;MOVE 48 BYTES AND FALL THRU TO MOVE
;
; GENERAL PURPOSE MOVE ROUTINE
; FROM 'HL' TO 'DE' FOR COUNT OF 8
;
MOVE:
	MOV	A,M		;GET A BYTE
	STAX	D		;PUT A BYTE
	INX	D		;INCREMENT TO NEXT
	INX	H
	DCR	B		;COUNT DOWN
	JNZ	MOVE
	RET
;
; READS NEXT SECTOR (GROUP OF FOUR DIRECTORY ENTRIES)
; RETURNS WITH ZERO FLAG SET IF NO MORE
;
NXTSEC:
	LHLD	DIRMAX		;SEE IF MORE SECTORS
	MOV	A,H
	ORA	L
	RZ			;RETURNS ZERO FLAG IF NO MORE
	LHLD	TRACK		;SET TRACK
	MOV	B,H
	MOV	C,L
	CALL	SETTRK
	LHLD	SECTOR		;SET SECTOR
	MOV	B,H
	MOV	C,L
	CALL	TRNSLT
	CALL	SETSEC
	CALL	READ		;READ A SECTOR
	ANI	1		;REVERSE SENSE OF ERROR FLAG
	XRI	1		;RETURNS WITH ZERO FLAG SET
	RET			;IF BAD READ
;
; PRINT FILES IN DIRBUF
;
PRFILES:
	LHLD	FCOUNT		;GET COUNT
	MOV	A,H		;ANY?
	ORA	L
	RZ
	MOV	B,H		;COUNT IN BC
	MOV	C,L
	LHLD	DIRBUF		;PT TO FIRST ONE
PRFLOOP:
	PUSH	B		;SAVE COUNT
	PUSH	H		;SAVE PTR
	CALL	PRINTFCB	;PRINT FCB
	POP	H		;GET REGS BACK
	POP	B
	LXI	D,ESIZE		;PT TO NEXT
	DAD	D
	DCX	B		;COUNT DOWN
	MOV	A,B
	ORA	C
	JNZ	PRFLOOP
	MVI	A,0FFH		;SET OK
	ORA	A
	RET
;
; FCB PRINTING ROUTINE
;
PRINTFCB:
	MVI	A,' '		;SPACE IN
	CALL	TYPE
	CALL	TYPE
	MOV	A,M		;GET USER NUMBER
	CALL	DECOUT		;PRINT IT
	MVI	A,':'
	CALL	TYPE
	INX	H
PR0:
	MVI	B,8
	CALL	PR1
	MVI	A,'.'
	CALL	TYPE
	MVI	B,3
	CALL	PR1
	LDA	ECOUNT		;INCREMENT COUNT
	INR	A
	STA	ECOUNT
	ANI	3		;EVERY 4
	CZ	CRLF		;NEW LINE
	RET
PR1:
	MOV	A,M
	ANI	7FH
	CALL	TYPE
	INX	H
	DCR	B
	JNZ	PR1	
	RET
;
;  PRINT VALUE IN A AS DECIMAL
;
DECOUT:
	PUSH	B	;SAVE REG
	MVI	C,'0'	;SET VALUE
DEC1:
	SUI	10	;DIVIDE BY 10
	JC	DEC2
	INR	C
	JMP	DEC1
DEC2:
	ADI	10	;ADD 10 BACK IN
	MOV	B,A	;SAVE VALUE
	MOV	A,C	;CHECK DIGITS
	CPI	'0'
	JNZ	DEC3
	MVI	A,' '	;LEADING SPACE
DEC3:
	CALL	TYPE	;DON'T TYPE LEADING ZERO
	MOV	A,B	;GET ONES
	ADI	'0'	;CONVERT TO ASCII
	CALL	TYPE
	POP	B	;GET REG
	RET
;
;  PRINT CHAR IN A ON TERMINAL; AFFECT NO REGS
;
TYPE:
	PUSH	H	;SAVE REGS
	PUSH	D
	PUSH	B
	PUSH	PSW
	CALL	CONST	;CHAR PENDING?
	ORA	A	;0=NONE
	JZ	TYPE1
	CALL	CONIN	;GET CHAR
	ANI	7FH	;MASK
	CPI	CTRLC	;ABORT?
	JZ	RETURN
	CPI	CTRLS	;PAUSE?
	JNZ	TYPE1
	CALL	CONIN	;GET ANOTHER CHAR
	CPI	CTRLC
	JZ	RETURN
TYPE1:
	POP	PSW	;GET CHAR TO SEND
	PUSH	PSW
	MOV	C,A	;CHAR IN C
	CALL	CONOUT
	POP	PSW	;RESTORE REGS
	POP	B
	POP	D
	POP	H
	RET
;
;  NEW LINE
;
CRLF:
	MVI	A,0DH		;NEW LINE
	CALL	TYPE
	MVI	A,0AH
	JMP	TYPE
;
;  PRINT MESSAGE AT RETURN ADDRESS
;
PRINT:
	XTHL			;SAVE HL, GET PTR
PRINTL:
	MOV	A,M		;GET BYTE
	INX	H		;PT TO NEXT
	ANI	7FH		;MASK
	JZ	PRINTD		;DONE IF ZERO
	CALL	TYPE
	JMP	PRINTL
PRINTD:
	XTHL
	RET
;
; SHIFT REGS 'HL' RIGHT 2 BITS LOGICAL
;
SHFHL2:
	CALL	SHFHL		;ROTATE RIGHT 1 BIT AND FALL THRU
SHFHL:
	XRA	A		;CLEAR CARRY
	MOV	A,H
	RAR			;SHIFTED BIT IN CARRY
	MOV	H,A
	MOV	A,L
	RAR
	MOV	L,A
	RET
;
; TRANSLATE REG 'BC' FROM LOGICAL TO PHYSICAL SECTOR NUMBER
;
TRNSLT:
	LHLD	DPH		;GET PTR TO DPH
	MOV	E,M		;GET ADDRESS OF XLT
	INX	H
	MOV	D,M
	CALL	SECTRAN		;USE BIOS ROUTINE
	MOV	C,L		;RETURN VALUE IN BC
	MOV	B,H
	RET

;
;  DIRALPHA -- ALPHABETIZES DIRECTORY PTED TO BY HL; BC CONTAINS
;	THE NUMBER OF FILES IN THE DIRECTORY
;
DIRALPHA:
	LHLD	FCOUNT	; GET FILE COUNT
	MOV	A,H	; ANY FILES?
	ORA	L
	RZ
	SHLD	N	; SET "N"
	MOV	B,H	; BC=COUNT
	MOV	C,L
	LHLD	DIRBUF	; PT TO DIRECTORY
;
;  SHELL SORT --
;    THIS SORT ROUTINE IS ADAPTED FROM "SOFTWARE TOOLS"
;    BY KERNIGAN AND PLAUGHER, PAGE 106.  COPYRIGHT, 1976, ADDISON-WESLEY.
;  ON ENTRY, BC=NUMBER OF ENTRIES AND HL=ADDRESS OF FIRST ENTRY
;
SORT:
	XCHG		; POINTER TO DIRECTORY IN DE
	LHLD	ORDER	; PT TO ORDER TABLE
;
;  SET UP ORDER TABLE; HL PTS TO NEXT ENTRY IN ORDER TABLE, DE PTS TO NEXT
;    ENTRY IN DIRECTORY, BC = NUMBER OF ELEMENTS REMAINING
;
SORT1:
	MOV	M,E	; STORE LOW-ORDER ADDRESS
	INX	H	; PT TO NEXT ORDER BYTE
	MOV	M,D	; STORE HIGH-ORDER ADDRESS
	INX	H	; PT TO NEXT ORDER ENTRY
	PUSH	H	; SAVE PTR
	LXI	H,ESIZE	; HL=NUMBER OF BYTES/ENTRY
	DAD	D	; PT TO NEXT DIR1 ENTRY
	XCHG		; DE PTS TO NEXT ENTRY
	POP	H	; GET PTR TO ORDER TABLE
	DCX	B	; COUNT DOWN
	MOV	A,B	; DONE?
	ORA	C
	JNZ	SORT1
;
;  THIS IS THE MAIN SORT LOOP FOR THE SHELL SORT IN "SOFTWARE TOOLS" BY K&P
;

;
;  SHELL SORT FROM "SOFTWARE TOOLS" BY KERNINGHAN AND PLAUGER
;
	LHLD	N	; NUMBER OF ITEMS TO SORT
	SHLD	GAP	; SET INITIAL GAP TO N FOR FIRST DIVISION BY 2

;  FOR (GAP = N/2; GAP > 0; GAP = GAP/2)
SRTL0:
	ORA	A	; CLEAR CARRY
	LHLD	GAP	; GET PREVIOUS GAP
	MOV	A,H	; ROTATE RIGHT TO DIVIDE BY 2
	RAR
	MOV	H,A
	MOV	A,L
	RAR
	MOV	L,A

;  TEST FOR ZERO
	ORA	H
	JZ	SDONE	; DONE WITH SORT IF GAP = 0

	SHLD	GAP	; SET VALUE OF GAP
	SHLD	I	; SET I=GAP FOR FOLLOWING LOOP

;  FOR (I = GAP + 1; I <= N; I = I + 1)
SRTL1:
	LHLD	I	; ADD 1 TO I
	INX	H
	SHLD	I

;  TEST FOR I <= N
	XCHG		; I IS IN DE
	LHLD	N	; GET N
	MOV	A,L	; COMPARE BY SUBTRACTION
	SUB	E
	MOV	A,H
	SBB	D	; CARRY SET MEANS I > N
	JC	SRTL0	; DON'T DO FOR LOOP IF I > N

	LHLD	I	; SET J = I INITIALLY FOR FIRST SUBTRACTION OF GAP
	SHLD	J

;  FOR (J = I - GAP; J > 0; J = J - GAP)
SRTL2:
	LHLD	GAP	; GET GAP
	XCHG		; ... IN DE
	LHLD	J	; GET J
	MOV	A,L	; COMPUTE J - GAP
	SUB	E
	MOV	L,A
	MOV	A,H
	SBB	D
	MOV	H,A
	SHLD	J	; J = J - GAP
	JC	SRTL1	; IF CARRY FROM SUBTRACTIONS, J < 0 AND ABORT
	MOV	A,H	; J=0?
	ORA	L
	JZ	SRTL1	; IF ZERO, J=0 AND ABORT

;  SET JG = J + GAP
	XCHG		; J IN DE
	LHLD	GAP	; GET GAP
	DAD	D	; J + GAP
	SHLD	JG	; JG = J + GAP

;  IF (V(J) <= V(JG))
	CALL	ICOMPARE	; J IN DE, JG IN HL

;  ... THEN BREAK
	JC	SRTL1

;  ... ELSE EXCHANGE
	LHLD	J	; SWAP J, JG
	XCHG
	LHLD	JG
	CALL	ISWAP	; J IN DE, JG IN HL

;  END OF INNER-MOST FOR LOOP
	JMP	SRTL2

;
;  SORT IS DONE -- RESTRUCTURE DIR1 IN SORTED ORDER IN PLACE
;
SDONE:
	LHLD	N	; NUMBER OF ENTRIES
	MOV	B,H	; ... IN BC
	MOV	C,L
	LHLD	ORDER	; PTR TO ORDERED POINTER TABLE
	SHLD	PTPTR	; SET PTR PTR
	LHLD	DIRBUF	; PTR TO UNORDERED DIRECTORY
	SHLD	PTDIR	; SET PTR DIR BUFFER

;  FIND PTR TO NEXT DIR1 ENTRY
SRTDN:
	LHLD	PTPTR	; PT TO REMAINING POINTERS
	XCHG		; ... IN DE
	LHLD	PTDIR	; HL PTS TO NEXT DIR ENTRY
	PUSH	B	; SAVE COUNT OF REMAINING ENTRIES

;  FIND PTR TABLE ENTRY
SRTDN1:
	LDAX	D	; GET CURRENT POINTER TABLE ENTRY VALUE
	INX	D	; PT TO HIGH-ORDER POINTER BYTE
	CMP	L	; COMPARE AGAINST DIR1 ADDRESS LOW
	JNZ	SRTDN2	; NOT FOUND YET
	LDAX	D	; LOW-ORDER BYTES MATCH -- GET HIGH-ORDER POINTER BYTE
	CMP	H	; COMPARE AGAINST DIR1 ADDRESS HIGH
	JZ	SRTDN3	; MATCH FOUND
SRTDN2:
	INX	D	; PT TO NEXT PTR TABLE ENTRY
	DCX	B	; COUNT DOWN
	MOV	A,C	; END OF TABLE?
	ORA	B
	JNZ	SRTDN1	; CONTINUE IF NOT

;  FATAL ERROR -- INTERNAL ERROR; POINTER TABLE NOT CONSISTENT
FERR$PTR:
	CALL	PRINT
	DB	0DH,0AH,'DIRALPHA -- Pointer Error',0
	JMP	RETURN

;  FOUND THE POINTER TABLE ENTRY WHICH POINTS TO THE NEXT UNORDERED DIR1 ENTRY
;    MAKE BOTH POINTERS (PTR TO NEXT, PTR TO CURRENT UNORDERED DIR1 ENTRY)
;    POINT TO SAME LOCATION (PTR TO NEXT DIR1 ENTRY TO BE ORDERED)
SRTDN3:
	LHLD	PTPTR	; GET PTR TO NEXT ORDERED ENTRY
	DCX	D	; DE PTS TO LOW-ORDER POINTER ADDRESS
	MOV	A,M	; MAKE PTR TO NEXT UNORDERED DIR1 PT TO BUFFER FOR
	STAX	D	;   DIR1 ENTRY TO BE MOVED TO NEXT UNORDERED DIR1 POS
	INX	H	; PT TO NEXT PTR ADDRESS
	INX	D
	MOV	A,M	; MAKE HIGH POINT SIMILARLY
	STAX	D

;  COPY NEXT UNORDERED DIR1 ENTRY TO HOLD BUFFER
	MVI	B,ESIZE	; B=NUMBER OF BYTES/ENTRY
	LHLD	PTDIR	; PT TO ENTRY
	LXI	D,HOLD	; PT TO HOLD BUFFER
	PUSH	B	; SAVE B=NUMBER OF BYTES/ENTRY
	CALL	MOVE
	POP	B

;  COPY TO-BE-ORDERED DIR1 ENTRY TO NEXT ORDERED DIR1 POSITION
	LHLD	PTPTR	; POINT TO ITS POINTER
	MOV	E,M	; GET LOW-ADDRESS POINTER
	INX	H
	MOV	D,M	; GET HIGH-ADDRESS POINTER
	LHLD	PTDIR	; DESTINATION ADDRESS FOR NEXT ORDERED DIR1 ENTRY
	XCHG		; HL PTS TO ENTRY TO BE MOVED, DE PTS TO DEST
	PUSH	B	; SAVE B=NUMBER OF BYTES/ENTRY
	CALL	MOVE
	POP	B
	XCHG		; HL PTS TO NEXT UNORDERED DIR1 ENTRY
	SHLD	PTDIR	; SET POINTER FOR NEXT LOOP

;  COPY ENTRY IN HOLD BUFFER TO LOC PREVIOUSLY HELD BY LATEST ORDERED ENTRY
	LHLD	PTPTR	; GET PTR TO PTR TO THE DESTINATION
	MOV	E,M	; GET LOW-ADDRESS POINTER
	INX	H
	MOV	D,M	; HIGH-ADDRESS POINTER
	LXI	H,HOLD	; HL PTS TO HOLD BUFFER, DE PTS TO ENTRY DEST
	CALL	MOVE	; B=NUMBER OF BYTES/ENTRY

;  POINT TO NEXT ENTRY IN POINTER TABLE
	LHLD	PTPTR	; POINTER TO CURRENT ENTRY
	INX	H	; SKIP OVER IT
	INX	H
	SHLD	PTPTR

;  COUNT DOWN
	POP	B	; GET COUNTER
	DCX	B	; COUNT DOWN
	MOV	A,C	; DONE?
	ORA	B
	JNZ	SRTDN
	RET		; DONE

;
;  SWAP (Exchange) the pointers in the ORDER table whose indexes are in
;    HL and DE
;
ISWAP:
	PUSH	H		; SAVE HL
	LHLD	ORDER		; ADDRESS OF ORDER TABLE - 2
	MOV	B,H		; ... IN BC
	MOV	C,L
	POP	H
	DCX	H		; ADJUST INDEX TO 0...N-1 FROM 1...N
	DAD	H		; HL PTS TO OFFSET ADDRESS INDICATED BY INDEX
				;   OF ORIGINAL HL (1, 2, ...)
	DAD	B		; HL NOW PTS TO POINTER INVOLVED
	XCHG			; DE NOW PTS TO POINTER INDEXED BY HL
	DCX	H		; ADJUST INDEX TO 0...N-1 FROM 1...N
	DAD	H		; HL PTS TO OFFSET ADDRESS INDICATED BY INDEX
				;   OF ORIGINAL DE (1, 2, ...)
	DAD	B		; HL NOW PTS TO POINTER INVOLVED
	MOV	C,M		; EXCHANGE POINTERS -- GET OLD (DE)
	LDAX	D		; -- GET OLD (HL)
	XCHG			; SWITCH
	MOV	M,C		; PUT NEW (HL)
	STAX	D		; PUT NEW (DE)
	INX	H		; PT TO NEXT BYTE OF POINTER
	INX	D
	MOV	C,M		; GET OLD (HL)
	LDAX	D		; GET OLD (DE)
	XCHG			; SWITCH
	MOV	M,C		; PUT NEW (DE)
	STAX	D		; PUT NEW (HL)
	RET
;
;  ICOMPARE compares the entry pointed to by the pointer pointed to by HL
;    with that pointed to by DE (1st level indirect addressing); on entry,
;    HL and DE contain the numbers of the elements to compare (1, 2, ...);
;    on exit, Carry Set means ((DE)) < ((HL)), Zero Set means ((HL)) = ((DE)),
;    and Non-Zero and No-Carry means ((DE)) > ((HL))
;
ICOMPARE:
	PUSH	H		; SAVE HL
	LHLD	ORDER		; ADDRESS OF ORDER - 2
	MOV	B,H		; ... IN BC
	MOV	C,L
	POP	H
	DCX	H		; ADJUST INDEX TO 0...N-1 FROM 1...N
	DAD	H		; DOUBLE THE ELEMENT NUMBER TO POINT TO THE PTR
	DAD	B		; ADD TO THIS THE BASE ADDRESS OF THE PTR TABLE
	XCHG			; RESULT IN DE
	DCX	H		; ADJUST INDEX TO 0...N-1 FROM 1...N
	DAD	H		; DO THE SAME WITH THE ORIGINAL DE
	DAD	B
	XCHG

;
;  HL NOW POINTS TO THE POINTER WHOSE INDEX WAS IN HL TO BEGIN WITH
;  DE NOW POINTS TO THE POINTER WHOSE INDEX WAS IN DE TO BEGIN WITH
;	FOR EXAMPLE, IF DE=5 AND HL=4, DE NOW POINTS TO THE 5TH PTR AND HL
; TO THE 4TH POINTER
;
	MOV	C,M		; BC IS MADE TO POINT TO THE OBJECT INDEXED TO
	INX	H		; ... BY THE ORIGINAL HL
	MOV	B,M
	XCHG
	MOV	E,M		; DE IS MADE TO POINT TO THE OBJECT INDEXED TO
	INX	H		; ... BY THE ORIGINAL DE
	MOV	D,M
	MOV	H,B		; SET HL = OBJECT PTED TO INDIRECTLY BY BC
	MOV	L,C

;
;  COMPARE DIR ENTRY PTED TO BY HL WITH THAT PTED TO BY DE;
;	NO NET EFFECT ON HL, DE; RET W/CARRY SET MEANS DE<HL
;	RET W/ZERO SET MEANS DE=HL
;
CMP$ENTRY:
;
;  COMPARE BY FILE NAME, FILE TYPE, EXTENSION, AND USER NUM (IN THAT ORDER)
;
	PUSH	H
	PUSH	D
	INX	H	; PT TO FN
	INX	D
	MVI	B,11	; COMPARE FN, FT
	CALL	COMP
	POP	D
	POP	H
	RNZ
	LDAX	D	; COMPARE USER NUMBER
	CMP	M
	RET
;
;  COMP COMPARES DE W/HL FOR B BYTES; RET W/CARRY IF DE<HL
;	MSB IS DISREGARDED
;
COMP:
	MOV	A,M	; GET (HL)
	ANI	7FH	; MASK MSB
	MOV	C,A	; ... IN C
	LDAX	D	; COMPARE
	ANI	7FH	; MASK MSB
	CMP	C
	RNZ
	INX	H	; PT TO NEXT
	INX	D
	DCR	B	; COUNT DOWN
	JNZ	COMP
	RET
;
;  AS COMP, BUT MATCH ON '?' PTED TO BY HL
;
COMP2:
	MOV	A,M	; GET (HL)
	ANI	7FH	; MASK MSB
	CPI	'?'	; MATCH '?'
	JZ	COMP2A
	MOV	C,A	; ... IN C
	LDAX	D	; COMPARE
	ANI	7FH	; MASK MSB
	CMP	C
	RNZ
COMP2A:
	INX	H	; PT TO NEXT
	INX	D
	DCR	B	; COUNT DOWN
	JNZ	COMP2
	RET

;
;  SORT BUFFERS
;
ORDER:
	DS	2	; PTR TO ORDER TABLE
DIRBUF:
	DS	2	; POINTER TO DIRECTORY
DSTART:
	DS	2	; POINTER TO FIRST DIRECTORY ENTRY
FCOUNT:
	DS	2	; TOTAL NUMBER OF FILES/NUMBER OF SELECTED FILES
HOLD:
	DS	ESIZE	; EXCHANGE HOLD BUFFER FOR FCB'S
PTPTR:
	DS	2	; POINTER POINTER
PTDIR:
	DS	2	; DIRECTORY POINTER
I:
	DS	2	; INDEXES FOR SORT
J:
	DS	2
JG:
	DS	2
N:
	DS	2	; NUMBER OF ELEMENTS TO SORT
GAP:
	DS	2	; BINARY GAP SIZE
;
; THIS IS THE WORKING COPY OF THE BIOS JUMP TABLE
;
WBOOT:	 DS	3
CONST:	 DS	3
CONIN:	 DS	3
CONOUT:	 DS	3
LIST:	 DS	3
PUNCH:	 DS	3
READER:	 DS	3
HOME:	 DS	3
SELDSK:	 DS	3
SETTRK:	 DS	3
SETSEC:	 DS	3
SETDMA:	 DS  	3
READ:	 DS	3
WRITE:	 DS	3
LISTST:	 DS	3
SECTRAN: DS	3
;
 	DS	100		;STACK DEPTH
STACK:
	DS	2		;LOCATION OF STACK
;
; DATA AREAS
;
FNCOUNT:
	DS	1		;NUMBER OF FILE NAMES FOUND
CLPFLG:	DS	1		;0 FOR NO MATCH LOCALLY
SYSTEM:	DS	1		;0 IF NO SYSTEM FILES
ECOUNT:	DS	1		;COUNT OF ENTRIES PRINTED - 1
FFLAG:	DS	1		;FILE FOUND FLAG (0=NO)
TEMP:	DS	2		;TEMP STORAGE FOR FCB PRINT
;
;  DISK PARAMETER DATA
;
DPH:	DS	2		;ADDRESS OF DPH
DIRMAX:	DS	2		;NUMBER OF SECTORS IN DIRECTORY =
;				;   MAXIMUM NUMBER OF DIRECTORY ENTRIES
;				;   DIVIDED BY 4 (ENTRIES PER SECTOR)
EXTENT:	DS	1		;EXTENT MASK
MAXSEC:	DS	2		;MAXIMUM NUMBER OF SECTORS/TRACK
SECTOR:	DS	2		;CURRENT SECTOR NUMBER
TRACK:	DS	2		;TRACK NUMBER OF DIRECTORY

FNTAB:	DS	11*40		;ARBITRARILY LARGE

SCRATCH	EQU	$/100H*100H+100H	;BEGINNING OF NEXT PAGE

	END
