REM file: Stree.bas - Public Domain DOS Utility
REM Version 1.0a created 02/05/1995
REM Version 1.1a created 02/06/1995
REM Version 1.2a created 02/21/1997

REM Compiling with Microsoft BASIC Professional Development System 7.1:
REM    BC STREE/FS/X/O;
REM    LINK STREE,,,QBX/E;

' declare subroutines
DECLARE SUB Directories (D$)

' 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 Magenta = 12
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: 'dta2.bi'
REM $INCLUDE: 'fcb.bi'
REM $INCLUDE: 'wdta.bi'

' declare functions
DECLARE FUNCTION ParseLine (S$)

' declare program dta
DIM BASIC.DTA.SEG AS INTEGER, BASIC.DTA.OFF AS INTEGER

' declare registers
COMMON SHARED InregsX AS RegtypeX, OutregsX AS RegtypeX

' declare date/time variables
COMMON SHARED Search.From.Date AS SINGLE, Search.To.Date AS SINGLE
COMMON SHARED Search.From.Time AS SINGLE, Search.To.Time AS SINGLE
COMMON SHARED File.Work.Date AS SINGLE, File.Work.Time AS SINGLE
COMMON SHARED Creation.Time AS INTEGER, Access.Time AS INTEGER
COMMON SHARED Modified.Time AS INTEGER, File.Access.Date AS SINGLE

' declare work variables
COMMON SHARED Search.Archive AS INTEGER, Search.Hidden AS INTEGER
COMMON SHARED Search.Readonly AS INTEGER, Search.System AS INTEGER
COMMON SHARED Recurse.Directories AS INTEGER, Attribute AS INTEGER
COMMON SHARED Extended.List AS INTEGER, Display.Errors AS INTEGER
COMMON SHARED Continuous.Display AS INTEGER, Directories.Counted AS INTEGER
COMMON SHARED Display.Lines AS INTEGER, Display.Lowercase AS INTEGER
COMMON SHARED Quit.Searching AS INTEGER, More.Display AS INTEGER
COMMON SHARED Drive.Search AS STRING * 1, Current.Drive AS STRING * 1
COMMON SHARED Nested.Levels AS INTEGER, Windows.Detected AS INTEGER
COMMON SHARED Nested.Recurse AS INTEGER, Windows.DOS AS INTEGER
COMMON SHARED Short.Filenames AS INTEGER, Short.Display AS INTEGER
COMMON SHARED Wide.Display AS INTEGER, Display.Length AS INTEGER

' declare directory variables
COMMON SHARED ASCIZ AS STRING * 260, ASCIZ2 AS STRING * 260
COMMON SHARED ASCIZ3 AS STRING * 260, ASCIZ.Sub AS STRING * 260
COMMON SHARED Directory.ASCIZ AS STRING * 260

' declare disk transfer area structures
COMMON SHARED SWDTA AS WDTAtype, TreeWDTA AS WDTAtype
COMMON SHARED TempDTA AS DTAtype

' declare command line work variables
COMMON SHARED Command.Line AS STRING, Command.Line.Redirect AS STRING
COMMON SHARED Command.Work AS STRING

' increase stack size
STACK STACK

' 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$))
    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$))
       ParseLine = True
    ELSE
       ParseLine = False
    END IF
 END IF
END FUNCTION 

' reset counters
Directories.Counted = False
Display.Lines = False
More.Display = False
Nested.Levels = False
Quit.Searching = False

' check windows dos
InregsX.AX = &H160A
CALL InterruptX(&H2F, InregsX, OutregsX)
IF OutregsX.AX = False THEN
   Temp = (OutregsX.BX And &HFF00) / 256
   IF Temp >= 4 THEN
      Windows.Detected = True
   END IF
END IF
InregsX.AX = &H4A33
CALL InterruptX(&H2F, InregsX, OutregsX)
IF OutregsX.AX = False THEN
   Windows.DOS = True
END IF

' 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 = UCASE$(ENVIRON$("STREE"))
END IF

' check command line switches
Search.Archive = ParseLine ("/A")
Continuous.Display = ParseLine ("/C")
Extended.List = ParseLine ("/E")
Short.Display = ParseLine ("/F")
Search.Hidden = ParseLine ("/H")
Search.Readonly = ParseLine ("/O")
Search.System = ParseLine ("/S")
Recurse.Directories = ParseLine ("/R")
Display.Lowercase = ParseLine ("/Y")
Short.Filenames = ParseLine ("/V")
Wide.Display = ParseLine ("/W")
Display.Errors = ParseLine ("/Z")
IF Wide.Display THEN
   IF Extended.List THEN
      Wide.Display = False
   END IF
   IF Short.Display = False THEN
      Wide.Display = False
   END IF
   IF Windows.Detected THEN
      IF Short.Filenames = False THEN
         Wide.Display = False
      END IF
   ELSE
   END IF
END IF

' get date/time from command line
Search.From.Date = False
Search.To.Date = False
Search.From.Time = False
Search.To.Time = False
Imbedded = INSTR(UCASE$(Command.Line), "/D")
IF Imbedded THEN
   D$ = MID$(Command.Line, Imbedded + 2, 21)
   Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + 23)
   IF LEN(D$) <> 21 THEN
      GOTO Boot.Error
   END IF
   IF MID$(D$, 11, 1) <> "-" THEN
      GOTO Boot.Error
   END IF
   S$ = LEFT$(D$, 10)
   D1! = INT(VAL(MID$(S$, 1, 2)))
   D2! = INT(VAL(MID$(S$, 4, 2)))
   D3! = INT(VAL(MID$(S$, 7, 4)))
   Search.From.Date = ((D3! - 1980) * 512) + D1! * 32 + D2!
   S$ = RIGHT$(D$, 10)
   D1! = INT(VAL(MID$(S$, 1, 2)))
   D2! = INT(VAL(MID$(S$, 4, 2)))
   D3! = INT(VAL(MID$(S$, 7, 4)))
   Search.To.Date = ((D3! - 1980) * 512) + D1! * 32 + D2!
   IF Search.From.Date < False OR Search.To.Date < False THEN
      GOTO Boot.Error
   END IF
END IF
Imbedded = INSTR(UCASE$(Command.Line), "/T")
IF Imbedded THEN
   T$ = MID$(Command.Line, Imbedded + 2, 17)
   Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + 19)
   IF LEN(T$) <> 17 THEN
      GOTO Boot.Error
   END IF
   IF MID$(T$, 9, 1) <> "-" THEN
      GOTO Boot.Error
   END IF
   S$ = LEFT$(T$, 8)
   T1! = INT(VAL(MID$(S$, 1, 2)))
   T2! = INT(VAL(MID$(S$, 4, 2)))
   T3! = INT(VAL(MID$(S$, 7, 2)))
   Search.From.Time = T1! * 2048 + T2! * 32 + INT(T3! / 2)
   S$ = RIGHT$(T$, 8)
   T1! = INT(VAL(MID$(S$, 1, 2)))
   T2! = INT(VAL(MID$(S$, 4, 2)))
   T3! = INT(VAL(MID$(S$, 7, 2)))
   Search.To.Time = T1! * 2048 + T2! * 32 + INT(T3! / 2)
   IF Search.From.Time < False OR Search.To.Time < False THEN
      GOTO Boot.Error
   END IF
END IF

' get extended date\time switches
Creation.Time = ParseLine("/1")
Access.Time = ParseLine("/2")
Modified.Time = ParseLine("/3")
IF Creation.Time = False THEN
   IF Access.Time = False THEN
      IF Modified.Time = False THEN
         Creation.Time = True
      END IF
   END IF
END IF

' check command line switch
Imbedded = INSTR(UCASE$(Command.Line), "/N")
IF Imbedded THEN
   Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + 2)
   GOSUB Get.Numeric
   Nested.Recurse = Var%
END IF

' recheck command line
IF INSTR(Command.Line, "/") THEN
   GOTO Boot.Usage
END IF

' 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 dos
IF Windows.Detected THEN
   ' get current directory
   InregsX.AX = &H7147
   InregsX.DX = ASC(Current.Drive) - 64
   InregsX.DS = VARSEG(Directory.ASCIZ)
   InregsX.SI = VARPTR(Directory.ASCIZ)
   CALL InterruptX(&H21, InregsX, OutregsX)
ELSE
   ' get current directory
   InregsX.AX = &H4700
   InregsX.DX = ASC(Current.Drive) - 64
   InregsX.DS = VARSEG(Directory.ASCIZ)
   InregsX.SI = VARPTR(Directory.ASCIZ)
   CALL InterruptX(&H21, InregsX, OutregsX)
END IF

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

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

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

' make header
IF Continuous.Display = False THEN
   COLOR White, Black
   PRINT "Stree v1.2a: Directory search utility;"
END IF

' search through all input filenames
DO
   ' get standard input
   Redirected.Input = False
   Standard.Input$ = NUL
   InregsX.AX = &HB00
   CALL InterruptX(&H21, InregsX, OutregsX)
   DO WHILE (OutregsX.AX AND &HFF) = &HFF
      Redirected.Input = True
      InregsX.AX = &H800
      CALL InterruptX(&H21, InregsX, OutregsX)
      Char$ = CHR$(OutregsX.AX AND &HFF)
      SELECT CASE ASC(Char$)
      CASE 10, 26
      CASE 13
	 EXIT DO
      CASE ELSE
	 Standard.Input$ = Standard.Input$ + Char$
      END SELECT
      InregsX.AX = &HB00
      CALL InterruptX(&H21, InregsX, OutregsX)
   LOOP

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

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

   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(ASCIZ)
         InregsX.SI = VARPTR(ASCIZ)
         CALL InterruptX(&H21, InregsX, OutregsX)
      ELSE
         ' get current directory
         InregsX.AX = &H4700
         InregsX.DX = ASC(Drive.Search) - 64
         InregsX.DS = VARSEG(ASCIZ)
         InregsX.SI = VARPTR(ASCIZ)
         CALL InterruptX(&H21, InregsX, OutregsX)
      END IF

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

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

         ' store current directory
         Directory$ = LEFT$(ASCIZ, INSTR(ASCIZ, CHR$(0)) - 1)
         IF Directory$ = "\" THEN
            Directory.Search$ = "\" 
         ELSE
            Directory.Search$ = "\" + Directory$
         END IF
         IF Command.Work <> NUL THEN
            IF Directory.Search$ = "\" + Command.Work THEN
               Directory.Search$ = "\" + Command.Work
            ELSE
               IF LEFT$(Command.Work, 1) <> "\" THEN
                  IF Directory.Search$ = "\" THEN
                     Directory.Search$ = "\" + Command.Work
                  ELSE
                     Directory.Search$ = Directory.Search$ + "\" + Command.Work
                  END IF
               ELSE
                  Directory.Search$ = Command.Work
               END IF
            END IF
         END IF

         ' 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 findnext error
         IF (OutregsX.Flags AND &H1) = &H0 THEN

            ' call routine to search for files
            IF Continuous.Display = False THEN
               COLOR Yellow, Black
               PRINT "Searching: " + Directory.Search$
            END IF

            ' subroutine to search directory filenames
            CALL Directories(Directory.Search$)
         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
   IF Quit.Searching THEN
      EXIT DO
   END IF
LOOP

End.Stree:

' 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.ASCIZ)
   InregsX.DX = VARPTR(Directory.ASCIZ)
   CALL InterruptX(&H21, InregsX, OutregsX)
ELSE
   ' restore current directory
   InregsX.AX = &H3B00
   InregsX.DS = VARSEG(Directory.ASCIZ)
   InregsX.DX = VARPTR(Directory.ASCIZ)
   CALL InterruptX(&H21, InregsX, OutregsX)
END IF

' display counters
IF Continuous.Display = False THEN
   IF Wide.Display THEN
      IF Display.Length THEN
         PRINT
      END IF
   ENDIF
   COLOR Yellow, Black
   PRINT "Directories counted"; Directories.Counted
   Prompt$ = "Press <enter> to exit to DOS:"
   CALL MorePrompt(Prompt$, CHR$(13), Outpt$)
END IF
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

' display program usage
Boot.Usage:
 ' make header
 COLOR White, Black
 PRINT "Stree v1.2a: Directory search utility; "
 COLOR Yellow, Black
 PRINT "Usage:"
 PRINT "   Stree [d:\path\][/ahos][/cdefnrtwyz]"
 PRINT "Where:"
 PRINT "   /c  continuous display      /e  extended display"
 PRINT "   /f  short filename display  /nxxx  recurse levels override"
 PRINT "   /r  recurse directories     /v  use 8.3 filenames"
 PRINT "   /w  wide list display       /y  lowercase display"
 PRINT "   /z  suppress errors"
 PRINT "   display directory ranges; (/1  creation, /2 accessed, /3 modified):"
 PRINT "      /d  is range of file dates in form mm/dd/yyyy-mm/dd/yyyy"
 PRINT "      /t  is range of file times in form hh:mm:ss-hh:mm:ss"
 PRINT "   display directory attributes / prefix with:"
 PRINT "      a  archive, h  hidden, o  read-only, s  system"
 COLOR Plain, Black
 END

Boot.Error:
 COLOR White, Black
 PRINT "Command line error. Type Stree /? for help."
 COLOR Plain, Black
 END

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

 ' make directory filename
 IF INSTR(Directory.Search$, "?") OR INSTR(Directory.Search$, "*") THEN
    ASCIZ.Sub = Directory.Search$ + CHR$(0)
 ELSE
    IF RIGHT$(Directory.Search$, 1) <> "\" THEN
       Directory.Search$ = Directory.Search$ + "\"
    END IF
    ASCIZ.Sub = Directory.Search$ + "*.*" + CHR$(0)
 END IF

 ' make directory filename for attribute search
 IF Directory.Search$ = "\" THEN
    ASCIZ2 = "\" + CHR$(0)
 ELSE
    IF INSTR(Directory.Search$, "?") OR INSTR(Directory.Search$, "*") THEN
       ASCIZ2 = Directory.Search$ + CHR$(0)
    ELSE
       ASCIZ2 = LEFT$(Directory.Search$, LEN(Directory.Search$) - 1) + CHR$(0)
    END IF
 END IF

 ' restore data segment dta
 GOSUB Restore.DTA

 ' check windows dos
 IF Windows.Detected THEN
    ' find first long filename
    InregsX.AX = &H714E
    InregsX.CX = &H37
    InregsX.SI = &H1
    InregsX.DS = VARSEG(ASCIZ.Sub)
    InregsX.DX = VARPTR(ASCIZ.Sub)
    InregsX.ES = VARSEG(TreeWDTA)
    InregsX.DI = VARPTR(TreeWDTA)
    CALL InterruptX(&H21, InregsX, OutregsX)
    Wfile.Handle = OutregsX.AX
 ELSE
    ' find first directory
    InregsX.AX = &H4E00
    InregsX.CX = &H37
    InregsX.DS = VARSEG(ASCIZ.Sub)
    InregsX.DX = VARPTR(ASCIZ.Sub)
    CALL InterruptX(&H21, InregsX, OutregsX)
 END IF

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

 ' check windows dos
 IF Windows.Detected THEN
    ' find first long filename
    InregsX.AX = &H714E
    InregsX.CX = &H37
    InregsX.SI = &H1
    InregsX.Ds = VARSEG(ASCIZ2)
    InregsX.DX = VARPTR(ASCIZ2)
    InregsX.ES = VARSEG(SWDTA)
    InregsX.DI = VARPTR(SWDTA)
    CALL InterruptX(&H21, InregsX, OutregsX)
    Sfile.Handle = OutregsX.AX

    ' store directory name
    Directory$ = SWDTA.ASCIZshort
    Directory$ = LEFT$(Directory$, INSTR(Directory$, CHR$(0)) - 1)

    ' close long filename search
    InregsX.AX = &H71A1
    InregsX.BX = Sfile.Handle
    CALL InterruptX(&H21, InregsX, OutregsX)

    ' check directory length
    IF Short.Filenames THEN
       ' get windows short filename
       InregsX.AX = &H7160
       InregsX.CX = &H8001
       InregsX.DS = VARSEG(ASCIZ2)
       InregsX.SI = VARPTR(ASCIZ2)
       InregsX.ES = VARSEG(ASCIZ3)
       InregsX.DI = VARPTR(ASCIZ3)
       CALL InterruptX(&H21, InregsX, OutregsX)
       Directory$ = ASCIZ3
       Directory$ = LEFT$(Directory$, INSTR(Directory$, CHR$(0)) - 1)
    END IF
 ELSE
    ' restore directory search dta
    InregsX.AX = &H1A00
    InregsX.DS = VARSEG(DTAfile2)
    InregsX.DX = VARPTR(DTAfile2)
    CALL InterruptX(&H21, InregsX, OutregsX)

    ' find first directory
    InregsX.AX = &H4E00
    InregsX.CX = &H37
    InregsX.DS = VARSEG(ASCIZ2)
    InregsX.DX = VARPTR(ASCIZ2)
    CALL InterruptX(&H21, InregsX, OutregsX)
    TempDTA = DTAfile2
    GOSUB Restore.DTA
 END IF

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

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

    ' store attribute
    Attribute = OutregsX.CX

    ' store directory date
    CALL Make.Date

    ' check date/time range
    Valid.Date = True
    IF Search.From.Date OR Search.To.Date THEN
       IF File.Work.Date < Search.From.Date THEN
          Valid.Date = False
       END IF
       IF File.Work.Date > Search.To.Date THEN
          Valid.Date = False
       END IF
    END IF
    IF Search.From.Time OR Search.To.Time THEN
       IF File.Work.Time < Search.From.Time THEN
          Valid.Date = False
       END IF
       IF File.Work.Time > Search.To.Time THEN
          Valid.Date = False
       END IF
    END IF

    ' check directory attribute
    Valid.Attribute = True

    ' check for read-only file
    IF (Attribute AND &H1) = &H1 THEN
       IF Search.Readonly = False THEN
          Valid.Attribute = False
       END IF
    END IF

    ' check for hidden file
    IF (Attribute AND &H2) = &H2 THEN
       IF Search.Hidden = False THEN
          Valid.Attribute = False
       END IF
    END IF

    ' check for system file
    IF (Attribute AND &H4) = &H4 THEN
       IF Search.System = False THEN
          Valid.Attribute = False
       END IF
    END IF

    ' check for archive file
    IF (Attribute AND &H20) = &H20 THEN
       IF Search.Archive = False THEN
          Valid.Attribute = False
       END IF
    END IF

    ' check for valid directory
    IF Valid.Date THEN
       IF Valid.Attribute THEN

          ' increment directories counted
          Directories.Counted = Directories.Counted + 1

          ' store directory name
          IF Windows.Detected AND Short.Filenames THEN
             Outpt$ = RTRIM$(Directory$)
          ELSE
             Outpt$ = RTRIM$(Directory.Search$)
          END IF
          IF Short.Display THEN
             IF RIGHT$(Outpt$, 1) = "\" THEN
                Outpt$ = LEFT$(Outpt$, LEN(Outpt$) -1)
             END IF
             FOR Imbedded = LEN(Outpt$) TO 1 STEP -1
                IF MID$(Outpt$, Imbedded, 1) = "\" THEN
                   Outpt$ = MID$(Outpt$, Imbedded + 1)
                   EXIT FOR
                END IF
             NEXT
          END IF
          IF Short.Filenames = False OR Short.Display THEN
             IF MID$(Outpt$, 2, 1) <> ":" THEN
                Outpt$ = Drive.Search + ":" + Outpt$
             END IF
          END IF
          IF Display.Lowercase THEN
             Outpt$ = LCASE$(Outpt$)
          ELSE
             IF Windows.Detected = False THEN
                Outpt$ = UCASE$(Outpt$)
             END IF
          END IF

          ' display directory
          COLOR Yellow, Black
          PRINT Outpt$;
          IF Wide.Display THEN
             PRINT SPACE$(15 - LEN(Outpt$));
             Display.Length = Display.Length + 1
             IF Display.Length = 5 THEN
                PRINT
                Display.Length = False
                Display.Lines = Display.Lines + 1
             END IF
          END IF

          ' check display type
          IF Extended.List THEN
             CALL Display.List
          END IF
          IF Wide.Display = False THEN
             PRINT
          END IF
          IF More.Display = False THEN
             IF Continuous.Display = False THEN
                IF Wide.Display = False THEN
                   Display.Lines = Display.Lines + 1
                END IF
                IF Display.Lines >= 22 THEN
                   Display.Lines = False
                   Prompt$ = "More(y/n/c)?"
                   CALL MorePrompt(Prompt$, "ync", Outpt$)
                   SELECT CASE Outpt$
                   CASE "c"
                      More.Display = True
                   CASE "n"
                      Quit.Searching = True
                   END SELECT
                END IF
             END IF
          END IF
       END IF
    END IF
 END IF

 ' restore subroutine dta
 GOSUB Restore.DTA

 ' check to recurse directories
 IF Recurse.Directories THEN
    ' recurse directories
    DO
       ' check quit searching
       IF Quit.Searching THEN
          EXIT SUB
       END IF

       ' check directory attribute
       IF Windows.Detected THEN
          Attribute = ASC(TreeWDTA.fileattr)
       ELSE
          Attribute = ASC(DTAfile.fileattr)
       END IF
       IF (Attribute AND &H10) = &H10 THEN

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

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

             ' make next search directory
             IF INSTR(Directory.Search$, "?") OR INSTR(Directory.Search$, "*") THEN
                New.Directory$ = Directory.Search$
                FOR Imbedded = LEN(New.Directory$) TO 1 STEP -1
                   IF MID$(New.Directory$, Imbedded, 1) = "\" THEN
                      EXIT FOR
                   END IF
                NEXT
                New.Directory$ = LEFT$(New.Directory$, Imbedded - 1)
                IF New.Directory$ = "" THEN
                   Next.Directory$ = "\" + Directory$
                ELSE
                   Next.Directory$ = New.Directory$ + "\" + Directory$
                END IF
             ELSE
                Next.Directory$ = Directory.Search$ + Directory$
             END IF

             ' 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$)
             END IF
             IF Nested.Recurse > False THEN
                Nested.Levels = Nested.Levels - 1
             END IF

             ' restore data segment 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

' 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
 IF Wide.Display THEN
    IF Display.Length THEN
       Display.Length = False
       PRINT
    END IF
 ENDIF
 PRINT Temp.Outpt$
 Prompt$ = "Press R to retry, Q to quit, C to continue:"
 CALL MorePrompt(Prompt$, "rqc", Outpt$)
 SELECT CASE Outpt$
 CASE "r"
    RESUME
 CASE "q"
    Error.Level = True
    RESUME End.Stree
 CASE "c"
    OutregsX.Flags = &H1
    RESUME NEXT
 END SELECT
 END 0

SUB MorePrompt (Input.String$, Input.Mask$, Output.String$)
 COLOR White, Black
 PRINT Input.String$ + " ";
 Input.Char$ = NUL
 DO
    LOCATE , , 1
    Input.Char$ = INKEY$
    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

' 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

' routine to display extended directory attributes
SUB Display.List
 IF Windows.Detected THEN
    ' store file creation date\time
    IF Creation.Time THEN
       CALL Make.Date1
       GOSUB Convert.Date
       GOSUB Convert.Time
       COLOR Red, Black
       PRINT " " + File.Date$ + " " + File.Time$;
    END IF
    IF Access.Time THEN
       CALL Make.Date2
       GOSUB Convert.Date
       'GOSUB Convert.Time
       IF Creation.Time THEN
          COLOR White, Black
          PRINT " \";
       END IF
       COLOR Red, Black
       PRINT " " + File.Date$;
    END IF
    IF Modified.Time THEN
       CALL Make.Date3
       GOSUB Convert.Date
       GOSUB Convert.Time
       IF Creation.Time OR Access.Time THEN
          COLOR White, Black
          PRINT " \";
       END IF
       COLOR Red, Black
       PRINT " " + File.Date$ + " " + File.Time$;
    END IF
 ELSE
    ' construct file date and time for display
    GOSUB Convert.Date
    GOSUB Convert.Time

    ' display file date/time
    COLOR Green, Black
    PRINT " " + File.Date$ + " " + File.Time$;

    ' check extended directory date\time
    IF File.Access.Date > 0 THEN
       File.Work.Date = File.Access.Date
       GOSUB Convert.Date
       COLOR Cyan, Black
       PRINT " (" + File.Date$ + ")";
    END IF
 END IF

 ' display file attributes
 COLOR White, Black
 IF (Attribute AND &H1) = &H1 THEN
    PRINT " Read-only";
 END IF
 IF (Attribute AND &H2) = &H2 THEN
    PRINT " Hidden";
 END IF
 IF (Attribute AND &H4) = &H4 THEN
    PRINT " System";
 END IF
 IF (Attribute AND &H20) = &H20 THEN
    PRINT " Archive";
 END IF
 EXIT SUB

Convert.Date:
 Year! = INT(File.Work.Date / 512)
 Month! = INT((File.Work.Date AND &H1E0) / 32)
 Day! = INT(File.Work.Date AND &H1F)
 Year! = Year! + 1980
 File.Date$ = RIGHT$(STR$(Month! + 100), 2) + "-"
 File.Date$ = File.Date$ + RIGHT$(STR$(Day! + 100), 2) + "-"
 File.Date$ = File.Date$ + MID$(STR$(Year!), 2)
 RETURN

Convert.Time:
 Hour! = INT(File.Work.Time / 2048)
 Minute! = INT((File.Work.Time AND &H7E0) / 32)
 Seconds! = INT((File.Work.Time AND &H1F) * 2)
 File.Time$ = RIGHT$(STR$(Hour! + 100), 2) + ":"
 File.Time$ = File.Time$ + RIGHT$(STR$(Minute! + 100), 2) + ":"
 File.Time$ = File.Time$ + RIGHT$(STR$(Seconds! + 100), 2)
 RETURN
END SUB

SUB Make.Date
 IF Windows.Detected THEN
    ' store file creation date\time
    IF Creation.Time THEN
       CALL Make.Date1
    ELSE
       IF Access.Time THEN
          CALL Make.Date2
       ELSE
          IF Modified.Time THEN
             CALL Make.Date3
          END IF
       END IF
    END IF
 ELSE
    ' store file creation date\time
    File.Work.Time = ASC(MID$(TempDTA.FileTime, 2, 1))
    File.Work.Time = File.Work.Time * &H100 + ASC(MID$(TempDTA.FileTime, 1, 1))
    File.Work.Date = ASC(MID$(TempDTA.FileDate, 2, 1))
    File.Work.Date = File.Work.Date * &H100 + ASC(MID$(TempDTA.FileDate, 1, 1))
    CALL Get.Attributes
 END IF
END SUB

SUB Make.Date1:
 File.Work.Time = ASC(MID$(SWDTA.CreateTime, 2, 1))
 File.Work.Time = File.Work.Time * &H100 + ASC(MID$(SWDTA.CreateTime, 1, 1))
 File.Work.Date = ASC(MID$(SWDTA.CreateTime, 4, 1))
 File.Work.Date = File.Work.Date * &H100 + ASC(MID$(SWDTA.CreateTime, 3, 1))
END SUB

SUB Make.Date2
 File.Work.Time = ASC(MID$(SWDTA.AccessTime, 2, 1))
 File.Work.Time = File.Work.Time * &H100 + ASC(MID$(SWDTA.AccessTime, 1, 1))
 File.Work.Date = ASC(MID$(SWDTA.AccessTime, 4, 1))
 File.Work.Date = File.Work.Date * &H100 + ASC(MID$(SWDTA.AccessTime, 3, 1))
END SUB

SUB Make.Date3
 File.Work.Time = ASC(MID$(SWDTA.ModTime, 2, 1))
 File.Work.Time = File.Work.Time * &H100 + ASC(MID$(SWDTA.ModTime, 1, 1))
 File.Work.Date = ASC(MID$(SWDTA.ModTime, 4, 1))
 File.Work.Date = File.Work.Date * &H100 + ASC(MID$(SWDTA.ModTime, 3, 1))
END SUB

' routine gets extended file date\time in dos 7.00
SUB Get.Attributes
 ' declare some variables
 DIM DTAfile2 AS DTAtype2
 DIM FCBfile AS FCBtype
 DIM Current.DTA.Seg AS INTEGER
 DIM Current.DTA.Off AS INTEGER

 ' reset file last access date
 File.Access.Date = 0

 ' check for dos 7.00
 IF Windows.DOS = False THEN
    EXIT SUB
 END IF

 ' store current dta
 InregsX.AX = &H2F00
 CALL InterruptX(&H21, InregsX, OutregsX)
 Current.DTA.Seg = OutregsX.ES
 Current.DTA.Off = OutregsX.BX

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

 ' change to directory
 InregsX.AX = &H3B00
 InregsX.DS = VARSEG(ASCIZ2)
 InregsX.DX = VARPTR(ASCIZ2)
 CALL InterruptX(&H21, InregsX, OutregsX)

 ' check carry flag error
 IF (OutregsX.Flags AND &H1) = &H0 THEN
    ' store directory name
    FCBfile.ExtendedFCB = CHR$(255)
    FCBfile.FileAttribute = CHR$(&H37)
    FCBfile.Filename = "."
    FCBfile.Extension = ""
    FCBfile.DriveNumber = CHR$(ASC(Drive.Search) - 64)

    ' find first fcb
    InregsX.AX=&H1100
    InregsX.DS=VARSEG(FCBfile)
    InregsX.DX=VARPTR(FCBfile)
    CALL InterruptX(&H21,InregsX,OutregsX)

    ' check fcb error
    IF (OutregsX.AX AND &HFF) = &H0 THEN
       ' read extended date\time
       File.Access.Date = ASC(MID$(DTAfile2.LastAccessDate, 2, 1))
       File.Access.Date = File.Access.Date * &H100 + ASC(MID$(DTAfile2.LastAccessDate, 1, 1))
    END IF
 END IF

 ' restore current dta
 InregsX.AX = &H1A00
 InregsX.DS = Current.DTA.Seg
 InregsX.DX = Current.DTA.Off
 CALL InterruptX(&H21, InregsX, OutregsX)
END SUB
