REM file: Dirattr.bas - Public Domain DOS Utility
REM Version 1.0a created 04/04/1995
REM Version 1.1a created 03/26/2001
REM Version 1.2a created 12/10/2005
REM Version 1.3a created 02/10/2006
REM Version 1.4a created 09/15/2007

' declare subroutines
DECLARE SUB Directories (D$, F$)
DECLARE SUB Filenames (D$, F$)
DECLARE SUB DisplayFiles (D$, F$)

' default integer variables
DEFINT A-Z
REM $DYNAMIC

' define boolean values
CONST True = -1
CONST False = NOT True
CONST TrueD = -1#
CONST FalseD = NOT TrueD
CONST NUL = ""

' define color values
CONST Black = 0
CONST Cyan = 11
CONST Green = 10
CONST Plain = 7
CONST Red = 12
CONST White = 15
CONST Yellow = 14

' get include files
REM $INCLUDE: 'qbx.bi'
REM $INCLUDE: 'dta.bi'
REM $INCLUDE: 'wdta.bi'

' declare functions
DECLARE FUNCTION ParseLine (S$)
DECLARE FUNCTION BreakIS()
DECLARE FUNCTION ClearBreak()
DECLARE FUNCTION KeyIS()

' initialize filename buffer
COMMON SHARED ASCIIZ AS STRING * 260
COMMON SHARED Directory.ASCIIZ AS STRING * 260, ASCIIZ.Sub AS STRING * 260
COMMON SHARED Drive.Search AS STRING * 1, ASCIIZ.File AS STRING * 260
COMMON SHARED ASCIIZ.Display AS STRING * 260

' declare program dta
DIM BASIC.DTA.SEG AS INTEGER, BASIC.DTA.OFF AS INTEGER
COMMON SHARED TempDTA AS DTAtype, TempWDTA AS WDTAtype
COMMON SHARED TreeWDTA AS WDTAtype, FileWDTA AS WDTAtype

' declare registers
COMMON SHARED InregsX AS RegtypeX, OutregsX AS RegtypeX
COMMON SHARED InregsX2 AS RegTypeX, InregsX3 AS RegTypeX

' declare work variables
COMMON SHARED Files.Counter AS INTEGER, Quit.Searching AS INTEGER
COMMON SHARED List.Archive AS INTEGER, List.Hidden AS INTEGER
COMMON SHARED List.Readonly AS INTEGER, List.System AS INTEGER
COMMON SHARED List.Directory AS INTEGER, List.Lowercase AS INTEGER
COMMON SHARED List.Drive AS INTEGER, List.Filename AS INTEGER
COMMON SHARED List.Volume AS INTEGER

COMMON SHARED No.List.Archive AS INTEGER, No.List.Hidden AS INTEGER
COMMON SHARED No.List.Readonly AS INTEGER, No.List.System AS INTEGER
COMMON SHARED No.List.Directory AS INTEGER

' declare nest recursion variables
COMMON SHARED Nested.Recurse AS INTEGER, Nested.Levels AS INTEGER

' declare search work variables
COMMON SHARED Continuous.Display AS INTEGER, Display.Descrip AS INTEGER
COMMON SHARED Recurse.Directories AS INTEGER, Redirected.Input AS INTEGER
COMMON SHARED Display.Filenames AS INTEGER, Lines.Counted AS INTEGER
COMMON SHARED More.Display AS INTEGER, Display.Errors AS INTEGER

' initialize drive work variables
COMMON SHARED Drive.Number AS INTEGER, Dirs.Counted AS INTEGER
COMMON SHARED Search.Drive AS INTEGER, Files.Counted AS INTEGER
COMMON SHARED Volumes.Counted AS INTEGER, Windows.Detected AS INTEGER

' declare command line work variables
COMMON SHARED Command.Line AS STRING, Command.Line.Redirect AS STRING
COMMON SHARED Command.Work AS STRING, Control.Break AS INTEGER
COMMON SHARED Last.Switch AS INTEGER, Pipe.Buffer AS STRING * 1

' declare external procedures
DECLARE SUB SetInt
DECLARE SUB RestInt

' backwards compatible for bc 7.1
REM $INCLUDE: 'bc7.inc'

' increase stack size
STACK STACK

' install new interrupt service routine
CALL SetInt

' declare standard error trap
ON ERROR GOTO Error.Routine

' command line parser
FUNCTION ParseLine (X$)
 Imbedded = INSTR(Command.Line, LCASE$(X$))
 IF Imbedded THEN
    Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + LEN(X$))
    Last.Switch = Imbedded - 1
    ParseLine = True
 ELSE
    Imbedded = INSTR(Command.Line, UCASE$(X$))
    IF Imbedded THEN
       Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + LEN(X$))
       Last.Switch = Imbedded - 1
       ParseLine = True
    ELSE
       ParseLine = False
    END IF
 END IF
END FUNCTION 

' store basic dta
InregsX.AX = &H2F00
CALL InterruptX(&H21, InregsX, OutregsX)
BASIC.DTA.SEG = OutregsX.ES
BASIC.DTA.OFF = OutregsX.BX

' get current drive
InregsX.AX = &H1900
CALL InterruptX(&H21, InregsX, OutregsX)
Current.Drive$ = CHR$((OutregsX.AX AND &HFF) + 65)

' check windows
Windows.Detected = True
If Load.Windows = False Then
   InregsX.AX = &H160A
   CALL InterruptX(&H2F, InregsX, OutregsX)
   IF OutregsX.AX > 0 THEN
      InregsX.AX = &H4A33
      CALL InterruptX(&H2F, InregsX, OutregsX)
      IF OutregsX.AX = 0 THEN
         Windows.Detected = 0 ' DOS 7.00
      END IF
   END IF
END IF
IF INSTR(COMMAND$, "/_") THEN
   Windows.Detected = True
END IF

' check windows dos
IF Windows.Detected THEN
   ' get current directory
   InregsX.AX = &H7147
   InregsX.DX = ASC(Current.Drive$) - 64
   InregsX.DS = VARSEG(Directory.ASCIIZ)
   InregsX.SI = VARPTR(Directory.ASCIIZ)
   CALL InterruptX(&H21, InregsX, OutregsX)
ELSE
   ' get current directory
   InregsX.AX = &H4700
   InregsX.DX = ASC(Current.Drive$) - 64
   InregsX.DS = VARSEG(Directory.ASCIIZ)
   InregsX.SI = VARPTR(Directory.ASCIIZ)
   CALL InterruptX(&H21, InregsX, OutregsX)
END IF

' display any errors
CALL DisplayError ("Error accessing drive.")

' store directory
Directory.ASCIIZ = "\" + RTRIM$(Directory.ASCIIZ) + CHR$(0)

' check command line
SELECT CASE COMMAND$
CASE "/?"
   GOTO Boot.Usage
END SELECT

' read command line from PSP
Command.Line = NUL
InregsX.AX = &H6200
CALL InterruptX(&H21, InregsX, OutregsX)
PSPsegment = OutregsX.BX
PSPoffset = 128
DEF SEG = PSPsegment
FOR Count = 1 TO 127
   Command.Char = PEEK(PSPoffset + Count)
   SELECT CASE Command.Char
   CASE 0, 10, 13
      EXIT FOR
   CASE ELSE
      Command.Line = Command.Line + CHR$(Command.Char)
   END SELECT
NEXT
DEF SEG
IF Command.Line = NUL THEN
   Command.Line = ENVIRON$("DIRATTR")
END IF
Command.Line = RTRIM$(Command.Line)

' get command line switches
No.List.Archive = ParseLine ("//A")
No.List.Hidden = ParseLine ("//H")
No.List.Directory = ParseLine ("//I")
No.List.Readonly = ParseLine ("//O")
No.List.System = ParseLine ("//S")
List.Archive = ParseLine ("/A")
List.Hidden = ParseLine ("/H")
List.Directory = ParseLine ("/I")
List.Readonly = ParseLine ("/O")
List.System = ParseLine ("/S")
List.Volume = ParseLine ("/V")
List.Drive = ParseLine ("/D")
List.Filename = ParseLine ("/E")
Continuous.Display = ParseLine ("/C")
List.Lowercase = ParseLine ("/U")
Recurse.Directories = ParseLine ("/R")
Display.Filenames = ParseLine ("/X")
Display.Descrip = ParseLine ("/Y")
Display.Errors = ParseLine ("/Z")
Control.Break = ParseLine ("/~")
More.Display = Continuous.Display
Var = ParseLine("/_")

' get nested switch from command line
Imbedded = INSTR(UCASE$(Command.Line), "/N")
IF Imbedded THEN
   Last.Switch = Imbedded - 1
   Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + 2)
   GOSUB Get.Numeric
   Nested.Recurse = CINT(Var#)
END IF

' recheck command line
IF INSTR(Command.Line, "/") THEN
   GOTO Boot.Error
END IF
Command.Line = RTRIM$(Command.Line)
IF Last.Switch THEN
   IF LEN(Command.Line) > Last.Switch THEN
      GOTO Boot.Error
   END IF
END IF

' reset count variables
Dirs.Counted = False
Files.Counted = False
Volumes.Counted = False

' remove blanks from command line
Command.Line = RTRIM$(Command.Line)
Command.Line = LTRIM$(Command.Line)
Command.Line.Redirect = Command.Line

' check break flag override
IF Control.Break THEN
   Var = ClearBreak
END IF

' search through all input filenames
Redirected.Input = False

' check pipe length
InregsX.AX = &H4202 ' eof
InregsX.BX = 0 ' stdin
InregsX.CX = 0
InregsX.DX = 0
Call InterruptX(&H21, InregsX, OutregsX)
If OutregsX.AX > 0 Then
   Pipe.Redirect = True
   InregsX.AX = &H4200
   InregsX.BX = 0 ' stdin
   InregsX.CX = 0
   InregsX.DX = 0
   Call InterruptX(&H21, InregsX, OutregsX)
Endif
If Pipe.Redirect = False Then
   DEF SEG = &H40
   X = PEEK(&H71)
   DEF SEG
   IF X = 64 THEN
      DEF SEG = &H40
      POKE &H71, 0
      DEF SEG
   END IF
Endif
DO
   ' check control break
   IF BreakIS THEN
      EXIT DO
   END IF

   ' get standard input
   Standard.Input$ = NUL

   If Pipe.Redirect Then
      DO

         ' read from device
         InregsX.AX = &H3F00
         InregsX.BX = 0 ' stdin
         InregsX.CX = 1 ' char
         InregsX.DS = VARSEG(Pipe.Buffer)
         InregsX.DX = VARPTR(Pipe.Buffer)
         Call InterruptX(&H21, InregsX, OutregsX)
         If (OutregsX.Flags AND &H1) = &H1 Then
            Exit Do
         Endif
         If (OutregsX.Flags AND &H1) = &H0 Then
            If OutregsX.AX = 0 Then
               Exit Do
            Endif

            ' store input flag
            Redirected.Input = True

            ' store character
            Char$ = Pipe.Buffer

            ' check character
            SELECT CASE ASC(Char$)
            CASE 10, 26
            CASE 13
               EXIT DO
            CASE ELSE
               Standard.Input$ = Standard.Input$ + Char$
            END SELECT
         END IF
      LOOP
   END IF

   ' clear break flag
   IF Redirected.Input = False THEN
      IF Cleared = False THEN
         Cleared = True
         Var = ClearBreak
      END IF
   END IF

   ' check control break
   IF BreakIS THEN
      EXIT DO
   END IF

   ' check nul filename input
   IF Redirected.Input = False THEN
      IF Standard.Input$ = NUL THEN
         CALL RestInt ' restore Control-Break
         X$ = Inkey$ ' quits here
         CALL SetInt ' reset Control-Break
         IF X$ = CHR$(0) + CHR$(0) THEN
            EXIT DO
         END IF
      END IF
   END IF

   ' check standard input
   IF Redirected.Input THEN
      IF Standard.Input$ = NUL THEN
	 EXIT DO
      END IF
   END IF

   ' display header
   GOSUB Header

   ' store entire command
   Command.Line = Command.Line.Redirect

   ' filename processing loop
   DO
      ' store redirected input
      Standard.Input$ = RTRIM$(Standard.Input$)
      Standard.Input$ = LTRIM$(Standard.Input$)
      IF LEFT$(Standard.Input$, 1) = CHR$(34) THEN
         Standard.Input$ = MID$(Standard.Input$, 2)
      END IF
      IF RIGHT$(Standard.Input$, 1) = CHR$(34) THEN
         Standard.Input$ = LEFT$(Standard.Input$, LEN(Standard.Input$) - 1)
      END IF

      ' store entire command
      IF LEFT$(Command.Line, 1) = CHR$(34) THEN
         Imbedded = INSTR(2, Command.Line, CHR$(34))
         IF Imbedded THEN
            Command.Work = Standard.Input$ + MID$(Command.Line, 2, Imbedded - 2)
            Command.Line = MID$(Command.Line, Imbedded + 1)
         ELSE
            Command.Work = Standard.Input$ + Command.Line
            Command.Line = NUL
         END IF
      ELSE
         Imbedded = INSTR(Command.Line, " ")
         IF Imbedded THEN
            Command.Work = Standard.Input$ + LEFT$(Command.Line, Imbedded - 1)
            Command.Line = MID$(Command.Line, Imbedded + 1)
         ELSE
            Command.Work = Standard.Input$ + Command.Line
            Command.Line = NUL
         END IF
      END IF
      Command.Line = LTRIM$(Command.Line)
      Command.Line = RTRIM$(Command.Line)

      ' store current drive
      IF MID$(Command.Work, 2, 1) = ":" THEN
         Drive.Search = LEFT$(Command.Work, 1)
         Command.Work = MID$(Command.Work, 3)
      ELSE
	 Drive.Search = Current.Drive$
      END IF
      Drive.Search = UCASE$(Drive.Search)

      ' check windows dos
      IF Windows.Detected THEN
         ' get current directory
         InregsX.AX = &H7147
         InregsX.DX = ASC(Drive.Search) - 64
         InregsX.DS = VARSEG(ASCIIZ)
         InregsX.SI = VARPTR(ASCIIZ)
         CALL InterruptX(&H21, InregsX, OutregsX)
      ELSE
         ' get current directory
         InregsX.AX = &H4700
         InregsX.DX = ASC(Drive.Search) - 64
         InregsX.DS = VARSEG(ASCIIZ)
         InregsX.SI = VARPTR(ASCIIZ)
         CALL InterruptX(&H21, InregsX, OutregsX)
      END IF

      ' display any errors
      CALL DisplayError ("Error accessing drive.")

      ' check carry flag error
      IF (OutregsX.Flags AND &H1) = &H0 THEN

         ' store current directory
         Directory.Search$ = "\" + LEFT$(ASCIIZ, INSTR(ASCIIZ, CHR$(0)) - 1)
         Imbedded1 = INSTR(Command.Work, "\")
         Imbedded2 = Imbedded1
         WHILE Imbedded1
            Imbedded2 = Imbedded1
            Imbedded1 = INSTR(Imbedded1 + 1, Command.Work, "\")
         WEND
         IF Imbedded2 THEN
            Directory.Search$ = LEFT$(Command.Work, Imbedded2)
            Command.Work = MID$(Command.Work, Imbedded2 + 1)
         END IF
         IF RIGHT$(Directory.Search$, 1) <> "\" THEN
            Directory.Search$ = Directory.Search$ + "\"
         END IF

         ' get filename spec
         Filename.Search$ = Command.Work
         IF Filename.Search$ = NUL THEN
            Filename.Search$ = "*.*"
         END IF
         Command.Work = NUL
   
         ' change to drive
         InregsX.AX = &HE00
         InregsX.DX = ASC(Drive.Search) - 65
         CALL InterruptX(&H21, InregsX, OutregsX)
   
         ' display any errors
         CALL DisplayError ("Error accessing drive.")

         ' check carry flag error
         IF (OutregsX.Flags AND &H1) = &H0 THEN

            ' display search filename
            IF Continuous.Display = False THEN
               COLOR Yellow, Black
               IF LEFT$(Directory.Search$, 2) = "\\" THEN
                  PRINT "Searching: " + Directory.Search$ + Filename.Search$
               ELSE
                  PRINT "Searching: " + Drive.Search + ":" + Directory.Search$ + Filename.Search$
               END IF
               Lines.Counted = Lines.Counted + 1
            END IF

            ' call routine to search for files
            CALL Directories(Directory.Search$, Filename.Search$)

            ' restore current drive
            InregsX.AX = &HE00
            InregsX.DX = ASC(Current.Drive$) - 65
            CALL InterruptX(&H21, InregsX, OutregsX)

            ' check windows dos
            IF Windows.Detected THEN
               ' restore current directory
               InregsX.AX = &H713B
               InregsX.DS = VARSEG(Directory.ASCIIZ)
               InregsX.DX = VARPTR(Directory.ASCIIZ)
               CALL InterruptX(&H21, InregsX, OutregsX)
            ELSE
               ' restore current directory
               InregsX.AX = &H3B00
               InregsX.DS = VARSEG(Directory.ASCIIZ)
               InregsX.DX = VARPTR(Directory.ASCIIZ)
               CALL InterruptX(&H21, InregsX, OutregsX)
            END IF
         END IF
      END IF

      ' check search filename
      IF Command.Line = NUL THEN
         EXIT DO
      END IF

      ' check quit searching
      IF Quit.Searching THEN
	 EXIT DO
      END IF
   LOOP

   ' check for more filenames
   IF Standard.Input$ = NUL THEN
      EXIT DO
   END IF

   ' check quit searching flag
   IF Quit.Searching THEN
      EXIT DO
   END IF
LOOP

End.Dirattr:

' restore basic dta
InregsX.AX = &H1A00
InregsX.DS = BASIC.DTA.SEG
InregsX.DX = BASIC.DTA.OFF
CALL InterruptX(&H21, InregsX, OutregsX)

' restore current drive
InregsX.AX = &HE00
InregsX.DX = ASC(Current.Drive$) - 65
CALL InterruptX(&H21, InregsX, OutregsX)

' check windows dos
IF Windows.Detected THEN
   ' restore current directory
   InregsX.AX = &H713B
   InregsX.DS = VARSEG(Directory.ASCIIZ)
   InregsX.DX = VARPTR(Directory.ASCIIZ)
   CALL InterruptX(&H21, InregsX, OutregsX)
ELSE
   ' restore current directory
   InregsX.AX = &H3B00
   InregsX.DS = VARSEG(Directory.ASCIIZ)
   InregsX.DX = VARPTR(Directory.ASCIIZ)
   CALL InterruptX(&H21, InregsX, OutregsX)
END IF

' display counters
IF Continuous.Display = False THEN
   COLOR Yellow, Black
   IF List.Volume THEN
      PRINT "Volumes counted"; Volumes.Counted
   ELSE
      PRINT "Directories counted"; Dirs.Counted
      PRINT "Files counted"; Files.Counted
   END IF
   Prompt$ = "Press <enter> to exit to DOS:"
   CALL MorePrompt(Prompt$, CHR$(13), Outpt$)
END IF

' restore key trapping
CALL RestInt

COLOR Plain, Black
END

Boot.Usage:
 ' restore key trapping
 CALL RestInt
 Var$=Inkey$
 ' make header
 COLOR White, Black
 PRINT "Dirattr v1.4a: File/directory attribute display utility; "
 COLOR Yellow, Black
 PRINT "Usage:"
 PRINT "   Dirattr [[d:\path\]filename.ext][//ahiosv][/cdenruxyz]"
 PRINT "Where:"
 PRINT "   Don't list files with bits:"
 PRINT "     //a  archive  //h  hidden  //i  directory  //o  read-only
 PRINT "     //s  system"
 PRINT "   Only list files with bits:"
 PRINT "     /a  archive   /h  hidden   /i  directory   /o  read-only
 PRINT "     /s  system    /v  volume"
 PRINT "   Remaining switches:"
 PRINT "     /c  continuous display     /u  display files in lowercase
 PRINT "     /d  prepend drive letter   /x  don't display filename"
 PRINT "     /e  don't display drive    /y  don't display attributes 
 PRINT "     /r  recurse directories    /z  don't display errors"
 PRINT "     /n### override nested directories"
 COLOR Plain, Black
 END

Get.Numeric:
 Var# = False
 DO
    Temp$ = MID$(Command.Line, Imbedded, 1)
    IF Temp$ >= "0" AND Temp$ <= "9" THEN
       Var# = Var# * 10 + VAL(Temp$)
       Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + 1)
    ELSE
       EXIT DO
    END IF
 LOOP
 RETURN

Boot.Error:
 CALL RestInt
 Var$=Inkey$
 COLOR White, Black
 PRINT "Command line error. Type Dirattr /? for help."
 COLOR Plain, Black
 END

' make header
Header:
 IF Header.Flag THEN
    RETURN
 END IF
 Header.Flag = True
 IF Continuous.Display = False THEN
    COLOR White, Black
    PRINT "Dirattr v1.4a: File/directory attribute display utility;"
 END IF
 RETURN

' critical error trap
Error.Routine:
 Data.Error = ERR
 IF Display.Errors THEN
    Error.Level = True
    OutregsX.Flags = &H1
    RESUME NEXT
 END IF
 SELECT CASE Data.Error
 CASE 53
    Temp.Outpt$ = "File not found."
 CASE 61
    Temp.Outpt$ = "Disk full."
 CASE 70
    Temp.Outpt$ = "Permission denied."
 CASE 71
    Temp.Outpt$ = "Disk not ready."
 CASE ELSE
    Temp.Outpt$ = "Untrapped error" + STR$(Data.Error) + "."
 END SELECT
 COLOR Green, Black
 PRINT Temp.Outpt$
 Prompt$ = "Press R to retry, Q to quit, C to continue:"
 CALL MorePrompt(Prompt$, "rqc", Outpt$)
 IF BreakIS THEN
    Outpt$ = "q"
 END IF
 SELECT CASE Outpt$
 CASE "r"
    RESUME
 CASE "q"
    Error.Level = True
    RESUME End.Dirattr
 CASE "c"
    OutregsX.Flags = &H1
    RESUME NEXT
 END SELECT
 COLOR Plain, Black
 ' restore key trapping
 CALL RestInt
 END 0

' subroutine to access directories
SUB Directories (Directory.Search$, Filename.Search$)
 ' declare subroutine variables
 DIM Attribute AS INTEGER
 DIM DTAfile AS DTAtype
 DIM Wfile.Handle AS INTEGER

 ' make directory filename
 IF List.Volume THEN
    ASCIIZ.Sub = "\*.*" + CHR$(0)
 ELSE
    ASCIIZ.Sub = Directory.Search$ + "*.*" + CHR$(0)
 END IF
 GOSUB Restore.DTA

 ' find first directory
 IF List.Volume THEN
    InregsX.AX = &H4E00
    InregsX.CX = &H08
    InregsX.DS = VARSEG(ASCIIZ.Sub)
    InregsX.DX = VARPTR(ASCIIZ.Sub)
    CALL InterruptX(&H21, InregsX, OutregsX)
 ELSE
    IF Windows.Detected THEN
       InregsX.AX = &H714E
       InregsX.CX = &H37
       InregsX.SI = &H1
       InregsX.DS = VARSEG(ASCIIZ.Sub)
       InregsX.DX = VARPTR(ASCIIZ.Sub)
       InregsX.ES = VARSEG(TreeWDTA)
       InregsX.DI = VARPTR(TreeWDTA)
       CALL InterruptX(&H21, InregsX, OutregsX)
       Wfile.Handle = OutregsX.AX
    ELSE
       InregsX.AX = &H4E00
       InregsX.CX = &H37
       InregsX.DS = VARSEG(ASCIIZ.Sub)
       InregsX.DX = VARPTR(ASCIIZ.Sub)
       CALL InterruptX(&H21, InregsX, OutregsX)
    END IF
 END IF

 ' check find first error
 IF (OutregsX.Flags AND &H1) = &H1 THEN
    EXIT SUB
 END IF

 ' search directory names
 CALL Filenames(Directory.Search$, Filename.Search$)
 GOSUB Restore.DTA

 ' check to recurse directories
 IF Recurse.Directories THEN

    ' recurse directories
    DO
       ' check control break
       IF BreakIS THEN
          Quit.Searching = True
       END IF

       ' check to quit
       IF Quit.Searching THEN
          EXIT DO
       END IF

       ' check directory attribute
       IF Windows.Detected THEN
          Attribute = ASC(TreeWDTA.FileBits)
       ELSE
          Attribute = ASC(DTAfile.FileBits)
       END IF

       ' check for directory
       IF (Attribute AND &H10) = &H10 THEN

          ' store directory name
          IF Windows.Detected THEN
             Directory$ = TreeWDTA.ASCIIZfull
          ELSE
             Directory$ = DTAfile.ASCIIZfilename
          END IF
          Directory$ = LEFT$(Directory$, INSTR(Directory$, CHR$(0)) - 1)

          ' check directory name
          IF Directory$ <> "." AND Directory$ <> ".." THEN

             ' make next search directory
             Next.Directory$ = Directory.Search$ + Directory$ + "\"

             ' check recursion levels
             Recursion% = True
             IF Nested.Recurse > False THEN
                Nested.Levels = Nested.Levels + 1
                IF Nested.Levels >= Nested.Recurse THEN
                   Recursion% = False
                END IF
             END IF

             ' recursively search subdirectories
             IF Recursion% THEN
                CALL Directories(Next.Directory$, Filename.Search$)
             END IF
             IF Nested.Recurse > False THEN
                Nested.Levels = Nested.Levels - 1
             END IF

             ' restore dta
             GOSUB Restore.DTA
          END IF
       END IF

       ' check windows dos
       IF Windows.Detected THEN
          ' find next long filename
          InregsX.AX = &H714F
          InregsX.BX = Wfile.Handle
          InregsX.SI = &H1
          InregsX.ES = VARSEG(TreeWDTA)
          InregsX.DI = VARPTR(TreeWDTA)
          CALL InterruptX(&H21, InregsX, OutregsX)
       ELSE
          ' find next directory
          InregsX.AX = &H4F00
          CALL InterruptX(&H21, InregsX, OutregsX)
       END IF

       ' check findnext error
       IF (OutregsX.Flags AND &H1) = &H1 THEN
          EXIT DO
       END IF
    LOOP
 END IF

 ' check windows dos
 IF Windows.Detected THEN
    ' close long filename search
    InregsX.AX = &H71A1
    InregsX.BX = Wfile.Handle
    CALL InterruptX(&H21, InregsX, OutregsX)
 END IF
 EXIT SUB

Restore.DTA:
 ' restore directory search dta
 InregsX.AX = &H1A00
 InregsX.DS = VARSEG(DTAfile)
 InregsX.DX = VARPTR(DTAfile)
 CALL InterruptX(&H21, InregsX, OutregsX)
 RETURN
END SUB

' subroutine to access filenames in directory
SUB Filenames (Directory.Search$, Filename.Search$)
 ' declare subroutine variables
 DIM Attribute AS INTEGER
 DIM DTAfile AS DTAtype
 DIM Wfile.Handle AS INTEGER

 ' make filename
 IF List.Volume THEN
    ASCIIZ.File = "\*.*" + CHR$(0)
 ELSE
    ASCIIZ.File = Directory.Search$ + Filename.Search$ + CHR$(0)
 END IF

 ' store dta
 InregsX.AX = &H1A00
 InregsX.DS = VARSEG(DTAfile)
 InregsX.DX = VARPTR(DTAfile)
 CALL InterruptX(&H21, InregsX, OutregsX)

 ' find first filename
 IF List.Volume THEN
    InregsX.AX = &H4E00
    InregsX.CX = &H08
    InregsX.DS = VARSEG(ASCIIZ.File)
    InregsX.DX = VARPTR(ASCIIZ.File)
    CALL InterruptX(&H21, InregsX, OutregsX)
 ELSE
    IF Windows.Detected THEN
       InregsX.AX = &H714E
       InregsX.CX = &H37
       InregsX.SI = &H1
       InregsX.DS = VARSEG(ASCIIZ.File)
       InregsX.DX = VARPTR(ASCIIZ.File)
       InregsX.ES = VARSEG(FileWDTA)
       InregsX.DI = VARPTR(FileWDTA)
       CALL InterruptX(&H21, InregsX, OutregsX)
       Wfile.Handle = OutregsX.AX
    ELSE
       InregsX.AX = &H4E00
       InregsX.CX = &H37
       InregsX.DS = VARSEG(ASCIIZ.File)
       InregsX.DX = VARPTR(ASCIIZ.File)
       CALL InterruptX(&H21, InregsX, OutregsX)
    END IF
 END IF

 ' check find first error
 IF (OutregsX.Flags AND &H1) = &H1 THEN
    EXIT SUB
 END IF

 ' search filenames
 DO
    ' store filename
    IF List.Volume THEN
       Filename$ = DTAfile.ASCIIZfilename
    ELSE
       IF Windows.Detected THEN
          Filename$ = FileWDTA.ASCIIZfull
       ELSE
          Filename$ = DTAfile.ASCIIZfilename
       END IF
    END IF
    Filename$ = LEFT$(Filename$, INSTR(Filename$, CHR$(0)) - 1)

    ' check filename
    IF Filename$ <> "." AND Filename$ <> ".." THEN

       ' display filename
       IF Windows.Detected THEN
          TempWDTA = FileWDTA
       ELSE
          TempDTA = DTAfile
       END IF
       CALL DisplayFiles(Directory.Search$, Filename$)

       ' restore dta
       GOSUB Restore.FDTA
    END IF

    ' check control break
    IF BreakIS THEN
       Quit.Searching = True
    END IF

    ' check to quit
    IF Quit.Searching THEN
       EXIT DO
    END IF

    ' check windows dos
    IF Windows.Detected THEN
       ' find next long filename
       InregsX.AX = &H714F
       InregsX.BX = Wfile.Handle
       InregsX.SI = &H1
       InregsX.ES = VARSEG(FileWDTA)
       InregsX.DI = VARPTR(FileWDTA)
       CALL InterruptX(&H21, InregsX, OutregsX)
    ELSE
       ' find next filename
       InregsX.AX = &H4F00
       CALL InterruptX(&H21, InregsX, OutregsX)
    END IF

    ' check find first error
    IF (OutregsX.Flags AND &H1) = &H1 THEN
       EXIT DO
    END IF
 LOOP

 ' check windows dos
 IF Windows.Detected THEN
    ' close long filename search
    InregsX.AX = &H71A1
    InregsX.BX = Wfile.Handle
    CALL InterruptX(&H21, InregsX, OutregsX)
 END IF
 EXIT SUB

Restore.FDTA:
 ' restore directory search dta
 InregsX.AX = &H1A00
 InregsX.DS = VARSEG(DTAfile)
 InregsX.DX = VARPTR(DTAfile)
 CALL InterruptX(&H21, InregsX, OutregsX)
 RETURN
END SUB

' subroutine to display a filename description
SUB DisplayFiles (Search.Directory$, Display.Filename$)
 ' declare subroutine variables
 DIM Attribute AS INTEGER
 DIM DTAfile AS DTAtype

 ' restore directory search dta
 InregsX.AX = &H1A00
 InregsX.DS = VARSEG(DTAfile)
 InregsX.DX = VARPTR(DTAfile)
 CALL InterruptX(&H21, InregsX, OutregsX)

 ' make filename
 Search.Filename$ = Search.Directory$ + Display.Filename$
 ASCIIZ.Display = Search.Filename$ + CHR$(0)

 ' check file attribute
 IF List.Volume THEN

    ' make volume label search
    IF LEFT$(Search.Directory$, 2) = "\\" THEN
       IF RIGHT$(Search.Directory$, 1) = "\" THEN
          ASCIIZ = Search.Directory$ + "*.*" + CHR$(0)
       ELSE
          ASCIIZ = Search.Directory$ + "\*.*" + CHR$(0)
       END IF
    ELSE
       ASCIIZ = "\*.*" + CHR$(0)
    END IF

    ' find first directory
    InregsX.AX = &H4E00
    InregsX.CX = &H08
    InregsX.DS = VARSEG(ASCIIZ)
    InregsX.DX = VARPTR(ASCIIZ)
    CALL InterruptX(&H21, InregsX, OutregsX)

    ' store volume label
    ASCIIZ = DTAfile.ASCIIZfilename

    ' store volume label attribute
    Attribute = &H08
 ELSE

    ' check windows dos
    OutregsX.Flags = &H0
    IF Windows.Detected THEN
       ' get file attributes
       InregsX.AX = &H7143
       InregsX.BX = &H0
       InregsX.DS = VARSEG(ASCIIZ.Display)
       InregsX.DX = VARPTR(ASCIIZ.Display)
       CALL InterruptX(&H21, InregsX, OutregsX)
    ELSE
       ' get file attributes
       InregsX.AX = &H4300
       InregsX.DS = VARSEG(ASCIIZ.Display)
       InregsX.DX = VARPTR(ASCIIZ.Display)
       CALL InterruptX(&H21, InregsX, OutregsX)
    END IF

    ' store file attribute
    Attribute = OutregsX.CX
 END IF

 ' display any errors
 Var = INSTR(ASCIIZ.Display, CHR$(0))
 Var$ = ASCIIZ.Display
 IF Var THEN
    Var$ = LEFT$(Var$, Var - 1)
 ELSE
    Var$ = ASCIIZ.Display
 END IF
 Var$ = RTRIM$(Var$)
 FOR Var = LEN(Var$) TO 1 STEP -1
    IF MID$(Var$, Var, 1) = "\" THEN
       Var$ = MID$(Var$, Var + 1)
       EXIT FOR
    END IF
 NEXT
 CALL DisplayError ("Error"+Str$(OutregsX.AX)+" reading filename attributes: "+Var$)

 ' check carry flag error
 IF (OutregsX.Flags AND &H1) = &H0 THEN

    ' count files displayed
    IF List.Volume THEN
       Volumes.Counted = Volumes.Counted + 1
    END IF

    ' store list work variable
    List.File = True

    ' check list attribute
    IF List.Readonly THEN
       IF (Attribute AND &H1) = &H0 THEN
          List.File = False
       END IF
    END IF

    ' check list attribute
    IF List.Hidden THEN
       IF (Attribute AND &H2) = &H0 THEN
          List.File = False
       END IF
    END IF

    ' check list attribute
    IF List.System THEN
       IF (Attribute AND &H4) = &H0 THEN
          List.File = False
       END IF
    END IF

    ' check list attribute
    IF List.Directory THEN
       IF (Attribute AND &H10) = &H0 THEN
          List.File = False
       END IF
    END IF

    ' check list attribute
    IF List.Archive THEN
       IF (Attribute AND &H20) = &H0 THEN
          List.File = False
       END IF
    END IF

    ' check list attribute
    IF No.List.Readonly THEN
       IF (Attribute AND &H1) = &H1 THEN
          List.File = False
       END IF
    END IF

    ' check list attribute
    IF No.List.Hidden THEN
       IF (Attribute AND &H2) = &H2 THEN
          List.File = False
       END IF
    END IF

    ' check list attribute
    IF No.List.System THEN
       IF (Attribute AND &H4) = &H4 THEN
          List.File = False
       END IF
    END IF

    ' check list attribute
    IF No.List.Directory THEN
       IF (Attribute AND &H10) = &H10 THEN
          List.File = False
       END IF
    END IF

    ' check list attribute
    IF No.List.Archive THEN
       IF (Attribute AND &H20) = &H20 THEN
          List.File = False
       END IF
    END IF

    ' check volume display
    IF List.Volume THEN
       IF Display.Filenames = False THEN
          Volume.Label$ = ASCIIZ
          Imbedded = INSTR(Volume.Label$, CHR$(0))
          IF Imbedded THEN
             Volume.Label$ = LEFT$(Volume.Label$, Imbedded - 1)
          END IF
          Volume.Label$ = RTRIM$(Volume.Label$)
          IF List.Drive THEN
             PRINT Drive.Search; ":";
          END IF
          COLOR Yellow, Black
          Outpt$ = Volume.Label$
          IF List.Lowercase THEN
             PRINT LCASE$(Outpt$);
          ELSE
             PRINT UCASE$(Outpt$);
          END IF
          PRINT " ";
       END IF
       COLOR White, Black
       IF Display.Descrip = False THEN
          PRINT "Volume"
       ELSE
          PRINT
       END IF
       Lines.Counted = Lines.Counted + 1
       GOSUB More.Display3
       EXIT SUB
    END IF

    ' check list work variable
    IF List.File THEN

       ' count files displayed
       IF (Attribute AND &H10) = &H10 THEN
          Dirs.Counted = Dirs.Counted + 1
       ELSE
          Files.Counted = Files.Counted + 1
       END IF

       ' calculate lines counted
       IF Display.Filenames THEN
          IF Display.Descrip = False THEN
             Flag = False
             IF (Attribute AND &H1) = &H1 THEN ' read-only
                Flag = True
             END IF
             IF (Attribute AND &H2) = &H2 THEN ' hidden
                Flag = True
             END IF
             IF (Attribute AND &H4) = &H4 THEN ' system
                Flag = True
             END IF
             IF (Attribute AND &H10) = &H10 THEN ' directory
                Flag = True
             END IF
             IF (Attribute AND &H20) = &H20 THEN ' archive
                Flag = True
             END IF
             IF Flag THEN
                Lines.Counted = Lines.Counted + 1
             END IF
          END IF
       END IF

       ' display filename
       IF Display.Filenames = False THEN
          IF LEFT$(Search.Directory$, 2) <> "\\" THEN
             IF List.Filename = False THEN
                Outpt$ = Drive.Search + ":" + Search.Filename$
             ELSE
                Outpt$ = Search.Filename$
             END IF
          ELSE
             Outpt$ = Search.Filename$
          END IF
          Outpt$ = RTRIM$(Outpt$)

          ' calculate line length
          Outpt.Length = LEN(Outpt$)
          IF Display.Descrip = False THEN
             Outpt.Length = Outpt.Length + 1
             IF (Attribute AND &H1) = &H1 THEN
                Outpt.Length = Outpt.Length + LEN("Read-only ")
             END IF
             IF (Attribute AND &H2) = &H2 THEN
                Outpt.Length = Outpt.Length + LEN("Hidden ")
             END IF
             IF (Attribute AND &H4) = &H4 THEN
                Outpt.Length = Outpt.Length + LEN("System ")
             END IF
             IF (Attribute AND &H10) = &H10 THEN
                Outpt.Length = Outpt.Length + LEN("Directory ")
             END IF
             IF (Attribute AND &H20) = &H20 THEN
                Outpt.Length = Outpt.Length + LEN("Archive ")
             END IF
          END IF

          ' preprompt for more
          GOSUB More.Display2

          ' check to quit
          IF Quit.Searching THEN
             EXIT SUB
          END IF

          ' display filename
          COLOR Yellow, Black
          IF List.Lowercase THEN
             PRINT LCASE$(Outpt$);
          ELSE
             IF Windows.Detected THEN
                PRINT Outpt$;
             ELSE
                PRINT UCASE$(Outpt$);
             END IF
          END IF

          ' check suppress display
          IF Display.Descrip THEN
             PRINT

             ' prompt for more
             GOSUB More.Display3
             EXIT SUB
          END IF
          PRINT " ";
       END IF

       ' check suppress display
       IF Display.Descrip = False THEN

          ' display attributes
          COLOR White, Black
          Flag = False

          ' check for read-only file
          IF (Attribute AND &H1) = &H1 THEN
             PRINT "Read-only ";
             Flag = True
          END IF

          ' check for hidden file
          IF (Attribute AND &H2) = &H2 THEN
             PRINT "Hidden ";
             Flag = True
          END IF

          ' check for system file
          IF (Attribute AND &H4) = &H4 THEN
             PRINT "System ";
             Flag = True
          END IF

          ' check for directory file
          IF (Attribute AND &H10) = &H10 THEN
             PRINT "Directory ";
             Flag = True
          END IF

          ' check for archive file
          IF (Attribute AND &H20) = &H20 THEN
             PRINT "Archive ";
             Flag = True
          END IF

          ' check display
          IF Display.Filenames = False THEN
             PRINT
          ELSE
             IF Flag THEN
                PRINT
             END IF
          END IF

          ' prompt for more
          GOSUB More.Display3
       END IF
    END IF
 END IF
 EXIT SUB

' check to display more prompt before line list
More.Display2:

 ' check continuous display
 IF More.Display THEN
    RETURN
 END IF

 ' reset lines displayed
 Lines.Counted2 = False

 ' calculate length of display
 IF Outpt.Length > 240 THEN
    Lines.Counted = Lines.Counted + 4
    Lines.Counted2 = Lines.Counted2 + 4
 ELSE
    IF Outpt.Length > 160 THEN
       Lines.Counted = Lines.Counted + 3
       Lines.Counted2 = Lines.Counted2 + 3
    ELSE
       IF Outpt.Length > 80 THEN
          Lines.Counted = Lines.Counted + 2
          Lines.Counted2 = Lines.Counted2 + 2
       ELSE
          Lines.Counted = Lines.Counted + 1
          Lines.Counted2 = Lines.Counted2 + 1
       END IF
    END IF
 END IF

 ' check lines dislpayed
 IF Lines.Counted >= 23 THEN

    ' store lines displayed after prompt
    Lines.Counted = Lines.Counted2

    ' prompt for more
    COLOR Yellow, Black
    Prompt$ = "More(y/n/c)?"
    CALL MorePrompt(Prompt$, "ync", Outpt2$)
    IF BreakIS THEN
       Outpt2$ = "n"
    END IF
    SELECT CASE Outpt2$
    CASE "c"
       More.Display = True
    CASE "n"
       Quit.Searching = True
    END SELECT
 END IF
 RETURN

' check to display more prompt after line list
More.Display3:

 ' check continuous display
 IF More.Display THEN
    RETURN
 END IF

 ' check lines displayed
 IF Lines.Counted >= 23 THEN

    ' reset lines displayed
    Lines.Counted = False

    ' prompt for more
    COLOR Yellow, Black
    Prompt$ = "More(y/n/c)?"
    CALL MorePrompt(Prompt$, "ync", Outpt2$)
    IF BreakIS THEN
       Outpt2$ = "n"
    END IF
    SELECT CASE Outpt2$
    CASE "c"
       More.Display = True
    CASE "n"
       Quit.Searching = True
    END SELECT
 END IF
 RETURN
END SUB

SUB MorePrompt (Input.String$, Input.Mask$, Output.String$)
 COLOR White, Black
 PRINT Input.String$ + " ";
 Input.Char$ = NUL
 DO
    LOCATE , , 1
    InregsX2 = InregsX
    DO
       IF BreakIS THEN
          EXIT DO
       END IF
       IF KeyIS THEN
          IF OutregsX.AX <> 0 THEN
             InregsX.AX = &H0000
             CALL InterruptX(&H16, InregsX, OutregsX)
             Input.Char$=CHR$(OutregsX.AX AND &HFF)
             EXIT DO
          END IF
       END IF
    LOOP
    InregsX = InregsX2
    IF BreakIS THEN
       EXIT DO
    END IF
    IF LEN(Input.Char$) THEN
       Input.Char$ = LCASE$(Input.Char$)
       IF INSTR(Input.Mask$, Input.Char$) THEN
	  PRINT Input.Char$
	  Output.String$ = Input.Char$
	  EXIT DO
       END IF
    END IF
 LOOP
END SUB

' clears Control-Break flag
FUNCTION ClearBreak
 DEF SEG = &H40
 POKE &H71, &H0
 DEF SEG
 ClearBreak = True
END FUNCTION

' checks Control-Break flag
FUNCTION BreakIS
 STATIC Var AS INTEGER
 IF Redirected.Input THEN
    DEF SEG = &H40
    X = PEEK(&H71)
    DEF SEG
    IF X = 64 THEN
       Var = True
    END IF
 END IF
 IF KeyIS THEN
    IF OutregsX.AX = 0 THEN
       Var = True
       DEF SEG = &H40
       POKE &H71, 64
       DEF SEG
    END IF
 END IF
 IF Var THEN
    Continuous.Display = True
 END IF
 BreakIS = Var
END FUNCTION

' checks keyboard buffer
FUNCTION KeyIS
 InregsX3 = InregsX
 InregsX.AX = &H0100
 CALL InterruptX(&H16, InregsX, OutregsX)
 InregsX = InregsX3
 IF (OutregsX.Flags AND &H40) = &H40 THEN
    KeyIS = False
 ELSE
    KeyIS = True
 END IF
END FUNCTION

' displays carry flag error
SUB DisplayError (Temp$)
 ' check carry flag error
 IF (OutregsX.Flags AND &H1) = &H1 THEN
    ' check display errors flag
    IF Display.Errors = False THEN
       ' display error
       COLOR Red, Black
       PRINT Temp$
    END IF
 END IF
END SUB
