	TITLE	'XDIR - CP/M DIRECTORY DISPLAY PROGRAM'
;			-  XDIR -
;					BY S J SINGER
;MODIFICATIONS
;	MAY 15, 1980 - ADDED SUPPORT FOR DRIVES A-D
;	MAY 15, 1980 - CHANGED THE DISPLAY TO 18 LINES
;	MAY 15, 1980 - FIXED BUG WITH DIR EXTENTS NOT BEING IN
;		       ORDER.  IF A LARGER EXTENT CAME BEFORE
;		       EXTENT ZERO, THE PROGRAM WOULD LOOP INFINITELY.
;	MAY 24, 1980 - CHANGED SO THAT DIR ENTRIES ARE READ VIA
;			BDOS CALLS (011H AND 012H).  
;	MAY 24, 1980 - CHANGED SO THAT SPACE INFO IS GOTTEN IN
;			ACCORDANCE WITH CP/M 2.0 AND ABOVE.
;
;	XDIR IS A CPM UTILITY THAT DISPLAYS A DISK DIRECTORY IN A MORE
;READABLE FORM. THE DIRECTORY IS READ FROM THE SPECIFIED DISK, SORTED
;THEN DISPLAYED IN A 3 COLUMN FORMAT. BOTH THE FILE NAMES AND FILE SIZES 
;IN 1 K GROUPS ARE DISPLAYED.
;	THE PRESENT VERSION OF XDIR HAS CERTAIN MINOR LIMITATIONS WHICH
;MAY BE EASILY REMOVED IF THEY CAUSE PROBLEMS.
;
;	1.  ONLY 54 FILES ARE DISPLAYED. THIS LIMIT IS SUFFICIENT FOR
;	    ALMOST ALL DISKS AND LEAVES ROOM ON THE SCREEN FOR EDITING.
;	    TO CHANGE THE NO OF LINES DISPLAYED, CHANGE THE LINES VARIABLE
;	    IN THE DATA ALLOCATION SECTION OF THE PROGRAM. FOR EXAMPLE
;	    TO DISPLAY A MAXIMUM OF 54 FILES CHANGE LINES TO 18.
;
;	OCCASIONALLY THE FILE NAMES WILL BE DISPLAYED WITH THE FORMAT
;SCRAMBLED OR THE SPACE REMAINING ON THE DISK WILL NOT AGREE WITH THAT REPORTED
;BY THE STAT UTILITY. THIS ALMOST ALWAYS MEANS THE DISK DIRECTORY HAS BEEN
;MESSED UP SOMEHOW. USUALLY THE PROBLEM CAN BE CORRECTED BY COPYING ALL THE
;FILES TO ANOTHER DISK USING PIP.
;	XDIR WAS ASSEMBLED USING THE NEW CP/M MACRO ASSEMBLER AND USES A LARGE
;NUMBER OF MACROS. THESE ARE CONTAINED IN A LIBRARY CALLED MACRO.LIB WHICH
;IS REQUIRED IF THE PROGRAM IS TO BE REASSEMBLED.
;
;
;		COMMAND FORMAT
;
;	XDIR			DISPLAY DIRECTORY OF LOGGED DISK
;	XDIR A			DISPLAY DIRECTORY OF DISK A
;	XDIR B			DISPLAY DIRECTORY OF DISK B
;	XDIR C			DISPLAY DIRECTORY OF DISK C
;	XDIR D			DISPLAY DIRECTORY OF DISK D
;
;
;	THE PROGRAM STARTS READING THE CP/M DIRECTORY UNTIL AN EOF OCCURS.
;AS IT READS EACH ENTRY, IT MAINTAINS A TABLE OF ALL FILES.  THE TABLE CON-
;TAINS ONE ENTRY FOR EACH FILE FROM ITS LARGEST EXTENT.  WHEN ALL FILES ARE
;IN THE TABLE, IT SORTS THEM USING QUICKSORT AND AN AUXILIARY ADDRESS TABLE.
;WHEN THE SORT COMPLETES, THE FILE TABLE IS PRINTED IN THE ORDER OF THE AUX
;ADDRESS TABLE AND CONTROL IS GIVEN BACK TO CP/M.

;		* * *  COMMON EQUATES  * * *
LINES	EQU	18	;LINES PER PAGE OF DISPLAY
MAXDIR	EQU	128	;MAXIMUM NUMBER OF DIRECTORY ENTRIES

	MACLIB	MACRO		;INCLUDE MACRO LIBRARY
	PAGE
;************************************************
;*		PROGRAM INITIALIZATION		*
;************************************************

	ORG	100H		;SET PROG START
	LXI	H,0
	DAD	SP		;GET OLD STACK POINTER
	SHLD	OLDSTK		;SAVE IT
	LXI	SP,NEWSTK	;LOAD NEW STACK POINTER
;
;    START OF DIRECTORY ROUTINE READ IN GROUPS 0 AND 1 AND STORE
;    DIRECTORY FILE NAMES NOT FLAGGED E5.  IF EXTENT NOT ZERO STORE
;    OVER PREVIOUS FILE NAME. TABLE OF POINTERS WILL BE BUILT IN PDIR
;
DIR:	DISKIO	?DRIVE
	STA	NEWDRV
	LDA	81H
	ORA	A		;CHECK IF INPUT BUFFER EMPTY
	JZ	DDD
	LDA	82H		;CHECK NEW DRIVE (A-D)
	CPI	'A'
	JC 	DSKERR
	CPI	'D'+1
	JNC	DSKERR
	SUI	'A'		;MAKE IT RELATIVE TO ZERO.
	STA	NEWDRV		;SAVE IT.
DDD:	PRINT	<CR,LF,LF,'                        DIRECTORY DRIVE - '>
	LDA	NEWDRV		;LOGGED DISK
	ADI	'A'		
	CHAROUT
	PRINT	<CR,LF,LF>
	CALL	FIXB		;RESTORE DRIVE.

;		GET THE CURRENT DISK MAX VALUE.
	MVI	C,01FH		;GET DPB ADR FROM CP/M.
	CALL	5
	SHLD	DPBADR		;SAVE IT.
	LXI	D,2		;GET THE BSH.
	DAD	D
	MOV	A,M		;SAVE IT.
	STA	DSKBSH
	MOV	C,A		;  AGAIN.
	INX	H		;GET THE BLM FOR LATER.
	MOV	A,M
	STA	DSKBLM		;SAVE IT.
	MOV	A,C		;CONVERT BSH TO REL SHIFT.
	SUI	3
	MOV	C,A		;SAVE IT.
	INX	H		;GET THE DSM.
	INX	H
	MOV	E,M
	INX	H
	MOV	D,M
	INX	H
	XCHG
	INX	H		;MAKE IT RELATIVE TO ONE.
	DCR	C		;SHIFT MAX BY (C) TIMES.
	INR	C
	JZ	$+8
	DAD	H	
	DCR	C
	JMP	$-7
	SHLD	MAXSTR		;SAVE VALUE.

;		GET DIRECTORY SIZE.
	XCHG			;HL = DPBDRM
	MOV	E,M	
	INX	H
	MOV	D,M
	XCHG
	INX	H		;MAKE IT RELATIVE TO ONE.
	LXI	D,32-1		;ROUND IT TO EVEN # OF K.
	DAD	D
	HALF			;DIVIDE IT BY 32 (# IN 1K).
	HALF			;/4
	HALF			;/8
	HALF			;/16
	HALF			;/32
	SHLD	DIRSIZ		;SAVE IT.

;		ADJUST MAX SIZE BY DIRECTORY SIZE.
	LHLD	MAXSTR
	DSUB	DIRSIZ
	SHLD	MAXSTR


DIR2:	XRA	A
	STA	COUNT		;COUNT OF DIRECTORY ENTRIES

;	FILL	PDIR,PDIR+(2*(MAXDIR+1)) ;ZERO DIR PTR TABLE
	LXI	H,PDIR
	LXI	D,2*(MAXDIR+1)
	MVI	M,0
	INX	H
	DCX	D
	MOV	A,E
	ORA	D
	JNZ	$-6

	LXI	H,DIRBUF	;POINTS TO DIRECTORY BUFFER
	SHLD	OUTB
	LXI	H,PDIR		;POINTER TABLE
	SHLD	IPOINT
	LDA	NEWDRV
	MOV	E,A
	DISKIO	LOGIN		;LOG IN NEW DRIVE NO
DIR4:	LXI	H,80H		;POINTS TO INPUT BUFFER
	SHLD	INB
	MVI	C,01AH		;ISSUE SET DMA.
	XCHG
	CALL	5
	MVI	C,011H		;ISSUE DIR FIRST GET.
	LXI	D,DIRFCB
	CALL	5
	JMP	DIR6

;		GET NEXT DIR BUFFER.
DIR5:
	MVI	C,012H
	CALL	5
	CPI	255		;END OF DIR?
	JZ	SORT		;...YES, GO SORT TABLE.
	ADD	A		;CONVERT # TO DISP.
	ADD	A
	ADD	A
	ADD	A
	ADD	A
	ADI	80H		;ADD IN BUFFER ADDRESS.
	MOV	L,A
	MVI	H,0
	SHLD	INB		;SAVE PTR.

;		SEE IF EOF.
DIR6:	LHLD	OUTB		;LOAD DESTINATION POINTER
	XCHG			;PUT IT IN DE
	LHLD	INB		;LOAD SOURCE POINTER
	MVI	A,0E5H		;FLAG BYTE
	CMP	M		;TEST FIRST BYTE
	JNZ	DIR8
	INX	H
	CMP	M		;TEST SECOND BYTE
	JZ	SORT		;SORT DIRECTORY
	JMP	DIR12A


DIR8:
;		IF ITS THE ZERO ENTRY, ADD IT TO TABLE.
	INX	H
	SAVE	H,D

;		SCAN TABLE TO SEE IF AN EXTENT EXISTS FOR THE FILE.
	LXI	H,0		;SEARCH FOR SAME NAME AND SWITCH
	SHLD	J		;INITIALIZE INDEX
DIR9:	DLOAD	PDIR,J
	MOV	A,H
	ORA	L
	JZ	DIR10		;ERROR TABLE EMPTY
	XCHG
	LHLD	INB		;POINTER TO NEW DIR ENTRY
	SAVE	D,H
	INX	H
	MATCH	,,11		;COMPARE 11 CHARAACTERS
	RESTORE	H,D
	JZ	SWITCH		;STORE NEW ENTRY OVER OLD
	INDEX	J,2		;INCR INDEX BY 2
	JMP	DIR9

;		REPLACE EXTENT IN TABLE IF ITS GREATER.
SWITCH:
	CALL	CLCSPC		;GET NUMBER OF GROUPS.
	XCHG
	LXI	B,11		;ADD IT TO CNT IN TBL.
	DAD	B
	ADD	M
	MOV	M,A
	JNC	$+5
	INX	H
	INR	M
	JMP	DIR12

;		INSERT ENTRY IN TABLE.
DIR10:	RESTORE	D,H
	SAVE	D,H
	MOVE	,,15		;MOVE THE DIRECTORY ENTRY
	RESTORE H,D
	LXI	H,12		;MOVE # OF GRPS TO EX.
	DAD	D
	CALL	CLCSPC		;GET NUMBER OF CPM GROUPS.
	MVI	M,0		;ZERO S1.
	DCX	H
	MOV	M,A
	LDA	COUNT
	INR	A
	STA	COUNT		;INCR COUNT OF DIRECTORY ENTRIES
	LHLD	OUTB
	DSTORE	0,IPOINT	;INDEXED STORE HL
	INDEX	OUTB,16
	INDEX	IPOINT,2
	JMP	DIR12A

;		BUMP INPUT POINTERS AND CHECK FOR END OF BUFFER.
DIR12:
	RESTORE	H
DIR12A:
	JMP	DIR5		;READ ANOTHER BLOCK FROM DIRECTORY

;		GET NUMBER OF CP/M GROUPS IN THIS EXTENT.
CLCSPC:	SAVE	B,D,H
	LHLD	INB		;POINT TO EXTENT GROUP #'S.
	LXI	D,16
	DAD	D
	MVI	B,0		;ZERO COUNT.
	MVI	C,16		;SET FOR MAX # OF GROUPS.
CLCLP:
	MOV	A,M		;GET A GROUP #.
	ORA	A		;ALLOCATED?
	JZ	$+4		;...NO.
	INR	B		;...YES, BUMP COUNT.
	INX	H		;BUMP GROUP PTR.
	DCR	C		;DECR COUNT.
	JNZ	CLCLP		;LOOP FOR ALL ENTRIES.
	MOV	A,B		;GET COUNT.
	RESTORE H,D,B
	RET



;    THIS ROUTINE PRINTS THE DIRECTORY IN 3 COLUMNS. NO OF LINES
;    PRINTED IS CONTROLED BY VARIABLE LINES. ALL DIRECTORY NAMES
;    ARE PRESENT IN TABLE BUT ONLY A MAXIMUM OF 3*LINES WILL BE
;    PRINTED.
;
DIR14:	LXI	H,0
	SHLD	W		;INITIALIZE ALLOCATION
	SHLD	I		;INITIALIZE INDEX
DIR16:	DLOAD	PDIR,I		;INDEX LOAD POINTER
	DJZ	ENDFIL		;EXIT IF POINTER ZERO
	CALL	DIR20		;CALL PRINT ROUTINE FOR AN ENTRY.
	PRINT	'         '
	DLOAD	PDIR+LINES*2,I	;POINTER COL 2
	DJZ	DIR18		;NO PRINT IF ZERO
	CALL	DIR20		;PRINT IT
	PRINT	'         '
	DLOAD	PDIR+LINES*4,I	;POINTER COL 3
	DJZ	DIR18
	CALL	DIR20		;CALL PRINT ROUTINE
DIR18:	PRINT	CRLF,$
	INDEX	I,2		;INCR INDEX BY 2
	LXI	D,LINES*2	;CHECK INDEX LIMIT
	CPHL
	JZ	ENDFIL		;EXIT WHEN INDEX 32
	JMP	DIR16		;PRINT SOME MORE


;    *** SUBROUTINE TO PRINT A SINGLE DIRECTORY ENTRY ***
;
;		PRINT THE FILE NAME.
DIR20:	MVI	C,11		;NAME LENGTH
DIR22:	SAVE	B,H		;SAVE REGISTERS
	MOV	A,M		;GET A CHAR.
	ANI	07FH		;REMOVE POSSIBLE INDICATOR.
	CHAROUT			;PUT IT TO THE CONSOLE.
	RESTORE	H,B		;RESTORE THE REGISTERS
	INX	H		;INCR NAME POINTER
	DCR	C		;DECR CHAR COUNT
	JNZ	DIR22		;LOOP FOR LENGTH OF NAME.

;		CALCULATE AND PRINT THE FILE SIZE.
DIR24:
	MOV	E,M		;DE = # OF ALLOCATED GROUPS
	INX	H
	MOV	D,M
	XCHG

	LDA	DSKBSH		;CONVERT CP/M GROUP BACK TO K.
	SUI	3
	JZ	$+8
	DAD	H
	DCR	A
	JNZ	$-2
	SAVE	H

	XCHG			;ADD IT TO CUM TOTAL.
	LHLD	W
	DAD	D
	SHLD	W
	
	PRINT	' '
	POP	H
	PUSH	H
	LXI	D,1000
	CPHL
	JNC	DIR30
	PRINT	' '
	POP	H
	PUSH	H
	LXI	D,100
	CPHL
	JNC	DIR30
	PRINT	' '
	POP	H
	PUSH	H
	LXI	D,10
	CPHL
	JNC	DIR30
	PRINT	' '
DIR30:	POP	H
	DECOUT
	PRINT	'K'
	RET
;
;
;    THIS ROUTINE RESTORES DRIVE B
;
FIXB:	LDA	NEWDRV		;CHECK DRIVE NO
	ORA	A
	RZ			;RETURN IF DRIVE A
	LDA	NEWDRV		;SELECT THE DRIVE.
	MOV	E,A
	DISKIO	LOGIN
	RET
;
;    THIS IS THE EXIT POINT FROM THE PROGRAM. PRINT NO OF FILES AND
;    SPACE REMAINING, RELOAD OLD STACK POINTER AND RETURN BACK TO CCP.
;
ENDFIL:	PRINT	<CR,LF,'                '>
	LXI	H,0
	LDA	COUNT
	MOV	L,A
	DECOUT
	PRINT	' FILES    '

	LHLD	MAXSTR		;GET MAX STORAGE SIZE.
	DSUB	W		;SUBTRACT IN-USE SIZE.
	DECOUT
	PRINT	<'K BYTES REMAINING ON DISK',CR,LF>
EF1:	LHLD	OLDSTK
	SPHL			;RELOAD OLD STACK POINTER
	RET			;RETURN TO CCP WITHOUT REBOOT
;
DSKERR:	PRINT	<CR,LF,'ERROR - SELECT DRIVE A, B, C OR D'>
	JMP	EF1		;EXIT
;
;    THIS SECTION DOES THE ACTUAL SORTING OF THE DIRECTORY. DURING THE
;    INPUT OF THE DIRECTORY NAMES, A TABLE OF ADDRESS POINTERS PDIR
;    WAS CONSTRUCTED. THE SORT ROUTINE SORTS THE ADDRESS POINTERS
;    RATHER THAN THE ACTUAL DIRECTORY. 
;    THIS IS AN IMPLEMENTATION OF C. A. R. HOARE'S QUICKSORT ALGORITHM.
;    THE ALGORITHM IS VERY FAST AND GENERALLY USEFUL, HOWEVER CAUTION
;    SHOULD BE USED WITH LARGE FILES. THE ALGORITHM IS RECURSIVE AND
;    THE STACK SPACE REQUIRED IS PROPORTIONAL TO THE NO OF ITEMS TO BE
;    SORTED.
;
SORT:	LDA	COUNT		;NO OF ENTRIES IN DIR
	ORA	A
	JZ	ENDFIL		;EXIT IF DIRECTORY EMPTY
	DCR	A
	LXI	H,0		;ZERO HL
	MOV	L,A
	DAD	H
	SHLD	LAST		;END OF ARRAY
	LXI	H,0
	SHLD	FIRST		;START OF ARRAY
	LXI	H,0FFFFH
	PUSH	H		;FLAG FOR STACK EMPTY
	LHLD	FIRST
	PUSH	H
	LHLD	LAST
	PUSH	H		;STACK CONTAINS FIRST AND LAST INDICES
;
;    NOW POP STACK AND KEEP CALLING SPLIT RECURSIVELY TILL STACK EMPTY
;
SORT2:	POP	H
	MOV	A,H
	CPI	0FFH
	JZ	DIR14		;GO TO PRINT ROUTINE
	SHLD	J
	SHLD	LAST
	POP	H
	SHLD	I
	SHLD	FIRST
	CALL	SPLIT
	LHLD	I
	XCHG
	LHLD	FIRST
	CPHL
	JZ	SORT4
	PUSH	H		;I ON STACK
	DCX	D
	DCX	D
	PUSH	D		;J ON STACK
SORT4:	LHLD	J
	XCHG
	LHLD	LAST
	CPHL
	JZ	SORT8
	INX	D
	INX	D
	PUSH	D		;NEW I ON STACK
	PUSH	H		;NEW J ON STACK
SORT8:	JMP	SORT2
;
;    SPLIT SUBROUTINE DOES A SINGLE PARTITION ON AN ARRAY OF POINTERS
;
SPLIT:	HALF	I
	XCHG
	HALF	J
	DAD	D
	MOV	A,L
	ANI	0FEH
	MOV	L,A
	SHLD	K		;K=I+J/2
	DLOAD	PDIR,K
	SHLD	W		;W IS POINTER TO PARTITION ELEMENT OF PDIR
SPLIT2:	DLOAD	PDIR,I		;GET ITEM FROM LEFT
	XCHG
	LHLD	W		;PARTITION ELEMENT
	MATCH	,,11		;CONPARE KEYS
	JP	SPLIT4
	INDEX	I,2		;INCR I
	JMP	SPLIT2
SPLIT4:	DLOAD	PDIR,J		;GET ITEM FROM RIGHT
	XCHG
	LHLD	W		;PARTITION ELEMENT
	XCHG
	MATCH	,,11		;COMPARE KEYS
	JP	SPLIT6
	INDEX	J,-2
	JMP	SPLIT4		;LOOP BACK
SPLIT6:	LHLD	I
	XCHG
	LHLD	J
	CPHL			;COMPARE I AND J
	RZ			;RET IF I = J
	DLOAD	PDIR,I		;SWITCH POINTERS
	SAVE	H
	DLOAD	PDIR,J
	DSTORE	PDIR,I
	RESTORE	H
	DSTORE	PDIR,J
	JMP	SPLIT2
;
;   DATA ALLOCATIONS
;
LINES	EQU	18		;LINES PER PAGE ON DISPLAY
SPACE:	DB	' $'		;ASCII SPACE
CRLF:	DB	0DH,0AH,24H	;ASCII CR LF
I:	DW	0		;PSEUDO INDEX REGISTER
J:	DW	0		;PSEUDO INDEX REGISTER
K:	DW	0		;PSEUDO INDEX REGISTER
FIRST:	DW	0		;START OF ARRAY
LAST:	DW	0		;END OF ARRAY
W:	DW	0		;STORAGE FOR PARTITION INDEX
DIRSIZ:	DW	0		;SIZE OF DIRECTORY IN K
MAXSTR:	DW	0		;MAXIMUM STORAGE AVAILABLE
LINE:	DW	0		;LINE NUMBER FOR LISTING
IPOINT:	DW	00		;VARIABLE BUFFER POINTER
DRVNO:	DB	0		;STORAGE FOR ORIGINALLY LOGGED DRIVE
NEWDRV:	DB	0		;STORAGE FOR NEW DRIVE NO
COUNT:	DB	0		;COUNT OF DIRECTORY ENTRIES
OLDSTK:	DW	0		;STORAGE FOR OLD STACK POINTER
ENDSTK:	DS	128		;STORAGE FOR NEW STACK
NEWSTK:	DW	0		;NEW STACK
INB:	DW	0		;STORES POINTER TO INPUT BUFFER AREA
OUTB:	DW	0		;STORES POINTER TO DIRECTORY BUFFER AREA
DPBADR:	DW	0		;ADDRESS OF DISK'S CP/M DPB
DSKBSH: DB 	0		;DISK DPBBSH VALUE
DSKBLM:	DB	0		;DISK DPBBLM VALUE
DIRFCB:	DB	'?','????????','???','?',0,0,0
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	DB	0,0,0
PDIR	DW	0		;POINTER TABLE TO DIRECTORY
DIRBUF:	EQU	PDIR+(2*MAXDIR)	;START OF AREA USED TO STORE AND SORT DIRECTORY
	END
