;
;                       DIRS.ASM
;                    revised 10/15/80
;
;               SORTED DIRECTORY PROGRAM
;               by Keith Petersen, W8SDZ
;
;DISPLAY FORMAT IS SAME AS CP/M 2.x DIR, EXCEPT IS SORTED
;ALPHABETICALLY. SUGGESTED AS A REPLACEMENT FOR THE "DIR"
;COMMAND IN CP/M-2. PRESENT VERSION ALLOWS MAX. OF 256 NAMES.
;
;PRINTS A 4-WIDE DIRECTORY, SORTED ALPHABETICALLY.
;COMPATIBLE WITH CP/M 1.4 AND 2.x. IGNORES "SYS" FILES.
;
;BASED ON 'FMAP' BY WARD CHRISTENSEN.
;
;DIRS FILENAME.FILETYPE or just DIRS
;
;ALLOWS '*' OR '?' TYPE SPECIFICATIONS
;DRIVE NAME BY ALSO BE SPECIFIED
;
; 10/15/80 - Trap file table overflow into BDOS (should only be
;               a problem with MP/M, where TPA can be tiny). (BRR)
; 10/01/80 - Say 'File not found.' instead of 'NO FILE' and
;               display user number heading if CHKUSR enabled.
;               Take 'NO FILE' path if first search succeeds,
;               but nothing qualifies for the sort.
;               (BRR)
; 09/08/80 - CHKUSR conditional assembly added to force match of
;               current user number, as required in MP/M to
;               prevent user 0 files from showing up in all
;               areas.  Also used in CP/M 2.2 systems containing
;               BDOSPAT.  (BRR)
; 09/07/80 - Modified to assemble with RMAC and externalized
;               page 0 for use with MP/M.  (Must be linked
;               with PG0EQU.ASM)
;            Also modified to allow (via conditional assembly)
;               'S' option to display system files.
;            (by Bruce R. Ratoff)
;
;
FALSE   EQU     0               ;DEFINE LOGICAL FALSE
TRUE    EQU     NOT FALSE       ;DEFINE LOGICAL TRUE
;
ALTCPM  EQU     FALSE   ;PUT TRUE HERE FOR H8 OR TRS-80
RMAC    EQU     TRUE    ;PUT TRUE HERE FOR ASSEMBLY BY RMAC
SOPT    EQU     TRUE    ;PUT TRUE TO ALLOW 'DIR *.* S' FORM
CHKUSR  EQU     TRUE    ;PUT TRUE IF USER # MATCH REQUIRED
;
       IF      ALTCPM
BASE    EQU     4200H
TPA     EQU     4300H
       ENDIF
       IF      RMAC
       EXTRN   BASE,FCB,BDOS   ;MAKE BASE EXTERNAL
       ENDIF
       IF      (NOT ALTCPM) AND (NOT RMAC)
BASE    EQU     $       ;WILL DEFAULT TO 0 (OR 100H WITH MAC +R OPTION)
TPA     EQU     100H
       ENDIF
;
       IF      NOT RMAC
FCB     EQU     BASE+5CH
BDOS    EQU     BASE+5
       ENDIF
;
NPL     EQU     4       ;NUMBER OF NAMES PER LINE
DELIM   EQU     ':'     ;FENCE (DELIMITER) CHARACTER
;
       IF      NOT RMAC
       ORG     TPA
       ENDIF
;
;SAVE THE STACK
START   LXI     H,0
       DAD     SP      ;H=STACK
       SHLD    STACK   ;SAVE IT
       LXI     SP,STACK ;GET NEW STACK
;
       IF      SOPT
       LDA     FCB+17  ;SAVE S OPTION FLAG
       STA     SOPFLG  ;(BLANK OR LETTER S)
       ENDIF
;
       IF      CHKUSR
       MVI     E,0FFH
       MVI     C,CURUSR        ;INTERROGATE USER NUMBER
       CALL    BDOS
       STA     USERNO
       LXI     D,USRMSG        ;DISPLAY IT
       MVI     C,PRINT
       CALL    BDOS
       LDA     USERNO
       CPI     10
       JC      DUX
       MVI     A,'1'
       CALL    TYPE
       LDA     USERNO
       SUI     10
DUX     ADI     '0'
       CALL    TYPE
       LXI     D,USRMS2
       MVI     C,PRINT
       CALL    BDOS
       ENDIF
;
       LXI     H,FCB
       MOV     A,M     ;GET DRIVE NAME
       ORA     A       ;ANY SPECIFIED?
       JNZ     START2  ;YES SKIP NEXT ROUTINE
       MVI     C,CURDSK
       CALL    BDOS    ;GET CURRENT DISK NR
       INR     A       ;MAKE A:=1
;
START2  ADI     'A'-1   ;MAKE IT PRINTABLE
       STA     DRNAM   ;SAVE FOR LATER
       LXI     H,FCB+1 ;POINT TO NAME
       MOV     A,M     ;ANY SPECIFIED?
       CPI     ' '
       JNZ     GOTFCB
;NO FCB - MAKE FCB ALL '?'
       MVI     B,11    ;FN+FT COUNT
QLOOP   MVI     M,'?'   ;STORE '?' IN FCB
       INX     H
       DCR     B
       JNZ     QLOOP
;LOOK UP THE FCB IN THE DIRECTORY
GOTFCB  MVI     C,FSRCHF ;GET 'SEARCH FIRST' FNC
       LXI     D,FCB
       CALL    BDOS    ;READ FIRST
       INR     A       ;WERE THERE ANY?
       JNZ     SOME    ;GOT SOME
NONE    CALL    ERXIT
       IF      NOT CHKUSR
       DB      'NO FILE$'
       ENDIF
       IF      CHKUSR
       DB      'File not found.$'
;
USRMSG  DB      'Directory for user $'
USRMS2  DB      ':',13,10,'$'
       ENDIF
;
;READ MORE DIRECTORY ENTRIES
MOREDIR MVI     C,FSRCHN ;SEARCH NEXT
       LXI     D,FCB
       CALL    BDOS    ;READ DIR ENTRY
       INR     A       ;CHECK FOR END (0FFH)
       JZ      SPRINT  ;NO MORE - SORT & PRINT
;POINT TO DIRECTORY ENTRY
SOME    DCR     A       ;UNDO PREV 'INR A'
       ANI     3       ;MAKE MODULUS 4
       ADD     A       ;MULTIPLY...
       ADD     A       ;..BY 32 BECAUSE
       ADD     A       ;..EACH DIRECTORY
       ADD     A       ;..ENTRY IS 32
       ADD     A       ;..BYTES LONG
       LXI     H,BASE+81H ;POINT TO BUFFER
                       ;(SKIP TO FN/FT)
       ADD     L       ;POINT TO ENTRY
       ADI     9       ;POINT TO SYS BYTE
       MOV     L,A     ;SAVE (CAN'T CARRY TO H)
;
       IF      SOPT
       LDA     SOPFLG  ;DID USER REQUEST SYS FILES?
       CPI     'S'
       JZ      SYSFOK
       ENDIF
;
       MOV     A,M     ;GET SYS BYTE
       ANI     80H     ;CHECK BIT 7
       JNZ     MOREDIR ;SKIP THAT FILE
SYSFOK  MOV     A,L     ;GO BACK NOW
       SUI     9       ;BACK TO FT/FN START
       MOV     L,A     ;HL POINTS TO ENTRY NOW
;
       IF      CHKUSR
       DCX     H       ;POINT TO USER BYTE IN FCB
       LDA     USERNO  ;GET CURRENT USER
       CMP     M
       JNZ     MOREDIR ;IGNORE IF DIFFERENT
       INX     H
       ENDIF
;
;MOVE ENTRY TO TABLE
       XCHG            ;ENTRY TO DE
       LHLD    NEXTT   ;NEXT TABLE ENTRY TO HL
       MVI     B,11    ;ENTRY LENGTH
TMOVE   LDAX    D       ;GET ENTRY CHAR
       ANI     7FH     ;REMOVE ATTRIBUTES
       MOV     M,A     ;STORE IN TABLE
       INX     D
       INX     H
       DCR     B       ;MORE?
       JNZ     TMOVE
       SHLD    NEXTT   ;SAVE UPDATED TABLE ADDR
       LDA     COUNT   ;GET PREV COUNT
       INR     A
       STA     COUNT
       LXI     D,11    ;SIZE OF NEXT ENTRY
       DAD     D
       XCHG            ;FUTURE NEXTT IS IN DE
       LHLD    BDOS+1  ;PICK UP TPA END
       MOV     A,E
       SUB     L       ;COMPARE NEXTT-TPA END
       MOV     A,D
       SBB     H
       JC      MOREDIR ;IF TPA END>NEXTT THEN LOOP BACK FOR MORE
       CALL    ERXIT
       DB      'Out of memory.',13,10,'$'
;
;
;SORT AND PRINT
SPRINT  LDA     COUNT   ;GET FILE NAME COUNT
       ORA     A       ;ANY FOUND?
       JZ      NONE    ;NO, EXIT
;INIT THE ORDER TABLE
       LXI     H,ORDER
       LXI     D,TABLE
       LXI     B,11    ;ENTRY LENGTH
BLDORD  MOV     M,E     ;SAVE LO ORD ADDR
       INX     H
       MOV     M,D     ;SAVE HI ORD ADDR
       INX     H
       XCHG            ;TABLE ADDR TO HL
       DAD     B       ;POINT TO NEXT ENTRY
       XCHG
       DCR     A       ;MORE?
       JNZ     BLDORD  ;..YES
       LDA     COUNT   ;GET COUNT
       STA     SCOUNT  ;SAVE AS # TO SORT
       DCR     A       ;ONLY 1 ENTRY?
       JZ      DONE    ;..YES, SO SKIP SORT
SORT    XRA     A       ;GET A ZERO
       STA     SWITCH  ;SHOW NONE SWITCHED
       LDA     SCOUNT  ;GET COUNT
       DCR     A       ;USE 1 LESS
       STA     TEMP    ;SAVE # TO COMPARE
       STA     SCOUNT  ;SAVE HIGHEST ENTRY
       JZ      DONE    ;EXIT IF NO MORE
       LXI     H,ORDER ;POINT TO ORDER TABLE
SORTLP  CALL    COMPR   ;COMPARE 2 ENTRIES
       CM      SWAP    ;SWAP IF NOT IN ORDER
       INX     H       ;BUMP ORDER
       INX     H       ;..TABLE POINTER
       LDA     TEMP    ;GET COUNT
       DCR     A
       STA     TEMP
       JNZ     SORTLP  ;CONTINUE
;ONE PASS OF SORT DONE
       LDA     SWITCH  ;ANY SWAPS DONE?
       ORA     A
       JNZ     SORT
;SORT IS ALL DONE - PRINT ENTRIES
DONE    LXI     H,ORDER
       SHLD    NEXTT
;
;PRINT AN ENTRY
       CALL    DRPRNT  ;PRINT DRIVE NAME
       MVI     C,NPL   ;NR. OF NAMES PER LINE
ENTRY:  PUSH    B
       MVI     C,CONST ;CK STATUS OF KB
       CALL    BDOS    ;ANY KEY PRESSED?
       POP     B
       ORA     A
       JNZ     ABORT   ;YES, ABORT
       CALL    FENCE   ;PRINT FENCE CHAR AND SPACE
       LHLD    NEXTT   ;GET ORDER TABLE POINTER
       MOV     E,M     ;GET LO ADDR
       INX     H
       MOV     D,M     ;GET HI ADDR
       INX     H
       SHLD    NEXTT   ;SAVE UPDATED TABLE POINTER
       XCHG            ;TABLE ENTRY TO HL
       MVI     B,8     ;FILE NAME LENGTH
       CALL    TYPEIT  ;TYPE FILENAME
       CALL    PERIOD  ;PERIOD AFTER FN
       MVI     B,3     ;GET THE FILETYPE
       CALL    TYPEIT
       CALL    SPACE   ;SPACE OVER ONE
;SEE IF MORE ENTRIES
       LDA     COUNT
       DCR     A
       JZ      EXIT
       STA     COUNT
       DCR     C       ;ONE LESS ON THIS LINE
       CZ      CRLF    ;PRINT CR-LF AND DRIVE NAME
       JMP     ENTRY
;
PERIOD  MVI     A,'.'
       JMP     TYPE
;
DRPRNT  LDA     DRNAM   ;GET SAVED DRIVE NAME
       JMP     TYPE    ;PRINT IT
;
FENCE   MVI     A,DELIM ;FENCE CHARACTER
       CALL    TYPE    ;PRINT IT, FALL INTO SPACE
;
SPACE   MVI     A,' '
;
;TYPE CHAR IN A
TYPE    PUSH    B
       PUSH    D
       PUSH    H
       MOV     E,A
       MVI     C,WRCHR
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       RET
;
TYPEIT  MOV     A,M
       CALL    TYPE
       INX     H
       DCR     B
       JNZ     TYPEIT
       RET
;
CRLF    MVI     A,13    ;PRINT
       CALL    TYPE
       MVI     A,10    ;LF
       CALL    TYPE
       CALL    DRPRNT
       MVI     C,NPL   ;NUMBER OF NAMES PER LINE
       RET
;
;COMPARE ROUTINE FOR SORT
COMPR   PUSH    H       ;SAVE TABLE ADDR
       MOV     E,M     ;LOAD LO
       INX     H
       MOV     D,M     ;LOAD HI
       INX     H
       MOV     C,M
       INX     H
       MOV     B,M
;BC, DE NOW POINT TO ENTRIES TO BE COMPARED
       XCHG
CMPLP   LDAX    B
       CMP     M
       INX     H
       INX     B
       JZ      CMPLP
       POP     H
       RET             ;COND CODE TELLS ALL
;
;SWAP ENTRIES IN THE ORDER TABLE
SWAP    MVI     A,1
       STA     SWITCH  ;SHOW A SWAP WAS MADE
       MOV     C,M
       INX     H
       PUSH    H       ;SAVE TABLE ADDR+1
       MOV     B,M
       INX     H
       MOV     E,M
       MOV     M,C
       INX     H
       MOV     D,M
       MOV     M,B
       POP     H
       MOV     M,D
       DCX     H       ;BACK POINTER TO CORRECT LOC'N
       MOV     M,E
       RET
;
;ERROR EXIT
ERXIT   POP     D       ;GET MSG
       MVI     C,PRINT
       JMP     CALLB   ;PRINT MSG, EXIT
;
;ABORT - READ CHAR ENTERED
ABORT   MVI     C,RDCHR
CALLB   CALL    BDOS    ;DELETE THE CHAR
;
;FALL INTO EXIT
;EXIT - ALL DONE , RESTORE STACK
EXIT    LHLD    STACK   ;GET OLD STACK
       SPHL            ;MOVE TO STACK
       RET             ;..AND RETURN
;
NEXTT   DW      TABLE   ;NEXT TABLE ENTRY
COUNT   DB      0       ;ENTRY COUNT
SCOUNT  DB      0       ;# TO SORT
SWITCH  DB      0       ;SWAP SWITCH FOR SORT
BUFAD   DW      BASE+80H ;OUTPUT ADDR
ORDER   DS      512     ;ORDER TABLE (ROOM FOR 256 NAMES)
       DS      60      ;STACK AREA
STACK   DS      2       ;SAVE OLD STACK HERE
SOPFLG  DS      1       ;SET TO 'S' TO ALLOW SYS FILES TO PRINT
USERNO  DS      1       ;CONTAINS CURRENT USER NUMBER
DRNAM   DS      1       ;SAVE DRIVE NAME HERE
TEMP    DS      1       ;SAVE DIR ENTRY
TABLE   EQU     $       ;READ ENTRIES IN HERE
       DB      '...........'   ;DUMMY FIRST ENTRY TO FORCE .COM FILE SIZE
;
; BDOS EQUATES
;
RDCHR   EQU     1       ;READ CHAR FROM CONSOLE
WRCHR   EQU     2       ;WRITE CHR TO CONSOLE
PRINT   EQU     9       ;PRINT CONSOLE BUFF
CONST   EQU     11      ;CHECK CONS STAT
FOPEN   EQU     15      ;0FFH=NOT FOUND
FCLOSE  EQU     16      ;   "   "
FSRCHF  EQU     17      ;   "   "
FSRCHN  EQU     18      ;   "   "
CURDSK  EQU     25      ;GET CURRENTLY LOGGED DISK NAME
CURUSR  EQU     32      ;GET CURRENTLY LOGGED USER NUMBER (2.X ONLY)
;
       END