REM file: Dirs.bas - Public Domain DOS Utility
REM Version 1.0a created 06/08/1995
REM Version 1.1a created 02/01/1997

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

' 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 Green = 10
CONST Plain = 7
CONST Red = 12
CONST White = 15
CONST Yellow = 14

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

' declare functions
DECLARE FUNCTION ParseLine (S$)

' initialize filename buffer
COMMON SHARED ASCIZ AS STRING * 260, DTAfile AS DTAtype
COMMON SHARED WDTAfile AS WDTAtype, Wfile.Handle AS INTEGER
COMMON SHARED Attribute AS INTEGER

' initialize drive variables
COMMON SHARED Drive.Number AS INTEGER, Current.Drive AS INTEGER

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

' declare registers
COMMON SHARED InregsX AS RegTypeX, OutregsX AS RegTypeX
COMMON SHARED Inregs AS RegType, Outregs AS RegType

' declare work variables
COMMON SHARED Continuous.Display AS INTEGER, Display.Errors AS INTEGER
COMMON SHARED Root.Display AS INTEGER, Short.Display AS INTEGER
COMMON SHARED Append.Slash AS INTEGER, Files.Counter AS INTEGER
COMMON SHARED Continue.Listing AS INTEGER, Quit.Searching AS INTEGER
COMMON SHARED Parent.Display AS INTEGER, Windows.Detected AS INTEGER
COMMON SHARED No.Display.Drive AS INTEGER, Display.Lowercase AS INTEGER
COMMON SHARED Total.Dirs AS INTEGER

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

' increase stack size
STACK STACK

' declare standard error trap
ON ERROR GOTO Error.Routine

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

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

' get current drive
Inregs.AX = &H1900
CALL Interrupt(&H21, Inregs, Outregs)
Current.Drive = Outregs.AX AND &HFF

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

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

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

' get switches from command line
Append.Slash = ParseLine ("/A")
No.Display.Drive = ParseLine ("/B")
Continuous.Display = ParseLine ("/C")
Parent.Display = ParseLine ("/V")
Display.Lowercase = ParseLine ("/W")
Root.Display = ParseLine ("/X")
Short.Display = ParseLine ("/Y")
Display.Errors = ParseLine ("/Z")

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

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

' make header
IF Continuous.Display = False THEN
   COLOR White, Black
   PRINT "Dirs v1.1a: Directory display utility; "
END IF

' reset some variables
Files.Counter = False
Continue.Listing = False
Quit.Searching = False

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

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

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

   ' store original command line
   Command.Line = Command.Line.Redirect

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

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

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

      ' check windows dos
      IF Windows.Detected THEN
         ' get current directory
         InregsX.AX = &H7147
         InregsX.DX = Drive.Number + 1
         InregsX.DS = VARSEG(ASCIZ)
         InregsX.SI = VARPTR(ASCIZ)
         CALL InterruptX(&H21, InregsX, OutregsX)
      ELSE
         ' get current directory
         InregsX.AX = &H4700
         InregsX.DX = Drive.Number + 1
         InregsX.DS = VARSEG(ASCIZ)
         InregsX.SI = VARPTR(ASCIZ)
         CALL InterruptX(&H21, InregsX, OutregsX)
      END IF

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

      ' check carry flag error
      IF (OutregsX.Flags AND &H1) = &H0 THEN
         ' store current directory
         Directory.Search$ = "\" + LEFT$(ASCIZ, INSTR(ASCIZ, CHR$(0)) - 1)
         Imbedded = INSTR(Command.Work, "\")
         Imbedded2 = Imbedded
         WHILE Imbedded
            Imbedded2 = Imbedded
            Imbedded = INSTR(Imbedded + 1, Command.Work, "\")
         WEND
         IF Imbedded2 THEN
            Directory.Search$ = LEFT$(Command.Work, Imbedded2)
            Command.Work = MID$(Command.Work, Imbedded2 + 1)
         END IF
   
         ' get filename spec
         Filename.Search$ = Command.Work
         IF Filename.Search$ = NUL THEN
            IF RIGHT$(Directory.Search$, 1) <> "\" THEN
               Directory.Search$ = Directory.Search$ + "\"
            END IF
         ELSE
            IF RIGHT$(Directory.Search$, 1) = "\" THEN
               Directory.Search$ = Directory.Search$ + Filename.Search$
            ELSE
               Directory.Search$ = Directory.Search$ + "\" + Filename.Search$
            END IF
         END IF
         Command.Work = NUL

         ' change to drive
         Inregs.AX = &HE00
         Inregs.DX = Drive.Number
         CALL Interrupt(&H21, Inregs, Outregs)
   
         ' make directory filename
         IF RIGHT$(Directory.Search$, 1) <> "\" THEN
            Directory.Search$ = Directory.Search$ + "\"
         END IF
         Directory.File$ = Directory.Search$
         ASCIZ = Directory.Search$ + "*.*" + CHR$(0)

         ' make directory path filename
         IF RIGHT$(Directory.File$, 1) <> "\" THEN
            Imbedded = LEN(Directory.File$)
            DO WHILE Imbedded > False
               Imbedded = Imbedded - 1
               IF MID$(Directory.File$, Imbedded, 1) = "\" THEN
                  Directory.File$ = LEFT$(Directory.File$, Imbedded)
                  EXIT DO
               END IF
            LOOP
         END IF

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

         ' check findirst error
         IF (OutregsX.Flags AND &H1) = &H1 THEN
            IF OutregsX.Ax <> &H12 THEN
               CALL DisplayError ("Error reading directory.")
            END IF
            EXIT DO
         END IF

         ' display root directories
         IF Parent.Display = False THEN
            Directory$ = Directory.File$
            IF RIGHT$(Directory$, 1) = "\" THEN
               Directory$ = LEFT$(Directory$, LEN(Directory$) - 1)
            END IF
            IF Directory$ = NUL THEN
               Directory$ = "\"
            END IF
            GOSUB Display.Parent
         END IF

         ' check root display flag
         IF Root.Display = False THEN
            ' display .
            Directory.Name$ = "."
            GOSUB Display.Directory

            ' display ..
            IF Directory.File$ <> "\" THEN
               Directory.Name$ = ".."
               GOSUB Display.Directory
            END IF
         END IF

         ' list directories
         DO
            ' check for directory
            IF Windows.Detected THEN
               Attribute = ASC(WDTAfile.FileAttr)
            ELSE
               Attribute = ASC(DTAfile.FileAttr)
            END IF
            IF (Attribute AND &H10) = &H10 THEN
               ' make directory name
               IF Windows.Detected THEN
                  Directory.Name$ = WDTAfile.ASCIZfull
               ELSE
                  Directory.Name$ = DTAfile.ASCIZfilename
               END IF
               Imbedded = INSTR(Directory.Name$, CHR$(0))
               Directory.Name$ = LEFT$(Directory.Name$, Imbedded - 1)

               ' check directory display type
               IF Directory.Name$ <> "." AND Directory.Name$ <> ".." THEN
                  ' display directory
                  GOSUB Display.Directory

                  ' check search flag
                  IF Quit.Searching THEN
                     EXIT DO
                  END IF
               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(WDTAfile)
               InregsX.DI = VARPTR(WDTAfile)
               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

         ' check continue listing
         IF Quit.Searching THEN
            EXIT DO
         END IF
      END IF

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

   ' check search filename
   IF Standard.Input$ = NUL 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
End.Copy:
' restore basic dta
InregsX.AX = &H1A00
InregsX.DS = BASIC.DTA.SEG
InregsX.DX = BASIC.DTA.OFF
CALL InterruptX(&H21, InregsX, OutregsX)

' restore current drive
Inregs.AX = &HE00
Inregs.DX = Current.Drive
CALL Interrupt(&H21, Inregs, Outregs)

' display end program
IF Continuous.Display = False THEN
   COLOR White, Black
   PRINT "Directories counted:" + STR$(Total.Dirs)
   Prompt$ = "Press <enter> to exit to DOS:"
   CALL MorePrompt(Prompt$, CHR$(13), Outpt$)
END IF
COLOR Plain, Black
END

Display.Directory:
 ' check directory display type
 IF Short.Display = False THEN
    Directory$ = Directory.File$ + Directory.Name$
 ELSE
    Directory$ = Directory.Name$
 END IF
Display.Parent:
 ' check directory display type
 IF Append.Slash THEN
    IF RIGHT$(Directory$, 1) <> "\" THEN
       Directory$ = Directory$ + "\"
    END IF
 END IF

 ' increment counter
 Total.Dirs = Total.Dirs + 1

 ' display directory
 COLOR Yellow, Black
 IF No.Display.Drive = False THEN
    File.List$ = CHR$(Drive.Number + 65) + ":" + Directory$
 ELSE
    File.List$ = Directory$
 END IF
 IF Display.Lowercase THEN
    PRINT LCASE$(File.List$)
 ELSE
    IF Windows.Detected THEN
       PRINT File.List$
    ELSE
       PRINT UCASE$(File.List$)
    END IF
 END IF

 ' check files counter
 Files.Counter = Files.Counter + 1
 IF Files.Counter >= 21 THEN
    Files.Counter = False
    IF Continuous.Display = False THEN
       IF Continue.Listing = False THEN
          Prompt$ = "More(y/n/c)?"
          CALL MorePrompt(Prompt$, "ync", Outpt$)
          SELECT CASE Outpt$
	  CASE "c"
             Continue.Listing = True
	  CASE "n"
             Quit.Searching = True
	  END SELECT
       END IF
    END IF
 END IF
 RETURN

' display program usage
Boot.Usage:
 ' make header
 COLOR White, Black
 PRINT "Dirs v1.1a: Directory display utility; "
 COLOR Yellow, Black
 PRINT "Usage:"
 PRINT "   Dirs [d:][\path\][/abcvwxyz]"
 PRINT "Where:"
 PRINT "   /a  append directory path."
 PRINT "   /b  dont display drive letter."
 PRINT "   /c  continuous display."
 PRINT "   /v  don't display parent."
 PRINT "   /w  display lowercase."
 PRINT "   /x  don't display . or .."
 PRINT "   /y  short directory display."
 PRINT "   /z  supress error messages."
 COLOR Plain, Black
 END

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

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

SUB MorePrompt (Input.String$, Input.Mask$, Output.String$)
 COLOR White, Black
 PRINT Input.String$ + " ";
 Input.Char$ = NUL
 DO
    LOCATE , , 1
    Input.Char$ = INKEY$
    IF LEN(Input.Char$) THEN
       Input.Char$ = LCASE$(Input.Char$)
       IF INSTR(Input.Mask$, Input.Char$) THEN
	  PRINT Input.Char$
	  Output.String$ = Input.Char$
	  EXIT DO
       END IF
    END IF
 LOOP
END SUB

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