REM file: Xdir.bas - Public Domain DOS Utility
REM Version 1.0a created 06/07/1995
REM Version 1.1a created 06/26/1996
REM Version 1.2a created 03/06/2001
REM Version 1.3a created 11/28/2004

' default integer variables
DEFINT A-Z
REM $DYNAMIC

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

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

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

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

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

' initialize filename buffer
DIM ASCIIZ AS STRING * 260
COMMON SHARED Directory.ASCIIZ AS STRING * 260
COMMON SHARED Drive.Search AS STRING * 1

' declare program dta
DIM BASIC.DTA.SEG AS INTEGER, BASIC.DTA.OFF AS INTEGER
COMMON SHARED TempDTA AS DTAtype, WDTAfile AS WDTAtype
COMMON SHARED SDTAfile AS WDTAtype

' declare volume structure
COMMON SHARED BPBfile AS BPBtype

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

' declare work variables
COMMON SHARED Continuous.Display AS INTEGER, Double.Line AS INTEGER
COMMON SHARED Files.Counted AS INTEGER, Files.Counter AS INTEGER
COMMON SHARED More.Display AS INTEGER, Short.Filenames AS INTEGER
COMMON SHARED Quit.Searching AS INTEGER, Windows.Detected AS INTEGER
COMMON SHARED Windows.DOS 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.Attribute AS INTEGER, Display.Errors AS INTEGER
COMMON SHARED Display.Volume AS INTEGER, Short.Filename AS INTEGER
COMMON SHARED Wide.Filename AS INTEGER, List.Lowercase AS INTEGER
COMMON SHARED Wide.List AS INTEGER

' declare file attribute search work variables
COMMON SHARED Display.Any AS INTEGER, No.Display.Any AS INTEGER
COMMON SHARED Display.Hidden AS INTEGER, No.Display.Hidden AS INTEGER
COMMON SHARED Display.System AS INTEGER, No.Display.System AS INTEGER
COMMON SHARED Display.Readonly AS INTEGER, No.Display.Readonly AS INTEGER
COMMON SHARED Display.Archive AS INTEGER, No.Display.Archive AS INTEGER
COMMON SHARED No.Display.Drive AS INTEGER, Length AS INTEGER

' declare file date and time work variables
COMMON SHARED File.Work.Date AS SINGLE, File.Work.Time AS SINGLE
COMMON SHARED HourTemp AS SINGLE, MinuteTemp AS SINGLE, SecondsTemp AS SINGLE
COMMON SHARED DayTemp AS SINGLE, MonthTemp AS SINGLE, YearTemp AS SINGLE
COMMON SHARED File.Date AS STRING * 10, File.Time AS STRING * 8
COMMON SHARED Creation.Time AS INTEGER, Access.Time AS INTEGER
COMMON SHARED Modified.Time AS INTEGER, Total.Bytes AS DOUBLE
COMMON SHARED Search.Size.From AS DOUBLE, Search.Size.To AS DOUBLE
COMMON SHARED Search.File.Size, File.Access.Date AS SINGLE

' 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, Control.Break AS INTEGER
COMMON SHARED Last.Switch AS INTEGER, Pipe.Buffer AS STRING * 1
COMMON SHARED Redirected.Input 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

' declare standard error trap
ON ERROR GOTO Error.Routine

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

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

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

' check windows
Windows.Detected = True
If Load.Windows = False Then
   InregsX.AX = &H160A
   CALL InterruptX(&H2F, InregsX, OutregsX)
   IF OutregsX.AX > 0 THEN
      InregsX.AX = &H4A33
      CALL InterruptX(&H2F, InregsX, OutregsX)
      IF OutregsX.AX = 0 THEN
         Windows.Detected = 0 ' DOS 7.00
      END IF
   END IF
END IF
IF INSTR(COMMAND$, "/_") THEN
   Windows.Detected = True
END IF
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$("XDIR")
END IF
Command.Line = RTRIM$(Command.Line)

' get switches from command line
Display.Archive = ParseLine ("//A")
Display.Hidden = ParseLine ("//H")
Display.Readonly = ParseLine ("//O")
Display.System = ParseLine ("//S")
Display.Any = ParseLine ("//X")
No.Display.Archive = ParseLine ("/A")
No.Display.Drive = ParseLine ("/B")
No.Display.Hidden = ParseLine ("/H")
No.Display.Readonly = ParseLine ("/O")
No.Display.System = ParseLine ("/S")
No.Display.Any = ParseLine ("/X")

Continuous.Display = ParseLine ("/C")
Double.Line = ParseLine ("/L")
List.Lowercase = ParseLine ("/M")
Short.Filenames = ParseLine ("/K")
Recurse.Directories = ParseLine ("/R")
Display.Volume = ParseLine ("/V")
Wide.Filename = ParseLine ("/W")
Short.Filename = ParseLine ("/Y")
Display.Errors = ParseLine ("/Z")
Control.Break = ParseLine ("/~")
Var = ParseLine("/_")

' check switches
IF Windows.Detected THEN
   IF Wide.Filename THEN
      Short.Filenames = True
   END IF
ELSE
   Short.Filenames = True
END IF

' set attribute display variable
IF Display.Any OR No.Display.Any THEN
   Display.Attribute = True
END IF
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.DiIsplay.Readonly THEN
   Display.Attribute = True
END IF
IF Display.System OR No.Display.System THEN
   Display.Attribute = True
END IF

' reset file counter variable
Files.Counted = False
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 = 0!
   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 = 0!
   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
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.Work = 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)

      ' 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 carry flag error
      CALL DisplayError("Error accessing drive.")

      ' check error flag
      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 carry flag error
         CALL DisplayError("Error accessing drive.")

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

            ' display file spec searched
            IF Continuous.Display = False THEN
               COLOR Yellow, Black
               IF LEFT$(Directory.Search$, 2) = "\\" THEN
                  PRINT "Searching: " + Directory.Search$ + Filename.Search$
               ELSE
                  PRINT "Searching: " + Drive.Search + ":" + Directory.Search$ + Filename.Search$
               END IF
            END IF

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

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

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

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

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

   ' check 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.Xdir:

' 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 Short.Filename AND Wide.Filename THEN
   IF Double.Line = False THEN
      IF Wide.List THEN
	 PRINT
      END IF
   END IF
END IF
IF Continuous.Display = False THEN
   COLOR Yellow, Black
   Total$ = "Files counted" + STR$(Files.Counted)
   IF Double.Line THEN
      Total$ = Total$ + " Total Bytes " + Format$(Total.Bytes, "#,##0;;0")
   END IF
   PRINT Total$
   Prompt$ = "Press <enter> to exit to DOS:"
   CALL MorePrompt(Prompt$, CHR$(13), Outpt$)
END IF

' restore key trapping
CALL RestInt

COLOR Plain, Black
END

' display program usage
Boot.Usage:
 ' restore key trapping
 CALL RestInt
 Var$=Inkey$
 ' make header
 COLOR White, Black
 PRINT "Xdir v1.3a: File display utility; "
 COLOR Yellow, Black
 PRINT "Usage:"
 PRINT "   Xdir [d:][\path\filename.ext][//ahosx][/bcdfklmnrtvwyz]"
 PRINT "Where:"
 PRINT "   /b  dont list drive letter     /c  continuous display"
 PRINT "   /l  double line display        /m  list files in lowercase"
 PRINT "   /k  use 8.3 short filenames    /nxxx  nested recurse levels"
 PRINT "   /r  recurse directories        /v  display only volume label"
 PRINT "   /w  wide filename list form    /y  short filename list form"
 PRINT "   /z  suppress error messages"
 PRINT "   display file ranges: (/1  creation, /2  accessed, /3  modified)"
 PRINT "     /d  range of file dates in form mm/dd/yyyy-mm/dd/yyyy"
 PRINT "     /t  range of file times in form hh:mm:ss-hh:mm:ss"
 PRINT "     /f  range of file size xxx-xxx in kilobytes"
 PRINT "   display files attributes:"
 PRINT "     // prefix for files with, / prefix for files without,"
 PRINT "       a  archive, h  hidden, o  read-only, s  system, x  none"
 COLOR Plain, Black
 END

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

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

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

' subroutine to access directories
SUB Directories (Directory.Search$, Filename.Search$)
 ' declare subroutine variables
 DIM Attribute AS INTEGER
 DIM DTAfile AS DTAtype
 DIM ASCIIZ AS STRING * 260
 DIM Sfile.Handle AS INTEGER
 GOSUB Restore.DTA

 ' check to display volume
 IF Display.Volume THEN

    ' make directory filename
    GOSUB Restore.DTA
    ASCIIZ = "\*.*" + CHR$(0)

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

    ' display carry flag error
    CALL DisplayError("Error reading volume attribute.")

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

    ' display volume label
    COLOR Yellow, Black
    Volume.Label$ = DTAfile.ASCIIZfilename

    ' truncate volume label
    Imbedded = INSTR(Volume.Label$, CHR$(0))
    IF Imbedded THEN
       Volume.Label$ = LEFT$(Volume.Label$, Imbedded - 1)
    END IF
    Volume.Label$ = RTRIM$(Volume.Label$)

    COLOR Yellow, Black
    IF List.Lowercase THEN
       PRINT LCASE$(Volume.Label$);
    ELSE
       PRINT UCASE$(Volume.Label$);
    END IF

    ' check display line type
    IF Double.Line THEN
       PRINT " ";

       ' construct date\time strings
       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))
       CALL MakeDate

       ' display volume date\time
       COLOR Green, Black
       PRINT File.Date; " "; File.Time; " ";

       ' 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 carry flag error
       CALL DisplayError("Error reading volume attribute.")

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

       ' display volume serial number
       COLOR Red, Black
       FOR Serial.Digit = 4 TO 1 STEP -1
          IF Serial.Digit = 2 THEN
             PRINT "-";
          END IF
          PRINT HEX$(ASC(MID$(BPBfile.Serial, Serial.Digit, 1)));
       NEXT
       PRINT " ";

       ' display volume fat type
       COLOR White, Black
       PRINT BPBfile.System;
       Files.Counter = Files.Counter + 1
    END IF
    PRINT
    EXIT SUB
 END IF

 ' make directory filename
 GOSUB Restore.DTA
 ASCIIZ = Directory.Search$ + "*.*" + CHR$(0)

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

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

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

 ' check to recurse directories
 IF Recurse.Directories THEN

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

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

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

          ' store directory name
          IF Windows.Detected THEN
             Directory$ = SDTAfile.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 directory
          InregsX.AX = &H714F
          InregsX.BX = Sfile.Handle
          InregsX.SI = &H1
          InregsX.ES = VARSEG(SDTAfile)
          InregsX.DI = VARPTR(SDTAfile)
          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 = Sfile.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 display a filename
SUB DisplayFiles (Display.Filename$)

 ' declare subroutine variables
 DIM ASCIIZ AS STRING * 260
 DIM Attribute AS INTEGER

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

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

 ' store file attribute
 Attribute = OutregsX.CX

 ' read extended attributes
 CALL GetAttributes (Display.Filename$)

 ' check file attribute
 IF Display.Attribute THEN
    ' check for none
    IF Display.Any THEN
       IF (Attribute AND &H1) = &H1 THEN
          EXIT SUB
       END IF
       IF (Attribute AND &H2) = &H2 THEN
          EXIT SUB
       END IF
       IF (Attribute AND &H4) = &H4 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 &H1) = False THEN
          IF (Attribute AND &H2) = False THEN
             IF (Attribute AND &H4) = 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

    ' check for read-only file
    IF Display.Readonly THEN
       IF (Attribute AND &H1) = False THEN
          EXIT SUB
       END IF
    END IF
    IF No.Display.Readonly THEN
       IF (Attribute AND &H1) = &H1 THEN
          EXIT SUB
       END IF
    END IF

    ' check for hidden file
    IF Display.Hidden THEN
       IF (Attribute AND &H2) = False THEN
          EXIT SUB
       END IF
    END IF
    IF No.Display.Hidden THEN
       IF (Attribute AND &H2) = &H2 THEN
          EXIT SUB
       END IF
    END IF

    ' check for system file
    IF Display.System THEN
       IF (Attribute AND &H4) = False THEN
          EXIT SUB
       END IF
    END IF
    IF No.Display.System THEN
       IF (Attribute AND &H4) = &H4 THEN
          EXIT SUB
       END IF
    END IF

    ' check for archive file
    IF Display.Archive THEN
       IF (Attribute AND &H20) = False THEN
          EXIT SUB
       END IF
    END IF
    IF No.Display.Archive THEN
       IF (Attribute AND &H20) = &H20 THEN
          EXIT SUB
       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
 IF Windows.Detected THEN
    Millisecond = 0
    IF Creation.Time THEN
       InregsX.AX = &H7143
       InregsX.BX = &H08
       InregsX.DS = VARSEG(ASCIIZ)
       InregsX.DX = VARPTR(ASCIIZ)
       CALL InterruptX(&H21, InregsX, OutregsX)
       Millisecond = OutregsX.SI
       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
    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))
 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

 ' increment files counted
 Files.Counted = Files.Counted + 1

 ' increment total bytes counted
 Total.Bytes = Total.Bytes + File.Size

 ' check for filename display type
 Length = 0
 IF Short.Filename = False THEN
    List.Filename$ = Display.Filename$
    IF No.Display.Drive = False THEN
       IF LEFT$(List.Filename$, 2) <> "\\" THEN
          List.Filename$ = Drive.Search + ":" + List.Filename$
       END IF
    END IF
    IF List.Lowercase THEN
       Outpt$ = LCASE$(List.Filename$)
    ELSE
       IF Short.Filenames THEN
          Outpt$ = UCASE$(List.Filename$)
       ELSE
          Outpt$ = RTRIM$(List.Filename$)
       END IF
    END IF
    Length = LEN(Outpt$)
    CALL PageCount
    IF Files.Counter > 22 THEN
       CALL PagePrompt
       CALL PageCount
    END IF
    COLOR Yellow, Black
    PRINT Outpt$
    CALL PagePrompt
    Length = 0
 ELSE
    List.Filename$ = Display.Filename$
    Imbedded = INSTR(List.Filename$, "\")
    WHILE Imbedded
       List.Filename$ = MID$(List.Filename$, Imbedded + 1)
       Imbedded = INSTR(List.Filename$, "\")
    WEND
    IF No.Display.Drive = False THEN
       IF LEFT$(List.Filename$, 2) <> "\\" THEN
          List.Filename$ = Drive.Search + ":" + List.Filename$
       END IF
    END IF
    IF List.Lowercase THEN
       Outpt$ = LCASE$(List.Filename$)
    ELSE
       IF Short.Filenames THEN
          Outpt$ = UCASE$(List.Filename$)
       ELSE
          Outpt$ = RTRIM$(List.Filename$)
       END IF
    END IF
    COLOR Yellow, Black
    PRINT Outpt$;
    Length = LEN(Outpt$)
    IF Wide.Filename = False THEN
       PRINT
       Length = 0
       Files.Counter = Files.Counter + 1
    ELSE
       PRINT SPACE$(15 - LEN(Outpt$));
       Wide.List = Wide.List + 1
       IF Wide.List = 5 THEN
          Wide.List = False
          IF Double.Line = False THEN
             PRINT
             Length = 0
             Files.Counter = Files.Counter + 1
          END IF
       END IF
    END IF
 END IF
 CALL PagePrompt
 IF Quit.Searching THEN
    EXIT SUB
 END IF

 ' check display type
 IF Double.Line THEN

    ' check windows dos
    IF Windows.Detected THEN
       ' check date\time types and display
       IF Creation.Time THEN
          CALL Make.Date1
          CALL MakeDate
          COLOR Red, Black
          Outpt$ = File.Date + " " + File.Time
          Length = Length + LEN(Outpt$)
          PRINT Outpt$;
          GOSUB LengthCount
       END IF
       IF Access.Time THEN
          CALL Make.Date2
          CALL MakeDate
          IF Creation.Time THEN
             COLOR White, Black
             Outpt$ = "\"
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount
          END IF
          COLOR Red, Black
          Outpt$ = File.Date
          Length = Length + LEN(Outpt$)
          PRINT Outpt$;
          GOSUB LengthCount
       END IF
       IF Modified.Time THEN
          CALL Make.Date3
          CALL MakeDate
          IF Creation.Time OR Access.Time THEN
             COLOR White, Black
             Outpt$ = "\"
             Length = Length + LEN(Outpt$)
             PRINT Outpt$;
             GOSUB LengthCount
          END IF
          COLOR Red, Black
          Outpt$ = File.Date + " " + LEFT$(File.Time, 5)
          Length = Length + LEN(Outpt$)
          PRINT Outpt$;
          GOSUB LengthCount
       END IF
    ELSE
       ' construct dos file date\time and display
       CALL MakeDate
       COLOR Red, Black
       Outpt$ = File.Date + " " + LEFT$(File.Time, 5)
       Length = Length + LEN(Outpt$)
       PRINT Outpt$;
       GOSUB LengthCount
       IF File.Access.Date > 0 THEN
          File.Work.Date = File.Access.Date
          CALL MakeDate
          COLOR Cyan, Black
          Outpt$ = " (" + File.Date + ")"
          Length = Length + LEN(Outpt$)
          PRINT Outpt$;
          GOSUB LengthCount
       END IF
    END IF

    ' display file size
    IF Short.Filename = False AND Wide.Filename = False THEN
       COLOR Green, Black
       FileSize$ = Format$(File.Size, "#,##0;;0")
       Outpt$ = " " + FileSize$
       Length = Length + LEN(Outpt$)
       PRINT Outpt$;
       GOSUB LengthCount
    END IF
    COLOR White, Black

    ' 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 &H1) = &H1 THEN
       Outpt$ = " Read-only"
       Length = Length + LEN(Outpt$)
       PRINT Outpt$;
       GOSUB LengthCount
    END IF

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

    ' check for system file
    IF (Attribute AND &H4) = &H4 THEN
       Outpt$ = " System"
       Length = Length + LEN(Outpt$)
       PRINT Outpt$;
       GOSUB LengthCount
    END IF
    PRINT
    Files.Counter = Files.Counter + 1
    CALL PagePrompt
 END IF
 EXIT SUB

' calculate line length
LengthCount:
 ' check line length
 IF Length > 80 THEN
    Files.Counter = Files.Counter + 1
    Length = 0
 END IF
 ' check past line 22
 IF Files.Counter > 22 THEN
    CALL PagePrompt
    Length = 0
 END IF
 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

' subroutine to access filenames in a directory
SUB Filenames (Directory.Search$, Filename.Search$)
 ' declare subroutine variables
 DIM DTAfile AS DTAtype
 DIM ASCIIZ AS STRING * 260
 DIM Wfile.Handle AS INTEGER
 DIM ASCIIZ1 AS STRING * 260
 DIM ASCIIZ2 AS STRING * 68
 DIM Attribute AS INTEGER

 ' make filename
 ASCIIZ = Directory.Search$ + Filename.Search$ + CHR$(0)
 GOSUB Restore.File.DTA

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

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

 ' search filenames
 DO

    ' check directory attribute
    IF Windows.Detected THEN
       Attribute = ASC(WDTAfile.FileBits)
    ELSE
       Attribute = ASC(DTAfile.FileBits)
    END IF
    IF (Attribute AND &H10) <> &H10 THEN

       ' store filename
       IF Windows.Detected THEN
          IF Short.Filenames THEN
             Filename$ = WDTAfile.ASCIIZshort
          ELSE
             Filename$ = WDTAfile.ASCIIZfull
          END IF
       ELSE
          Filename$ = DTAfile.ASCIIZfilename
       END IF
       Filename$ = LEFT$(Filename$, INSTR(Filename$, CHR$(0)) - 1)

       ' store file size
       IF Windows.Detected THEN
          File.Size = False
          File.Size = File.Size * &H100 + ASC(MID$(WDTAfile.FileSizeHigh, 4, 1))
          File.Size = File.Size * &H100 + ASC(MID$(WDTAfile.FileSizeHigh, 3, 1))
          File.Size = File.Size * &H100 + ASC(MID$(WDTAfile.FileSizeHigh, 2, 1))
          File.Size = File.Size * &H100 + ASC(MID$(WDTAfile.FileSizeHigh, 1, 1))
          File.Size = File.Size * &H100 + ASC(MID$(WDTAfile.FileSizeLow, 4, 1))
          File.Size = File.Size * &H100 + ASC(MID$(WDTAfile.FileSizeLow, 3, 1))
          File.Size = File.Size * &H100 + ASC(MID$(WDTAfile.FileSizeLow, 2, 1))
          File.Size = File.Size * &H100 + ASC(MID$(WDTAfile.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

       ' check short filename
       IF Windows.Detected AND Short.Filenames THEN
          IF RTRIM$(Filename$) = "" THEN
             ' get windows short filename
             ASCIIZ1 = Directory.Search$ + WDTAfile.ASCIIZfull
             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)
             Filename$ = ASCIIZ2
             Imbedded = INSTR(Filename$, CHR$(0))
             IF Imbedded THEN
                Filename$ = LEFT$(Filename$, Imbedded - 1)
             END IF
             Filename$ = RTRIM$(Filename$)
             IF MID$(Filename$, 2, 1) = ":" THEN
                Filename$ = MID$(Filename$, 3)
             END IF
             Imbedded1 = INSTR(Filename$, "\")
             Imbedded2 = Imbedded1
             WHILE Imbedded1
                Imbedded2 = Imbedded1
                Imbedded1 = INSTR(Imbedded1 + 1, Filename$, "\")
             WEND
             IF Imbedded2 THEN
                Filename$ = MID$(Filename$, Imbedded2 + 1)
             END IF
             Filename$ = RTRIM$(Filename$)
          END IF
       END IF

       ' display filename
       TempDTA = DTAfile
       CALL DisplayFiles(Directory.Search$ + Filename$)
       GOSUB Restore.File.DTA
    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(WDTAfile)
       InregsX.DI = VARPTR(WDTAfile)
       CALL InterruptX(&H21, InregsX, OutregsX)
    ELSE
       ' find next filename
       InregsX.AX = &H4F00
       CALL InterruptX(&H21, InregsX, OutregsX)
    END IF

    ' check findfirst 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.File.DTA:
 ' restore directory search dta
 InregsX.AX = &H1A00
 InregsX.DS = VARSEG(DTAfile)
 InregsX.DX = VARPTR(DTAfile)
 CALL InterruptX(&H21, InregsX, OutregsX)
 RETURN
END SUB

SUB MakeDate
 ' construct file date and time for display
 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
    SecondsTemp = SecondsTemp + 1!
 END IF

 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) + "-" + RIGHT$(STR$(DayTemp + 100), 2) + "-" + MID$(STR$(YearTemp), 2)
 File.Time = RIGHT$(STR$(HourTemp + 100), 2) + ":" + RIGHT$(STR$(MinuteTemp + 100), 2) + ":" + RIGHT$(STR$(SecondsTemp + 100), 2)
END SUB

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

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

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

' routine gets extended file date\time in dos 7.00
SUB GetAttributes (Search.Directory$)
 ' 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 = 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)

 ' make directory
 FOR Imbedded = LEN(Search.Directory$) TO 1 STEP -1
    IF MID$(Search.Directory$, Imbedded, 1) = "\" THEN
       Search.Filename$ = MID$(Search.Directory$, Imbedded + 1)
       Directory$ = LEFT$(Search.Directory$, Imbedded - 1)
       EXIT FOR
    END IF
 NEXT
 IF RIGHT$(Directory$, 1) = "\" THEN
    Directory$ = LEFT$(Directory$, LEN(Directory$) - 1)
 END IF
 IF Directory$ = "" 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
    ' 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$ = ""
    END IF

    ' store directory name
    FCBfile.ExtendedFCB = CHR$(255)
    FCBfile.FileAttribute = CHR$(&H27)
    FCBfile.Filename = Filename$
    FCBfile.Extension = 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

' 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 57
    Temp.Outpt$ = "Device I/O error."
 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
 ' display end program
 IF Short.Filename AND Wide.Filename THEN
    IF Double.Line = False THEN
       IF Wide.List THEN
          Files.Counter = False
          PRINT
       END IF
    END IF
 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.Xdir
 CASE "c"
    OutregsX.Flags = &H1
    RESUME NEXT
 END SELECT
 COLOR Plain, Black
 ' 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 flag
FUNCTION BreakIS
 STATIC Var AS INTEGER
 IF Redirected.Input THEN
    DEF SEG = &H40
    X = PEEK(&H71)
    DEF SEG
    IF X = 64 THEN
       Var = True
    END IF
 END IF
 IF KeyIS THEN
    IF OutregsX.AX = 0 THEN
       Var = True
       DEF SEG = &H40
       POKE &H71, 64
       DEF SEG
    END IF
 END IF
 IF Var THEN
    Continuous.Display = True
 END IF
 BreakIS = Var
END FUNCTION

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

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