REM file: Whereis.bas - Public Domain DOS Utility
REM Version 1.0a created 01/20/1995
REM Version 1.1a created 02/05/1995
REM Version 1.2a created 06/08/1995
REM Version 1.3a created 04/30/1996
REM Version 1.4a created 05/09/1996
REM Version 1.5a created 01/21/1997
REM Version 1.6a created 01/21/1997
REM Version 1.6b created 11/29/1999
REM Version 1.6c created 12/24/1999
REM Version 1.6d created 06/18/2000
REM Version 1.6e created 10/18/2000
REM Version 1.6f created 12/21/2000
REM Version 1.7a created 02/27/2001
REM Version 1.8a created 04/19/2002
REM Version 1.9a created 03/01/2004
REM Version 2.0a created 08/03/2004
REM Version 2.1a created 10/06/2004
REM Version 2.2a created 10/20/2004
REM Version 2.3a created 11/22/2004
REM Version 2.4a created 12/20/2004
REM Version 2.5a created 12/28/2004
REM Version 2.6a created 12/31/2004
REM Version 2.7a created 12/31/2004
REM Version 2.8a created 12/31/2004
REM Version 2.9a created 01/04/2005
REM Version 3.0a created 01/15/2005
REM Version 3.1a created 03/15/2005
REM Version 3.2a created 12/10/2005
REM Version 3.3a created 01/15/2006
REM Version 3.4a created 02/28/2006
REM Version 3.5a created 08/31/2006
REM Version 3.6a created 09/15/2007

' default integer variables
DEFINT A-Z
REM $DYNAMIC

' define boolean values
CONST True = -1
CONST TrueD = -1#
CONST False = 0
CONST FalseD = 0#
CONST FalseS = 0!
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

' define filelist constant
CONST RecurseLevel = 8

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

' declare subroutines
DECLARE SUB Directories(D$,F$)
DECLARE SUB Filenames(D$,F$)
DECLARE SUB DisplayFiles(D$,F$)
DECLARE SUB ProcessCommand()
DECLARE SUB ReadAccessDate(D$,F$,C%)
DECLARE SUB CheckExcluded(F$,G$,T%)
DECLARE SUB ExecuteCommand(F$)

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

' initialize filename buffer
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, ASCIIZ.Short AS STRING * 68

' 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 Continuous.Display AS INTEGER, Double.Line AS INTEGER
COMMON SHARED Files.Counted AS DOUBLE, More.Display AS INTEGER
COMMON SHARED Dirs.Counted AS DOUBLE, Display.Dirs.Counted AS INTEGER
COMMON SHARED Dirs.Displayed AS DOUBLE, Control.Break AS INTEGER
COMMON SHARED Length AS INTEGER, Volumes.Counted AS INTEGER

' declare search work 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.Size AS DOUBLE, Recurse.Directories AS INTEGER
COMMON SHARED Display.Hidden AS INTEGER, Display.System AS INTEGER
COMMON SHARED Display.Readonly AS INTEGER, Display.Archive AS INTEGER
COMMON SHARED Display.Attribute AS INTEGER, Display.Errors AS INTEGER
COMMON SHARED Display.Volume AS INTEGER, Redirected.Input AS INTEGER
COMMON SHARED Display.Any AS INTEGER, No.Display.Archive AS INTEGER
COMMON SHARED No.Display.Readonly AS INTEGER, No.Display.System AS INTEGER
COMMON SHARED No.Display.Hidden AS INTEGER, No.Display.Any AS INTEGER
COMMON SHARED No.Display.Directory AS INTEGER, No.Display.Drive AS INTEGER
COMMON SHARED Display.Directory AS INTEGER, Short.Display AS INTEGER
COMMON SHARED Wide.Display AS INTEGER, Wide.List AS INTEGER
COMMON SHARED Current.Drive AS STRING * 1, Display.Search AS INTEGER
COMMON SHARED Remove.Slash AS INTEGER, Skip.Current AS INTEGER
COMMON SHARED First.Directory AS INTEGER, Max.Recurse AS INTEGER
COMMON SHARED Display.Only.Errors AS INTEGER

' declare file date\time and filesize work variables
COMMON SHARED File.Work.Date AS SINGLE, File.Work.Time AS SINGLE
COMMON SHARED Total.Bytes AS DOUBLE, Parameters() AS STRING * 348
COMMON SHARED Search.Size.From AS DOUBLE, Search.Size.To AS DOUBLE
COMMON SHARED Search.File.Size, Display.Lowercase AS INTEGER
COMMON SHARED Creation.Time AS INTEGER, Short.Filenames AS INTEGER
COMMON SHARED Access.Time AS INTEGER, Modified.Time AS INTEGER

' declare file creation time seconds work variables
COMMON SHARED Half.Second1 AS SINGLE, Half.Second2 AS SINGLE
COMMON SHARED Millisecond AS INTEGER

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

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

' declare filelist variables
COMMON SHARED Enable.Redirect AS INTEGER, Ignore.Prompts AS INTEGER

' declare dos command work variables
COMMON SHARED DOS.Command AS STRING, Windows.Detected AS INTEGER
COMMON SHARED Windows.DOS AS INTEGER, Exclude.List AS STRING

' declare error trap variables
COMMON SHARED Data.Error AS INTEGER, Error.Flag AS INTEGER

' 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

' dimension arrays
REDIM Parameters(1 TO 12) AS STRING * 348

' 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 (fixed v3.5a 08/31/2006).
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
InregsX.AX = &H4A33
CALL InterruptX(&H2F, InregsX, OutregsX)
IF OutregsX.AX = False THEN
   Windows.DOS = 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$("WHEREIS")
END IF
Command.Line = RTRIM$(Command.Line)

' get exclude switch
Exclude.List = Nul
Imbedded = INSTR(Command.Line, "/(")
IF Imbedded THEN
   Last.Switch = Imbedded - 1
   Exclude.List = Nul
   Next.Bracket = INSTR(Imbedded + 2, Command.Line, ")")
   IF Next.Bracket THEN
      Exclude.List = MID$(Command.Line, Imbedded + 2, Next.Bracket - Imbedded - 2)
      Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Next.Bracket + 1)
   END IF
   IF Exclude.List = Nul THEN
      GOTO Boot.Error
   END IF
END IF
Command.Line = Rtrim$(Command.Line)

' get dos command
DOS.Command = Nul
Imbedded = INSTR(Command.Line, "/[")
IF Imbedded THEN
   Last.Switch = Imbedded - 1
   DOS.Command = Nul
   Next.Bracket = INSTR(Imbedded + 2, Command.Line, "]")
   IF Next.Bracket THEN
      DOS.Command = MID$(Command.Line, Imbedded + 2, Next.Bracket - Imbedded - 2)
      Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Next.Bracket + 1)
   END IF
   IF DOS.Command = Nul THEN
      GOTO Boot.Error
   END IF
END IF
Command.Line = Rtrim$(Command.Line)

' edit special DOS command parameters
Imbedded = INSTR(DOS.Command, "//1")
WHILE Imbedded
   DOS.Command = LEFT$(DOS.Command, Imbedded - 1) + ">" + MID$(DOS.Command, Imbedded + 3)
   Imbedded = INSTR(DOS.Command, "//1")
WEND
Imbedded = INSTR(DOS.Command, "//2")
WHILE Imbedded
   DOS.Command = LEFT$(DOS.Command, Imbedded - 1) + "<" + MID$(DOS.Command, Imbedded + 3)
   Imbedded = INSTR(DOS.Command, "//2")
WEND
Imbedded = INSTR(DOS.Command, "//3")
WHILE Imbedded
   DOS.Command = LEFT$(DOS.Command, Imbedded - 1) + "|" + MID$(DOS.Command, Imbedded + 3)
   Imbedded = INSTR(DOS.Command, "//3")
WEND
Imbedded = INSTR(DOS.Command, "//4")
WHILE Imbedded
   DOS.Command = LEFT$(DOS.Command, Imbedded - 1) + "%" + MID$(DOS.Command, Imbedded + 3)
   Imbedded = INSTR(DOS.Command, "//4")
WEND
Imbedded = INSTR(DOS.Command, "//5")
WHILE Imbedded
   DOS.Command = LEFT$(DOS.Command, Imbedded - 1) + "]" + MID$(DOS.Command, Imbedded + 3)
   Imbedded = INSTR(DOS.Command, "//5")
WEND

' check command line switches
Display.Archive = ParseLine ("//A")
Display.Hidden = ParseLine ("//H")
Display.Directory = ParseLine ("//I")
Display.Readonly = ParseLine ("//O")
Display.System = ParseLine ("//S")
Display.Any = ParseLine ("//X")
No.Display.Archive = ParseLine ("/A")
No.Display.Hidden = ParseLine ("/H")
No.Display.Directory = ParseLine ("/I")
No.Display.Readonly = ParseLine ("/O")
No.Display.System = ParseLine ("/S")
No.Display.Any = ParseLine ("/X")
Var = ParseLine("/_")

No.Display.Drive = ParseLine ("/B")
Continuous.Display = ParseLine ("/C")
Short.Display = ParseLine ("/E")
Display.Search = ParseLine ("/G")
Skip.Current = ParseLine ("/J")
Short.Filenames = ParseLine ("/K")
Double.Line = ParseLine ("/L")
Display.Dirs.Counted = ParseLine ("/Q")
Recurse.Directories = ParseLine ("/R")
Remove.Slash = ParseLine ("/U")
Display.Volume = ParseLine ("/V")
Wide.Display = ParseLine ("/W")
Display.Lowercase = ParseLine ("/Y")
Display.Only.Errors = ParseLine ("/Z1")
Display.Errors = ParseLine ("/Z")
Control.Break = ParseLine ("/~")
Enable.Redirect = ParseLine ("/@")
Ignore.Prompts = ParseLine ("/#")
IF Wide.Display THEN
   Double.Line = False
   Short.Display  = True
   Short.Filenames = True
END IF

' set attribute display variable
Display.Attribute = False
IF Display.Archive OR No.Display.Archive THEN
   Display.Attribute = True
END IF
IF Display.Hidden OR No.Display.Hidden THEN
   Display.Attribute = True
END IF
IF Display.Readonly OR No.Display.Readonly THEN
   Display.Attribute = True
END IF
IF Display.System OR No.Display.System THEN
   Display.Attribute = True
END IF
IF Display.Any OR No.Display.Any THEN
   Display.Attribute = True
END IF
IF Display.Directory OR No.Display.Directory THEN
   Display.Attribute = True
END IF
IF Display.Volume THEN
   Display.Attribute = True
END IF

' reset file counter variable
Dirs.Counted = FalseD
Dirs.Displayed = FalseD
Files.Counted = FalseD
More.Display = False
   
' 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
   Last.Switch = Imbedded - 1
   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
   Last.Switch = Imbedded - 1
   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)
   IF T3! / 2! <> INT(T3! / 2!) THEN
      Half.Second1 = .5!
   ELSE
      Half.Second1 = FalseS
   END IF
   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 T3! / 2! <> INT(T3! / 2!) THEN
      Half.Second2 = .5!
   ELSE
      Half.Second2 = FalseS
   END IF
   IF Search.From.Time < False OR Search.To.Time < False THEN
      GOTO Boot.Error
   END IF
END IF

' get file size from command line
Search.File.Size = False
Search.Size.From = FalseD
Search.Size.To = FalseD
Imbedded = INSTR(UCASE$(Command.Line), "/F")
IF Imbedded THEN
   Last.Switch = Imbedded - 1
   Search.File.Size = True
   Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + 2)
   GOSUB Get.Numeric
   Search.Size.From = Var#
   IF MID$(Command.Line, Imbedded, 1) <> "-" THEN
      GOTO Boot.Error
   END IF
   Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + 1)
   GOSUB Get.Numeric
   Search.Size.To = Var#
   Search.Size.From = Search.Size.From * 1024#
   Search.Size.To = Search.Size.To * 1024#
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
         Modified.Time = True
      END IF
   END IF
END IF

' add one-half second
IF Creation.Time THEN
   Search.From.Time = Search.From.Time + Half.Second1
   Search.To.Time = Search.To.Time + Half.Second2
END IF

' 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 work variables
Files.Counter = False
Nested.Levels = False
Quit.Searching = 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
            Redirected.Input = True

            ' store character
            Char$ = Pipe.Buffer

            ' check input 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
      ' check control break
      IF BreakIS THEN
         EXIT DO
      END IF

      ' 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)

      ' locate filelist character
      Flag = False
      Command.Out$ = Command.Work
      Command.Out2$ = Nul
      IF MID$(Command.Out$, 2, 1) = ":" THEN
         Command.Out2$ = LEFT$(Command.Out$, 2)
         Command.Out$ = MID$(Command.Out$, 3)
      END IF
      IF LEFT$(Command.Out$, 1) = "@" THEN
         Flag = True
         Command.Out$ = MID$(Command.Out$, 2)
      ELSE
         FOR Var = LEN(Command.Out$) TO 1 STEP -1
            IF MID$(Command.Out$, Var, 1) = "\" THEN
               Char$ = MID$(Command.Out$, Var + 1)
               IF LEFT$(Char$, 1) = "@" THEN
                  Flag = True
                  Command.Out$ = LEFT$(Command.Out$, Var) + MID$(Char$, 2)
               END IF
               EXIT FOR
            END IF
         NEXT
      END IF
      IF LEN(Command.Out2$) THEN
         Command.Out$ = Command.Out2$ + Command.Out$
      END IF

      ' process work filename
      IF Flag AND Enable.Redirect THEN

         ' read ambiguated filename
         Command.Out$ = Ambiguate$(Command.Out$)
         IF Command.Out$ = Nul THEN
            Command.Out$ = "@" + Command.Out$
            Max.Recurse = 0
            CALL ProcessCommand
            GOTO Next.Filename
         END IF

	 ' process work filename
	 CLOSE #1
         Data.Error = False
         Error.Flag = True
         IF DIR$(Command.Out$) = Nul THEN
            Error.Flag = False
            Command.Out$ = "@" + Command.Out$
            Max.Recurse = 0
            CALL ProcessCommand
            GOTO Next.Filename
         END IF
         Error.Flag = False

         ' concatenate work filename
         Var$ = Command.Work ' command line filename
         FOR Var = LEN(Var$) TO 1 STEP -1
            IF MID$(Var$, Var, 1) = "\" THEN
               Var$ = MID$(Var$, Var + 1)
               EXIT FOR
            END IF
         NEXT
         IF MID$(Var$, 2, 1) = ":" THEN
            Var$ = MID$(Var$, 3)
         END IF
         Filename2$ = Var$
         Var$ = MID$(Var$, 2)
         Var2$ = Command.Out$ ' ambiguated filename
         FOR Var = LEN(Var2$) TO 1 STEP -1
            IF MID$(Var2$, Var, 1) = "\" THEN
               Var2$ = LEFT$(Var2$, Var)
               Var$ = Var2$ + Var$
               EXIT FOR
            END IF
         NEXT

         ' check excluded file
         IF LEN(Exclude.List) THEN
            Filename1$ = UCASE$(RTRIM$(Exclude.List))
            Filename2$ = UCASE$(RTRIM$(Filename2$))
            CALL CheckExcluded(Filename1$, Filename2$, Exclude.File)
            IF Exclude.File THEN
               Max.Recurse = 0
               CALL ProcessCommand
               GOTO Next.Filename
            END IF
         END IF

         ' prompt for filelist
         IF Ignore.Prompts = False THEN

            ' prompt to use filelist
            Prompt$ = "Use '" + Lcase$(Var$) + "' as filelist(y/n/q)?"
            CALL MorePrompt(Prompt$, "ynq", Outpt$)
            IF BreakIS THEN
               Outpt$ = "q"
            END IF
            SELECT CASE Outpt$
            CASE "q"
               Quit.Searching = True
               GOTO Next.Filename
            CASE "n"
               Max.Recurse = 0
               CALL ProcessCommand
               GOTO Next.Filename
            END SELECT
         END IF

         ' open work filename
         Data.Error = False
         Error.Flag = True
         OPEN Command.Out$ FOR INPUT SHARED AS #1
         Error.Flag = False

         ' check error flag
         IF Data.Error THEN
            OutregsX.Flags = &H1
            CALL DisplayError ("Error opening filelist.")
         ELSE
            ' process input filenames
            DO UNTIL EOF(1)
               ' get next filename
               LINE INPUT #1, Command.Work

               ' process search filename
               Command.Work = RTRIM$(Command.Work)
               IF LEN(Command.Work) THEN
                  Max.Recurse = 0
                  CALL ProcessCommand
               END IF
            LOOP
         END IF
      ELSE
         ' process remaining filenames
         Max.Recurse = 0
         CALL ProcessCommand
      END IF

' continue label
Next.Filename:

      ' 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 search filename
   IF Standard.Input$ = Nul THEN
      EXIT DO
   END IF

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

End.Whereis:

' 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 end program
IF Continuous.Display = False THEN
   IF Display.Search = False THEN
      IF Double.Line = False THEN
         IF Wide.List THEN
            Wide.List = False
            PRINT
         END IF
      END IF
   END IF
   COLOR Yellow, Black
   IF Display.Volume THEN
      Total$ = Format$(CDBL(Volumes.Counted), "#,##0;;0")
      PRINT "Volumes counted: "; Total$
   ELSE
      IF Display.Dirs.Counted = False THEN
         Total$ = Format$(Dirs.Counted, "#,##0;;0")
         PRINT "Directories counted: "; Total$
      ELSE
         Total$ = Format$(Dirs.Displayed, "#,##0;;0")
         PRINT "Directories displayed: "; Total$
      END IF
      Total$ = Format$(Files.Counted, "#,##0;;0")
      PRINT "Files counted: "; Total$
      IF Double.Line THEN
         Total$ = Format$(Total.Bytes, "#,##0;;0")
         PRINT "Bytes counted: "; Total$
      END IF
   END IF
   Prompt$ = "Press <enter> to exit to DOS:"
   CALL MorePrompt(Prompt$, CHR$(13), Outpt$)
END IF

' restore key trapping
CALL RestInt

' reset color
COLOR Plain, Black
END

' process command work
SUB ProcessCommand
 DIM ASCIIZ AS STRING * 260
 ' 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 find first 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 find first 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
             IF Display.Volume THEN
                PRINT "Searching: " + Drive.Search + ":"
             ELSE
                PRINT "Searching: " + Drive.Search + ":" + Directory.Search$ + Filename.Search$
             END IF
          END IF
          Files.Counter = Files.Counter + 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
END SUB

' make header
Header:
 IF Header.Flag THEN
    RETURN
 END IF
 Header.Flag = True
 IF Continuous.Display = False THEN
    COLOR White, Black
    PRINT "Whereis v3.6a: File search utility;"
 END IF
 RETURN

' display program usage
Boot.Usage:
 CALL RestInt
 Var$=Inkey$
 CALL SetInt
 Var = ClearBreak
 Display.Page = 1
 DO
    SELECT CASE Display.Page
    CASE 1
       GOSUB Display.Page.One
    CASE 2
       GOSUB Display.Page.Two
    END SELECT
    Prompt$ = "Press 1, 2, or q to quit:"
    CALL MorePrompt(Prompt$, "12q", Outpt$)
    IF BreakIS THEN
       CALL RestInt
       COLOR Plain, Black
       PRINT
       PRINT "Exiting to DOS:"
       END
    END IF
    SELECT CASE Outpt$
    CASE "1"
       Display.Page = 1
    CASE "2"
       Display.Page = 2
    CASE "q"
       CALL RestInt
       COLOR Plain, Black
       PRINT "Exiting to DOS:"
       END
    END SELECT
 LOOP
 CALL RestInt
 COLOR Plain, Black
 PRINT "Exiting to DOS:"
 END

Display.Page.One:
 CLS
 COLOR White, Black
 PRINT "Whereis v3.6a: File search utility;"
 COLOR Yellow, Black
 PRINT "Usage:"
 PRINT "  Whereis [@][d:\path\filename.ext][//ahiosx][/bcdefgjklnqrtuvwyz@#][/123]"
 PRINT "Where:
 PRINT "  /b  suppress drive letter      /c  continuous display"
 PRINT "  /e  short filename display     /g  display search directories"
 PRINT "  /j  skip current directory     /k  use 8.3 short filenames"
 PRINT "  /l  double line display        /nxxx  nested directories"
 PRINT "  /q  directory count override   /r  recurse directories"
 PRINT "  /u  remove trailing slash      /v  display volume label"
 PRINT "  /w  wide file list             /y  display lowercase"
 PRINT "  /z  no error messages          /z1 display only errors"
 PRINT "display ranges:
 PRINT "  /1  creation, /2  last access, /3  modify time"
 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 "  /f  is range of file sizes in form xxx-xxx in kilobytes"
 RETURN

Display.Page.Two:
 CLS
 COLOR White, Black
 PRINT "Whereis v3.6a: File search utility;"
 COLOR Yellow, Black
 PRINT "Usage:"
 PRINT "  Whereis [@][d:\path\filename.ext][//ahiosx][/bcdefgjklnqrtuvwyz@#][/123]"
 PRINT "Where:
 PRINT "  filelist switches:"
 PRINT "    /@  enable filelists   /#  ignore filelist prompts"
 PRINT "  exclude file list switch:"
 PRINT "    /(<filename.ext>)"
 PRINT "      excludes files containing ? and * characters."
 PRINT "  display file attributes: // prefix for files with, / prefix for files without"
 PRINT "    a  archive, h  hidden, i  directory, o  read-only, s  system, x  none"
 PRINT "  DOS command: /[<command>]"
 PRINT "    with command replacement parameters:"
 PRINT "      %1 = d:, %2 = d:\, %3 = d:\pathname, %4 = d:\pathname\"
 PRINT "      %5 = d:\pathname\filename.ext, %6 = \pathname, %7 = \pathname\"
 PRINT "      %8 = \pathname\filename.ext, %9 = filename.ext, %a = filename"
 PRINT "      %b = .ext, %c = ext, //1 = >, //2 = <, //3 = |, //4 = %, //5 = ]"
 RETURN

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 Whereis /? for help."
 COLOR Plain, Black
 END

' 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 volume label search
 IF Display.Volume THEN
    IF LEFT$(Directory.Search$, 2) = "\\" THEN
       IF RIGHT$(Directory.Search$, 1) = "\" THEN
          ASCIIZ.Sub = Directory.Search$ + "*.*" + CHR$(0)
       ELSE
          ASCIIZ.Sub = Directory.Search$ + "\*.*" + CHR$(0)
       END IF
    ELSE
       ASCIIZ.Sub = "\*.*" + CHR$(0)
    END IF
 ELSE
    ' display directories searched
    IF Display.Search THEN
       ' check skip directory
       Flag = True
       IF Skip.Current THEN
          IF First.Directory = False THEN
             First.Directory = True
             Flag = False
          END IF
       END IF
       IF Flag THEN
          ' count only directories displayed
          Dirs.Displayed = Dirs.Displayed + 1#
          ' store directory
          Filename$ = Directory.Search$
          IF Filename$ <> "" THEN
             IF Remove.Slash THEN
                Filename$ = LEFT$(Filename$, LEN(Filename$) - 1)
             END IF
          END IF
          ' store drive letter
          IF No.Display.Drive = False THEN
             IF LEFT$(Filename$, 2) <> "\\"  THEN
                Filename$ = Drive.Search + ":" + Filename$
             END IF
          END IF
          ' store directory
          Outpt$ = Filename$
          Length = LEN(Outpt$)
          ' calculate display lines
          CALL PageCount
          ' check overflow past line 22
          IF Files.Counter > 22 THEN
             CALL PagePrompt
             IF Quit.Searching THEN
                EXIT SUB
             END IF
             ' reset display lines
             CALL PageCount
          END IF
          ' display directory
          COLOR Yellow, Black
          PRINT Outpt$
          ' check paginate
          CALL PagePrompt
          IF Quit.Searching THEN
             EXIT SUB
          END IF
       END IF
    END IF
    ' increment directories actually searched
    Dirs.Counted = Dirs.Counted + 1#
    ' store directory asciiz
    ASCIIZ.Sub = Directory.Search$ + "*.*" + CHR$(0)
 END IF
 GOSUB Restore.DTA

 ' find first directory
 IF Display.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

 ' check volume date/time
 IF Display.Volume THEN
    File.Work.Time = ASC(MID$(DTAfile.FileTime, 2, 1))
    File.Work.Time = File.Work.Time * &H100 + ASC(MID$(DTAfile.FileTime, 1, 1))
    File.Work.Date = ASC(MID$(DTAfile.FileDate, 2, 1))
    File.Work.Date = File.Work.Date * &H100 + ASC(MID$(DTAfile.FileDate, 1, 1))
 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 Display.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 Display.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 Display.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
       ' store file size
       IF Windows.Detected THEN
          File.Size = False
          File.Size = File.Size * &H100 + ASC(MID$(FileWDTA.FileSizeHigh, 4, 1))
          File.Size = File.Size * &H100 + ASC(MID$(FileWDTA.FileSizeHigh, 3, 1))
          File.Size = File.Size * &H100 + ASC(MID$(FileWDTA.FileSizeHigh, 2, 1))
          File.Size = File.Size * &H100 + ASC(MID$(FileWDTA.FileSizeHigh, 1, 1))
          File.Size = File.Size * &H100 + ASC(MID$(FileWDTA.FileSizeLow, 4, 1))
          File.Size = File.Size * &H100 + ASC(MID$(FileWDTA.FileSizeLow, 3, 1))
          File.Size = File.Size * &H100 + ASC(MID$(FileWDTA.FileSizeLow, 2, 1))
          File.Size = File.Size * &H100 + ASC(MID$(FileWDTA.FileSizeLow, 1, 1))
       ELSE
          File.Size = False
          File.Size = File.Size + ASC(MID$(DTAfile.FileSize, 4, 1))
          File.Size = File.Size * &H100 + ASC(MID$(DTAfile.FileSize, 3, 1))
          File.Size = File.Size * &H100 + ASC(MID$(DTAfile.FileSize, 2, 1))
          File.Size = File.Size * &H100 + ASC(MID$(DTAfile.FileSize, 1, 1))
       END IF

       ' display filename
       IF Windows.Detected THEN
          TempWDTA = FileWDTA
       ELSE
          TempDTA = DTAfile
       END IF

       ' recursively call filelists
       IF LEFT$(Filename$, 1) = "@" AND Enable.Redirect THEN
          Filelist.Filename$ = Ambiguate$(Filename$)

          ' check recursion level
          Max.Recurse = Max.Recurse + 1
          IF Max.Recurse >= RecurseLevel THEN
             OutregsX.Flags = &H1
             CALL DisplayError ("Recursive filelist error.")
             Quit.Searching = True
             Flag = False
             EXIT DO
          END IF

          ' reset prompt flag
          Flag = True

          ' concatenate work filename
          Var$ = Filename$ ' command line filename
          FOR Var = LEN(Var$) TO 1 STEP -1
             IF MID$(Var$, Var, 1) = "\" THEN
                Var$ = MID$(Var$, Var + 1)
                EXIT FOR
             END IF
          NEXT
          IF MID$(Var$, 2, 1) = ":" THEN
             Var$ = MID$(Var$, 3)
          END IF
          Filename2$ = Var$
          Var2$ = Filelist.Filename$ ' ambiguated filename
          FOR Var = LEN(Var2$) TO 1 STEP -1
             IF MID$(Var2$, Var, 1) = "\" THEN
                Var2$ = LEFT$(Var2$, Var)
                Var$ = Var2$ + Var$
                EXIT FOR
             END IF
          NEXT

          ' check excluded file
          IF LEN(Exclude.List) THEN
             Filename1$ = UCASE$(RTRIM$(Exclude.List))
             Filename2$ = UCASE$(RTRIM$(Filename2$))
             CALL CheckExcluded(Filename1$, Filename2$, Exclude.File)
             IF Exclude.File THEN
                Flag = False
             END IF
          END IF

          ' prompt for filelist
          IF Ignore.Prompts = False THEN

             ' prompt to use filelist
             IF Flag THEN
                Prompt$ = "Use '" + Lcase$(Var$) + "' as filelist(y/n/q)?"
                CALL MorePrompt(Prompt$, "ynq", Outpt$)
                IF BreakIS THEN
                   Outpt$ = "q"
                END IF
                SELECT CASE Outpt$
                CASE "q"
                   Quit.Searching = True
                   Flag = False
                CASE "n"
                   Flag = False
                END SELECT
             END IF
          END IF

          ' open work filename
          IF Flag THEN
             V = FREEFILE
             Data.Error = False
             Error.Flag = True
             OPEN Filelist.Filename$ FOR INPUT SHARED AS #V
             Error.Flag = False

             ' check error flag
             IF Data.Error THEN
                OutregsX.Flags = &H1
                CALL DisplayError ("Error opening filelist.")
             ELSE
                ' process input filenames
                DO UNTIL EOF(V)
                   LINE INPUT #V, Next.Filename$
                   Next.Filename$ = RTRIM$(Next.Filename$)
                   IF LEN(Next.Filename$) THEN
                      ' process search filename
                      Command.Work = Next.Filename$
                      CALL ProcessCommand
                   END IF
                LOOP
                CLOSE #V
             END IF
          END IF
       ELSE

          ' display filename info
          Max.Recurse = 0
          CALL DisplayFiles(Directory.Search$, Filename$)
       END IF

       ' 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
SUB DisplayFiles (Search.Directory$, Search.Filename$)
 ' declare subroutine variables
 DIM Attribute AS INTEGER
 DIM BPBfile AS BPBtype
 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
 ASCIIZ.Display = Search.Directory$ + Search.Filename$ + CHR$(0)

 ' check file display type
 IF Display.Volume = False THEN
    ' check windows dos
    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

    ' 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$)

    ' store file attribute
    IF (OutregsX.Flags AND &H1) = &H0 THEN
       Attribute = OutregsX.CX
    ELSE
       Attribute = 0
    END IF

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

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

    ' check file attribute
    IF Display.Attribute THEN

       ' check for readonly file
       IF Display.Readonly THEN
	  IF (Attribute AND 1) <> 1 THEN
	     EXIT SUB
	  END IF
       END IF
       IF No.Display.Readonly THEN
	  IF (Attribute AND 1) = 1 THEN
	     EXIT SUB
	  END IF
       END IF

       ' check for hidden file
       IF Display.Hidden THEN
	  IF (Attribute AND 2) <> 2 THEN
	     EXIT SUB
	  END IF
       END IF
       IF No.Display.Hidden THEN
	  IF (Attribute AND 2) = 2 THEN
	     EXIT SUB
	  END IF
       END IF

       ' check for system file
       IF Display.System THEN
	  IF (Attribute AND 4) <> 4 THEN
	     EXIT SUB
	  END IF
       END IF
       IF No.Display.System THEN
	  IF (Attribute AND 4) = 4 THEN
	     EXIT SUB
	  END IF
       END IF

       ' check for directory file
       IF Display.Directory THEN
          IF (Attribute AND &H10) <> &H10 THEN
	     EXIT SUB
	  END IF
       END IF
       IF No.Display.Directory THEN
          IF (Attribute AND &H10) = &H10 THEN
	     EXIT SUB
	  END IF
       END IF

       ' check for archive file
       IF Display.Archive THEN
	  IF (Attribute AND &H20) <> &H20 THEN
	     EXIT SUB
	  END IF
       END IF
       IF No.Display.Archive THEN
	  IF (Attribute AND &H20) = &H20 THEN
	     EXIT SUB
	  END IF
       END IF

       ' check for no attributes
       IF Display.Any THEN
	  IF (Attribute AND 1) = 1 THEN
	     EXIT SUB
	  END IF
	  IF (Attribute AND 2) = 2 THEN
	     EXIT SUB
	  END IF
	  IF (Attribute AND 4) = 4 THEN
	     EXIT SUB
	  END IF
          IF (Attribute AND &H10) = &H10 THEN
	     EXIT SUB
	  END IF
	  IF (Attribute AND &H20) = &H20 THEN
	     EXIT SUB
	  END IF
       END IF
       IF No.Display.Any THEN
          IF (Attribute AND 1) = False THEN
             IF (Attribute AND 2) = False THEN
                IF (Attribute AND 4) = False THEN
                   IF (Attribute AND &H10) = False THEN
                      IF (Attribute AND &H20) = False THEN
                         EXIT SUB
                      END IF
		   END IF
		END IF
	     END IF
	  END IF
       END IF
    END IF

    ' check file size
    IF Search.File.Size THEN
       IF Search.Size.From = FalseD AND Search.Size.To = FalseD THEN
          IF File.Size <> FalseD THEN
	     EXIT SUB
	  END IF
       ELSE
          IF Search.Size.From > FalseD OR Search.Size.To > FalseD THEN
             IF Search.Size.From > FalseD THEN
                IF Search.Size.To = FalseD THEN
		   IF File.Size < Search.Size.From THEN
		      EXIT SUB
		   END IF
		END IF
	     END IF
             IF Search.Size.From = FalseD THEN
                IF Search.Size.To > FalseD THEN
		   IF File.Size > Search.Size.To THEN
		      EXIT SUB
		   END IF
		END IF
	     END IF
	     IF Search.Size.From <= Search.Size.To THEN
		IF File.Size < Search.Size.From OR File.Size > Search.Size.To THEN
		   EXIT SUB
		END IF
	     END IF
	     IF Search.Size.From > Search.Size.To THEN
		IF File.Size < Search.Size.From AND File.Size > Search.Size.To THEN
		   EXIT SUB
		END IF
	     END IF
	  END IF
       END IF
    END IF

    ' store file date and time
    Millisecond = False
    IF Windows.Detected THEN
       IF Creation.Time THEN
          File.Work.Time = ASC(MID$(TempWDTA.CreateTime, 2, 1))
          File.Work.Time = File.Work.Time * &H100 + ASC(MID$(TempWDTA.CreateTime, 1, 1))
          File.Work.Date = ASC(MID$(TempWDTA.CreateTime, 4, 1))
          File.Work.Date = File.Work.Date * &H100 + ASC(MID$(TempWDTA.CreateTime, 3, 1))
          InregsX.AX = &H7143
          InregsX.BX = &H08
          InregsX.DS = VARSEG(ASCIIZ.Display)
          InregsX.DX = VARPTR(ASCIIZ.Display)
          CALL InterruptX(&H21, InregsX, OutregsX)
          Millisecond = OutregsX.SI
       ELSE
          IF Access.Time THEN
             File.Work.Time = ASC(MID$(TempWDTA.AccessTime, 2, 1))
             File.Work.Time = File.Work.Time * &H100 + ASC(MID$(TempWDTA.AccessTime, 1, 1))
             File.Work.Date = ASC(MID$(TempWDTA.AccessTime, 4, 1))
             File.Work.Date = File.Work.Date * &H100 + ASC(MID$(TempWDTA.AccessTime, 3, 1))
          ELSE
             IF Modified.Time THEN
                File.Work.Time = ASC(MID$(TempWDTA.ModTime, 2, 1))
                File.Work.Time = File.Work.Time * &H100 + ASC(MID$(TempWDTA.ModTime, 1, 1))
                File.Work.Time = File.Work.Time AND NOT(&H1F) ' remove seconds
                File.Work.Date = ASC(MID$(TempWDTA.ModTime, 4, 1))
                File.Work.Date = File.Work.Date * &H100 + ASC(MID$(TempWDTA.ModTime, 3, 1))
             END IF
          END IF
       END IF
    ELSE
       File.Work.Time = ASC(MID$(TempDTA.FileTime, 2, 1))
       File.Work.Time = File.Work.Time * &H100 + ASC(MID$(TempDTA.FileTime, 1, 1))
       File.Work.Time = File.Work.Time AND NOT(&H1F) ' remove seconds
       File.Work.Date = ASC(MID$(TempDTA.FileDate, 2, 1))
       File.Work.Date = File.Work.Date * &H100 + ASC(MID$(TempDTA.FileDate, 1, 1))
    END IF
 END IF

 ' check date\time range
 IF Search.From.Date OR Search.To.Date THEN
    IF File.Work.Date < Search.From.Date THEN
       EXIT SUB
    END IF
    IF File.Work.Date > Search.To.Date THEN
       EXIT SUB
    END IF
 END IF
 IF Search.From.Time OR Search.To.Time THEN
    File.Work.Time = INT(File.Work.Time)
    IF Creation.Time THEN
       IF Millisecond >= 100 THEN
          File.Work.Time = File.Work.Time + .5!
       END IF
       IF File.Work.Time < Search.From.Time THEN
          EXIT SUB
       END IF
       IF File.Work.Time > Search.To.Time THEN
          EXIT SUB
       END IF
    END IF
    File.Work.Time = INT(File.Work.Time)
    IF Modified.Time THEN
       Search.From.Time2! = Search.From.Time AND NOT(&H1F)
       Search.To.Time2! = Search.To.Time AND NOT(&H1F)
       IF File.Work.Time < Search.From.Time2! THEN
          EXIT SUB
       END IF
       IF File.Work.Time > Search.To.Time2! THEN
          EXIT SUB
       END IF
    END IF
 END IF

 ' make filename
 File.List$ = Search.Filename$
 IF Display.Volume = False THEN
    Display.Filename$ = Search.Filename$
    IF Windows.Detected AND Short.Filenames THEN
       Display.Filename$ = TempWDTA.ASCIIZshort
       Imbedded = INSTR(Display.Filename$, CHR$(0))
       IF Imbedded THEN
          Display.Filename$ = LEFT$(Display.Filename$, Imbedded - 1)
       END IF
       Display.Filename$ = RTRIM$(Display.Filename$)
       IF Display.Filename$ = Nul THEN
          ' get windows short filename
          InregsX.AX = &H7160
          InregsX.CX = &H8001
          InregsX.DS = VARSEG(ASCIIZ.Display)
          InregsX.SI = VARPTR(ASCIIZ.Display)
          InregsX.ES = VARSEG(ASCIIZ.Short)
          InregsX.DI = VARPTR(ASCIIZ.Short)
          CALL InterruptX(&H21, InregsX, OutregsX)
          Display.Filename$ = ASCIIZ.Short
          Imbedded = INSTR(Display.Filename$, CHR$(0))
          IF Imbedded THEN
             Display.Filename$ = LEFT$(Display.Filename$, Imbedded - 1)
          END IF
          IF MID$(Display.Filename$, 2, 1) = ":" THEN
             Display.Filename$ = MID$(Display.Filename$, 3)
          END IF
          Imbedded1 = INSTR(Display.Filename$, "\")
          Imbedded2 = Imbedded1
          WHILE Imbedded1
             Imbedded2 = Imbedded1
             Imbedded1 = INSTR(Imbedded1 + 1, Display.Filename$, "\")
          WEND
          IF Imbedded2 THEN
             Display.Filename$ = MID$(Display.Filename$, Imbedded2 + 1)
          END IF
          Display.Filename$ = RTRIM$(Display.Filename$)
       END IF
    END IF
    IF Short.Display THEN
       File.List$ = Display.Filename$
    ELSE
       File.List$ = Search.Directory$ + Display.Filename$
    END IF
    IF No.Display.Drive = False THEN
       IF LEFT$(File.List$, 2) <> "\\" THEN
          File.List$ = Drive.Search + ":" + File.List$
       END IF
    END IF
    IF LEFT$(Search.Directory$, 2) = "\\" THEN
       IF MID$(File.List$, 2, 1) = ":" THEN
          File.List$ = MID$(File.List$, 3)
       END IF
    END IF
    IF (Attribute AND &H10) = &H10 THEN
       IF Display.Only.Errors = False THEN
          IF Remove.Slash = False THEN
             File.List$ = File.List$ + "\"
          END IF
       END IF
    END IF
 END IF

 ' display netpath volume label
 IF Display.Volume THEN

    ' check netpath
    IF LEFT$(Search.Directory$, 2) = "\\" THEN
       IF RIGHT$(Search.Directory$, 1) = "\" THEN
          ASCIIZ.Display = Search.Directory$ + "*.*" + CHR$(0)
       ELSE
          ASCIIZ.Display = Search.Directory$ + "\*.*" + CHR$(0)
       END IF

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

       ' display any errors
       IF (OutregsX.Flags AND &H1) = &H1 THEN
          CALL DisplayError ("Error reading volume label.")
       END IF

       ' check any errors
       IF (OutregsX.Flags AND &H1) = &H0 THEN

          ' store volume label
          ASCIIZ.Display = DTAfile.ASCIIZfilename

          ' display netpath volume label
          Volume.Label$ = ASCIIZ.Display
          Imbedded = INSTR(Volume.Label$, CHR$(0))
          IF Imbedded THEN
             Volume.Label$ = LEFT$(Volume.Label$, Imbedded - 1)
          END IF

          ' check excluded file
          IF LEN(Exclude.List) THEN
             Filename1$ = UCASE$(RTRIM$(Exclude.List))
             Filename2$ = UCASE$(RTRIM$(Volume.Label$))
             CALL CheckExcluded(Filename1$, Filename2$, Exclude.File)
             IF Exclude.File THEN
                EXIT SUB
             END IF
          END IF

          IF Double.Line THEN
             COLOR Yellow, Black
             PRINT Search.Directory$; " ";
          END IF
          COLOR White, Black
          PRINT RTRIM$(Volume.Label$)
       END IF

       ' increase counters
       Volumes.Counted = Volumes.Counted + 1
       Files.Counter = Files.Counter + 1

       ' check paginate
       CALL PagePrompt

       ' jump to end of sub
       GOTO End.Display
    END IF
 END IF

 ' check excluded file
 IF LEN(Exclude.List) THEN
    Filename1$ = UCASE$(RTRIM$(Exclude.List))
    Filename2$ = UCASE$(RTRIM$(Search.Filename$))
    CALL CheckExcluded(Filename1$, Filename2$, Exclude.File)
    IF Exclude.File THEN
       EXIT SUB
    END IF
 END IF

 ' check for directory bit
 IF (Attribute AND &H10) <> &H10 THEN
    ' increment files counted
    Files.Counted = Files.Counted + 1#
    Total.Bytes = Total.Bytes + File.Size
 END IF

 ' display directory/filename
 IF Display.Search = False THEN
    ' check for directory bit
    IF (Attribute AND &H10) = &H10 THEN
       Dirs.Displayed = Dirs.Displayed + 1#
    END IF

    ' format filename
    IF Display.Lowercase THEN
       Outpt$ = LCASE$(File.List$)
    ELSE
       IF Short.Filenames THEN
          Outpt$ = UCASE$(File.List$)
       ELSE
          Outpt$ = RTRIM$(File.List$)
       END IF
    END IF

    ' reset line length
    Length = LEN(Outpt$)

    ' display wide format
    IF Wide.Display THEN
       COLOR Yellow, Black
       Outpt$ = LEFT$(Outpt$, 14)
       PRINT Outpt$;
       PRINT SPACE$(15 - LEN(File.List$));
       Wide.List = Wide.List + 1
       IF Wide.List = 5 THEN
          Wide.List = False
          PRINT
          Files.Counter = Files.Counter + 1
       END IF
    ELSE
       ' check past line 22
       CALL PageCount
       IF Files.Counter > 22 THEN
          CALL PagePrompt
          IF Quit.Searching THEN
             EXIT SUB
          END IF
          ' reset display lines
          CALL PageCount
       END IF

       ' display filename
       COLOR Yellow, Black
       PRINT Outpt$

       ' increment volume counter
       IF Display.Volume THEN
          Volumes.Counted = Volumes.Counted + 1
       END IF
    END IF

    ' reset paginate
    CALL PagePrompt
    IF Quit.Searching THEN
       EXIT SUB
    END IF

    ' reset line length
    Length = False

    ' check extended display info
    IF Double.Line THEN
       Var = False ' set flag
       ' display file date\time
       COLOR Green, Black
       IF Windows.Detected THEN
          IF Creation.Time THEN
             GOSUB Make.Date1
             GOSUB Convert.Date
             GOSUB Convert.Time
             Outpt$ = File.Date$ + " " + File.Time$
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount
             Var = True
          END IF
          IF Access.Time THEN
             IF Creation.Time THEN
                Outpt$ = "\"
                Length = Length + LEN(Outpt$)
                PRINT Outpt$;
                GOSUB LengthCount
             END IF
             GOSUB Make.Date2
             GOSUB Convert.Date
             Outpt$ = File.Date$
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount
             Var = True
          END IF
          IF Modified.Time THEN
             IF Creation.Time OR Access.Time THEN
                Outpt$ = "\"
                Length = Length + LEN(Outpt$)
                PRINT Outpt$;
                GOSUB LengthCount
             END IF
             GOSUB Make.Date3
             GOSUB Convert.Date
             GOSUB Convert.Time
             Outpt$ = File.Date$ + " " + LEFT$(File.Time$, 5)
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount
             Var = True
          ENDIF
       ELSE
          GOSUB Make.Date4
          GOSUB Convert.Date
          GOSUB Convert.Time
          Outpt$ = File.Date$ + " " + LEFT$(File.Time$, 5)
          Length = Length + LEN(Outpt$)
          PRINT Outpt$;
          GOSUB LengthCount
          Var = True
       END IF
       IF Var THEN ' check flag
          Outpt$ = " "
          Length = Length + LEN(Outpt$)
          PRINT Outpt$;
          GOSUB LengthCount
       END IF

       ' check dos type
       IF Windows.DOS THEN
          ' check for directory bit
          IF (Attribute AND &H10) = &H10 THEN
             CALL ReadAccessDate(Search.Directory$, Search.Filename$, False)
          ELSE
             CALL ReadAccessDate(Search.Directory$, Search.Filename$, True)
          END IF
       END IF

       ' check display type
       IF Display.Volume THEN

          ' get volume info
          InregsX.AX = &H6900
          InregsX.BX = ASC(Drive.Search) - 64
          InregsX.DS = VARSEG(BPBfile)
          InregsX.DX = VARPTR(BPBfile)
          CALL InterruptX(&H21, InregsX, OutregsX)

          ' display any errors
          IF (OutregsX.Flags AND &H1) = &H1 THEN
             PRINT
             CALL DisplayError ("Error reading volume attribute.")
          END IF

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

             ' display volume serial number
             COLOR Red, Black
             FOR Serial.Digit = 4 TO 1 STEP -1
                IF Serial.Digit = 2 THEN
                   Outpt$ = "-"
                   Length = Length + LEN(Outpt$)
                   PRINT Outpt$;
                   GOSUB LengthCount
                END IF
                Digit = ASC(MID$(BPBfile.Serial, Serial.Digit, 1))
                Outpt$ = RIGHT$(HEX$(Digit + &H100), 2)
                Length = Length + LEN(Outpt$)
                PRINT Outpt$;
                GOSUB LengthCount
             NEXT
             Outpt$ = " "
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount

             ' display volume fat type
             COLOR White, Black
             Outpt$ = BPBfile.System
             Length = Length + LEN(Outpt$)
             PRINT Outpt$
             GOSUB LengthCount
          END IF
       END IF

       ' check display type
       IF Display.Volume = False THEN

          ' check for directory bit
          IF (Attribute AND &H10) = &H10 THEN
             Size$ = "<dir>"
          ELSE
             Size$ = Format$(File.Size, "#,##0;;0")
          END IF

          ' display file size
          COLOR Red, Black
          Outpt$ = Size$
          Length = Length + LEN(Outpt$)
          PRINT Outpt$;
          GOSUB LengthCount
          COLOR White, Black

          ' check for directory file
          IF (Attribute AND &H10) = &H10 THEN
             Outpt$ = " Directory"
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount
          END IF

          ' check for archive file
          IF (Attribute AND &H20) = &H20 THEN
             Outpt$ = " Archive"
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount
          END IF

          ' check for read-only file
          IF (Attribute AND 1) = 1 THEN
             Outpt$ = " Read-only"
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount
          END IF

          ' check for hidden file
          IF (Attribute AND 2) = 2 THEN
             Outpt$ = " Hidden"
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount
          END IF

          ' check for system file
          IF (Attribute AND 4) = 4 THEN
             Outpt$ = " System"
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount
          END IF
          PRINT
       END IF

       ' increment last line
       Files.Counter = Files.Counter + 1
    END IF

    ' check paginate
    CALL PagePrompt
    IF Quit.Searching THEN
       EXIT SUB
    END IF
 END IF
End.Display:
 ' check to run DOS command
 IF LEN(DOS.Command) THEN
    CALL ExecuteCommand(Search.Directory$ + Display.Filename$)
 END IF
 EXIT SUB

' calculate line length
LengthCount:
 ' check line length
 IF Length > 80 THEN
    Files.Counter = Files.Counter + 1
    Length = False
 END IF
 ' check past line 22
 IF Files.Counter > 22 THEN
    CALL PagePrompt
    Length = False
 END IF
 RETURN

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

Make.Date2:
 File.Work.Time = ASC(MID$(TempWDTA.AccessTime, 2, 1))
 File.Work.Time = File.Work.Time * &H100 + ASC(MID$(TempWDTA.AccessTime, 1, 1))
 File.Work.Date = ASC(MID$(TempWDTA.AccessTime, 4, 1))
 File.Work.Date = File.Work.Date * &H100 + ASC(MID$(TempWDTA.AccessTime, 3, 1))
 RETURN

Make.Date3:
 File.Work.Time = ASC(MID$(TempWDTA.ModTime, 2, 1))
 File.Work.Time = File.Work.Time * &H100 + ASC(MID$(TempWDTA.ModTime, 1, 1))
 File.Work.Date = ASC(MID$(TempWDTA.ModTime, 4, 1))
 File.Work.Date = File.Work.Date * &H100 + ASC(MID$(TempWDTA.ModTime, 3, 1))
 RETURN

Make.Date4:
 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))
 RETURN

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

Convert.Time:
 File.Work.Time = INT(File.Work.Time) ' remove half-second
 HourTemp! = INT(File.Work.Time / 2048)
 MinuteTemp! = INT((File.Work.Time AND &H7E0) / 32)
 SecondsTemp! = INT((File.Work.Time AND &H1F) * 2)
 IF Millisecond >= 100 THEN ' add one second
    SecondsTemp! = SecondsTemp! + 1!
 END IF
 File.Time$ = RIGHT$(STR$(HourTemp! + 100), 2) + ":"
 File.Time$ = File.Time$ + RIGHT$(STR$(MinuteTemp! + 100), 2) + ":"
 File.Time$ = File.Time$ + RIGHT$(STR$(SecondsTemp! + 100), 2)
 RETURN
END SUB

' counts display lines
SUB PageCount
 IF Length > 240 THEN
    Files.Counter = Files.Counter + 4
 ELSE
    IF Length > 160 THEN
       Files.Counter = Files.Counter + 3
    ELSE
       IF Length > 80 THEN
          Files.Counter = Files.Counter + 2
       ELSE
          Files.Counter = Files.Counter + 1
       END IF
    END IF
 END IF
END SUB

' check to paginate
SUB PagePrompt
 IF Files.Counter >= 22 THEN
    Files.Counter = False
    IF Continuous.Display = False THEN
       IF More.Display = False THEN
          Prompt$ = "More(y/n/c)?"
          CALL MorePrompt(Prompt$, "ync", Outpt$)
          IF BreakIS THEN
             Outpt$ = "n"
          END IF
          SELECT CASE Outpt$
          CASE "c"
             More.Display = True
          CASE "n"
             Quit.Searching = True
          END SELECT
       END IF
    END IF
 END IF
END SUB

' critical error trap
Error.Routine:
 Data.Error = ERR
 IF Error.Flag THEN
    RESUME NEXT
 END IF
 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 57
    Temp.Outpt$ = "Device I/O error."
 CASE 61
    Temp.Outpt$ = "Disk full."
 CASE 67
    Temp.Outpt$ = "Too many files."
 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.List THEN
    Wide.List = False
    PRINT
 END IF
 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.Whereis
 CASE "c"
    OutregsX.Flags = &H1
    RESUME NEXT
 END SELECT
 ' restore key trapping
 CALL RestInt
 END 0

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
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 = False 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

' routine displays directory or filename last access date in dos
SUB ReadAccessDate(Search.Directory$, Search.Filename$, SearchType%)
 ' declare some variables
 DIM DTAfile2 AS DTAtype2
 DIM FCBfile AS FCBtype
 DIM Current.DTA.Seg AS INTEGER
 DIM Current.DTA.Off AS INTEGER
 DIM ASCIIZ2 AS STRING * 260

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

 ' 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)

 ' make directory
 Directory$ = Search.Directory$
 IF RIGHT$(Directory$, 1) = "\" THEN
    Directory$ = LEFT$(Directory$, LEN(Directory$) - 1)
 END IF
 IF Directory$ = Nul THEN
    Directory$ = "\"
 END IF
 ASCIIZ2 = Directory$ + CHR$(0)

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

 ' check carry flag error
 IF (OutregsX.Flags AND &H1) = &H0 THEN
    ' check display type
    IF SearchType% = False THEN
       ' store directory name
       FCBfile.ExtendedFCB = CHR$(255)
       FCBfile.FileAttribute = CHR$(&H37)
       FCBfile.Filename = "."
       FCBfile.Extension = Nul
       FCBfile.DriveNumber = CHR$(ASC(Drive.Search) - 64)
    ELSE
       ' make filename
       Imbedded = INSTR(Search.Filename$, ".")
       IF Imbedded THEN
          Filename$ = LEFT$(Search.Filename$, Imbedded - 1)
          Extension$ = MID$(Search.Filename$, Imbedded + 1)
       ELSE
          Filename$ = Search.Filename$
          Extension$ = Nul
       END IF

       ' store filename
       FCBfile.ExtendedFCB = CHR$(255)
       FCBfile.FileAttribute = CHR$(&H27)
       FCBfile.Filename = Filename$
       FCBfile.Extension = Extension$
       FCBfile.DriveNumber = CHR$(ASC(Drive.Search) - 64)
    END IF

    ' 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)

 ' check access date
 IF File.Access.Date > 0 THEN
    YearTemp! = INT(File.Access.Date / 512)
    MonthTemp! = INT((File.Access.Date AND &H1E0) / 32)
    DayTemp! = INT(File.Access.Date AND &H1F)
    YearTemp! = YearTemp! + 1980

    File.Date$ = RIGHT$(STR$(MonthTemp! + 100), 2) + "-"
    File.Date$ = File.Date$ + RIGHT$(STR$(DayTemp! + 100), 2) + "-"
    File.Date$ = File.Date$ + MID$(STR$(YearTemp!), 2)
    COLOR Cyan, Black
    PRINT "(" +  File.Date$ + ") ";
 END IF
END SUB

' ambiguate function
FUNCTION Ambiguate$(Var$)
 ' initialize asciiz filenames
 DIM ASCIIZ1 AS STRING * 260
 DIM ASCIIZ2 AS STRING * 261
 IF Windows.Detected = False THEN
    Ambiguate$ = Var$
 END IF
 ' read windows 8.3 filename
 IF Windows.Detected THEN
    ' ambiguate filename
    ASCIIZ1 = Var$ + CHR$(0)
    InregsX.AX = &H7160
    InregsX.CX = &H8001
    InregsX.DS = VARSEG(ASCIIZ1)
    InregsX.SI = VARPTR(ASCIIZ1)
    InregsX.ES = VARSEG(ASCIIZ2)
    InregsX.DI = VARPTR(ASCIIZ2)
    CALL InterruptX(&H21, InregsX, OutregsX)

    ' check carry flag error
    IF (OutregsX.Flags AND &H1) = &H1 THEN
       Ambiguate$ = Nul
    END IF

    ' check carry flag error
    IF (OutregsX.Flags AND &H1) = &H0 THEN
       ' store windows 8.3 filename
       Var2$ = ASCIIZ2
       Imbedded = INSTR(Var2$, CHR$(0))
       IF Imbedded THEN
          Var2$ = LEFT$(Var2$, Imbedded - 1)
       END IF
       Ambiguate$ = Var2$
    END IF
 END IF
END FUNCTION

' subroutine to execute DOS command
SUB ExecuteCommand (DOS.Filename$)
 DIM ASCIIZ AS STRING * 260
 DIM DTAfile AS DTAtype
 DIM Current.DTA.SEG AS INTEGER, Current.DTA.OFF AS INTEGER

 ' make replacement parameters
 DOS.Pathname$ = DOS.Filename$
 Imbedded1 = INSTR(DOS.Pathname$, "\")
 Imbedded2 = False
 WHILE Imbedded1
    Imbedded2 = Imbedded1
    Imbedded1 = INSTR(Imbedded1 + 1, DOS.Pathname$, "\")
 WEND
 Filename$ = MID$(DOS.Pathname$, Imbedded2 + 1)
 IF Imbedded2 THEN
    DOS.Pathname$ = LEFT$(DOS.Pathname$, Imbedded2 - 1)
 ELSE
    DOS.Pathname$ = Nul
 END IF
 Extension$ = Nul
 Imbedded = INSTR(Filename$, ".")
 IF Imbedded THEN
    Extension$ = MID$(Filename$, Imbedded + 1)
    Filename$ = LEFT$(Filename$, Imbedded - 1)
 END IF

 ' store replacement parameters
 IF LEFT$(DOS.Filename$, 2) = "\\" THEN
    Parameters(1) = "\\"
    Parameters(2) = "\\"
    Parameters(3) = DOS.Pathname$
    Parameters(4) = DOS.Pathname$ + "\"
    Parameters(5) = DOS.Filename$
 ELSE
    Parameters(1) = Drive.Search + ":"
    Parameters(2) = Drive.Search + ":\"
    Parameters(3) = Drive.Search + ":" + DOS.Pathname$
    Parameters(4) = Drive.Search + ":" + DOS.Pathname$ + "\"
    Parameters(5) = Drive.Search + ":" + DOS.Filename$
 END IF
 Parameters(6) = DOS.Pathname$
 Parameters(7) = DOS.Pathname$ + "\"
 Parameters(8) = DOS.Filename$
 Parameters(9) = Filename$ + "." + Extension$
 Parameters(10) = Filename$
 Parameters(11) = "." + Extension$
 Parameters(12) = Extension$

 ' check display type
 IF Display.Volume = False THEN

    ' make directory filename
    ASCIIZ = DOS.Pathname$ + CHR$(0)

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

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

 ' create DOS command
 Execute.Command$ = DOS.Command

 ' replace parameters (fixed v3.4a 02/28/2006).
 Count = 1
 Do
    If Count >= Len(Execute.Command$) Then
       Exit Do
    Endif
    Imbed$ = Mid$(Execute.Command$, Count, 1)
    If Imbed$ = "%" Then
       Imbed2$ = Mid$(Execute.Command$, Count + 1, 1)
       Select Case Ucase$(Imbed2$)
       Case "1" To "9", "A" To "C"
          Count2 = Val("&H" + Imbed2$)
          Execute.Command$ = LEFT$(Execute.Command$, Count - 1) + _
          RTRIM$(Parameters(Count2)) + MID$(Execute.Command$, Count + 2)
          Count = Count + LEN(RTRIM$(Parameters(Count2)))
       Case Else
          Count = Count + 1
       End Select
    Else
       Count = Count + 1
    Endif
 Loop

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

 ' restore key trapping
 CALL RestInt

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

 ' start DOS command shell
 IF LEN(Execute.Command$) THEN
    ' check display counter
    IF Wide.List THEN
       Wide.List = False
       PRINT
    END IF

    ' read command path from environment
    Comspec$ = ENVIRON$("COMSPEC")
    IF LEN(Comspec$) THEN
       ' call shell routine
       Shell.Command$ = Comspec$ + " /E:4096 /C " + Execute.Command$
       SHELL Shell.Command$
    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)

 ' reset key trapping
 CALL SetInt

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

 ' check display type
 IF Display.Volume = False THEN

    ' 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 any errors
    CALL DisplayError ("Error accessing drive.")
 END IF
END SUB

' routine compares occurrence of filename1$ in filename2$ with pattern matching.
SUB CheckExcluded(Filename1$, Filename2$, Match)
 Match = True ' assume mask matches filename2.
 Length1 = 1
 Length2 = 1
 DO
    ' global replacement.
    IF MID$(Filename1$, Length1, 1) = "*" THEN
       DO
          Length1 = Length1 + 1
          IF Length1 > LEN(Filename1$) THEN
             EXIT SUB
          END IF
          ' global replacement followed by exclusion character.
          ' searches remaining string until exclusion character found or not.
          IF MID$(Filename1$, Length1, 1) = "^" THEN
             Length1 = Length1 + 1
             Not.Include$ = MID$(Filename1$, Length1, 1)
             DO
                IF Not.Include$ <> MID$(Filename2$, Length2, 1) THEN
                   Length2 = Length2 + 1
                ELSE
                   Match = False
                   EXIT SUB
                END IF
                IF Length2 > LEN(Filename2$) THEN
                   EXIT SUB
                END IF
             LOOP
          END IF
          ' global replacement followed by ? or another *
          ' skips to next character.
          IF MID$(Filename1$, Length1, 1) <> "*" THEN
             IF MID$(Filename1$, Length1, 1) <> "?" THEN
                EXIT DO
             END IF
          END IF
       LOOP
       ' global replacement.
       ' searches for next matching character.
       DO
          IF MID$(Filename1$, Length1, 1) = MID$(Filename2$, Length2, 1) THEN
             EXIT DO
          ELSE
             Length2 = Length2 + 1
          END IF
          IF Length2 > LEN(Filename2$) THEN
             EXIT DO
          END IF
       LOOP
    ELSE
       ' character replacement.
       ' matches any next character.
       IF MID$(Filename1$, Length1, 1) = "?" THEN
          Length1 = Length1 + 1
          Length2 = Length2 + 1
       ELSE
          ' exclusion character.
          ' checks next character unmatched.
          IF MID$(Filename1$, Length1, 1) = "^" THEN
             Length1 = Length1 + 1
             Not.Include$ = MID$(Filename1$, Length1, 1)
             IF Not.Include$ <> MID$(Filename2$, Length2, 1) THEN
                Length1 = Length1 + 1
                Length2 = Length2 + 1
             ELSE
                Match = False
                EXIT DO
             END IF
          ELSE
             ' matches next character.
             IF MID$(Filename1$, Length1, 1) = MID$(Filename2$, Length2, 1) THEN
                Length1 = Length1 + 1
                Length2 = Length2 + 1
             ELSE
                Match = False
                EXIT DO
             END IF
             ' check string lengths.
             IF Length1 > LEN(Filename1$) THEN
                IF Length2 <= LEN(Filename2$) THEN
                   Match = False
                END IF
                EXIT DO
             END IF
          END IF
       END IF
    END IF
 LOOP
END SUB
