REM file: Touch.bas - Public Domain DOS Utility
REM Version 1.0a created 12/29/1994
REM Version 1.1a created 01/27/1995
REM Version 1.2a created 02/05/1995
REM Version 1.3a created 04/25/1996
REM Version 1.4a created 06/08/1999
REM Version 1.5a created 04/15/2001
REM Version 1.6a created 11/17/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: 'wdta.bi'

' declare subroutines
DECLARE SUB Directories (d$, f$)
DECLARE SUB Filenames (d$, f$)
DECLARE SUB TouchFiles (f$)

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

' declare filename buffer
DIM ASCIIZ AS STRING * 260

' 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 InregsX2 AS RegtypeX, InregsX3 AS RegtypeX

' declare switch variables
COMMON SHARED Continuous.Display AS INTEGER, Prompt.Files AS INTEGER
COMMON SHARED Recurse.Directories AS INTEGER, Display.Errors AS INTEGER
COMMON SHARED Files.Counter AS INTEGER, Quit.Touching AS INTEGER
COMMON SHARED Create.Zero AS INTEGER, Windows.DOS AS INTEGER

' declare attribute variables
COMMON SHARED No.Touch.Archive AS INTEGER, No.Touch.Hidden AS INTEGER
COMMON SHARED No.Touch.Readonly AS INTEGER, No.Touch.System AS INTEGER
COMMON SHARED No.Touch.Any AS INTEGER

COMMON SHARED Touch.Archive AS INTEGER, Touch.Hidden AS INTEGER
COMMON SHARED Touch.Readonly AS INTEGER, Touch.System AS INTEGER
COMMON SHARED Touch.Any AS INTEGER

COMMON SHARED Touch.Create AS INTEGER, Touch.Access AS INTEGER
COMMON SHARED Touch.Modify AS INTEGER

' declare count variables
COMMON SHARED Archives.Touched AS DOUBLE, Hidden.Touched AS DOUBLE
COMMON SHARED System.Touched AS DOUBLE, Readonly.Touched AS DOUBLE
COMMON SHARED Files.Touched AS DOUBLE

' declare drive work variables
COMMON SHARED Drive.Number AS INTEGER, Current.Drive AS INTEGER
COMMON SHARED Search.Drive AS INTEGER, Windows.Detected AS INTEGER
COMMON SHARED Directory.ASCIIZ AS STRING * 260

' declare date\time work variables
COMMON SHARED HourTemp AS SINGLE, MinuteTemp AS INTEGER, SecondTemp AS INTEGER
COMMON SHARED MonthTemp AS INTEGER, DayTemp AS INTEGER, YearTemp AS SINGLE
COMMON SHARED Millisecond 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

' 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 = (OutregsX.AX AND &HFF) + 65 ' 0=a, 1=b, ..

' 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 = Current.Drive - 64 ' 1=a, 2=b, ..
   InregsX.DS = VARSEG(Directory.ASCIIZ)
   InregsX.SI = VARPTR(Directory.ASCIIZ)
   CALL InterruptX(&H21, InregsX, OutregsX)
ELSE
   ' get current directory
   InregsX.AX = &H4700
   InregsX.DX = Current.Drive - 64 ' 1=a, 2=b, ..
   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$("TOUCH")
END IF
Command.Line = RTRIM$(Command.Line)

' check command line switches
No.Touch.Archive = ParseLine ("//A")
No.Touch.Hidden = ParseLine ("//H")
No.Touch.Readonly = ParseLine ("//O")
No.Touch.System = ParseLine ("//S")
No.Touch.Any = ParseLine ("//Y")
Touch.Archive = ParseLine ("/A")
Touch.Hidden = ParseLine ("/H")
Touch.Readonly = ParseLine ("/O")
Touch.System = ParseLine ("/S")
Touch.Any = ParseLine ("/Y")
Continuous.Display = ParseLine ("/C")
Prompt.Files = ParseLine ("/P")
Recurse.Directories = ParseLine ("/R")
Create.Zero = ParseLine ("/X")
Display.Errors = ParseLine ("/Z")
Control.Break = ParseLine ("/~")
Var = ParseLine("/_")

' read date override switch
File.Date$ = DATE$
Imbedded = INSTR(UCASE$(Command.Line), "/D")
IF Imbedded THEN
   Last.Switch = Imbedded - 1
   File.Date$ = MID$(Command.Line, Imbedded + 2, 10)
   Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + 12)
   IF LEN(File.Date$) <> 10 THEN
      GOTO Boot.Error
   END IF
END IF

' store date values
MonthTemp = VAL(MID$(File.Date$, 1, 2))
DayTemp = VAL(MID$(File.Date$, 4, 2))
YearTemp = VAL(MID$(File.Date$, 7, 4))

' read time override switch
File.Time$ = TIME$
HourTemp = VAL(MID$(File.Time$, 1, 2))
MinuteTemp = VAL(MID$(File.Time$, 4, 2))
SecondTemp = VAL(MID$(File.Time$, 7, 2))
Imbedded = INSTR(UCASE$(Command.Line), "/T")
IF Imbedded THEN
   Last.Switch = Imbedded - 1
   File.Time$ = MID$(Command.Line, Imbedded + 2, 8)
   Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + 10)
   IF LEN(File.Time$) <> 8 THEN
      GOTO Boot.Error
   END IF
END IF

' store time values
HourTemp = VAL(MID$(File.Time$, 1, 2))
MinuteTemp = VAL(MID$(File.Time$, 4, 2))
SecondTemp = VAL(MID$(File.Time$, 7, 2))
IF INT(SecondTemp/2) <> SecondTemp/2 THEN
   Millisecond = 100
END IF

' read date\time override switches
Touch.Create = ParseLine ("/1")
Touch.Access = ParseLine ("/2")
Touch.Modify = ParseLine ("/3")
IF Touch.Create = False THEN
   IF Touch.Access = False THEN
      IF Touch.Modify = False THEN
         Touch.Modify = True
      END IF
   END IF
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
Archives.Touched = 0#
Hidden.Touched = 0#
Files.Counter = False
Files.Touched = 0#
Readonly.Touched = 0#
System.Touched = 0#
Quit.Touching = 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
         Search.Drive = ASC(UCASE$(LEFT$(Command.Work, 1)))
         Command.Work = MID$(Command.Work, 3)
      ELSE
	 Search.Drive = Current.Drive
      END IF
      Drive.Number = Search.Drive - 64

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

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

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

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

         ' get filename spec
         Filename.Search$ = Command.Work
         IF Filename.Search$ = NUL THEN
            Filename.Search$ = "*.*"
         END IF
         Command.Work = NUL

         ' change to drive
         InregsX.AX = &HE00
         InregsX.DX = Drive.Number - 1
         CALL InterruptX(&H21, InregsX, OutregsX)

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

         ' check carry flag error
         IF (OutregsX.Flags AND &H1) = &H0 THEN
            ' call routine to search for files
            CALL Directories(Directory.Search$, Filename.Search$)

            ' restore current drive
            InregsX.AX = &HE00
            InregsX.DX = Current.Drive - 65 ' 0=a, 1=b, ..
            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.Touching THEN
	 EXIT DO
      END IF
   LOOP

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

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

End.Touch:

' 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 = Current.Drive - 65 ' 0=a, 1=b, ..
CALL InterruptX(&H21, InregsX, OutregsX)

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

' display counters
IF Continuous.Display = False THEN
   COLOR Yellow, Black
   PRINT "Files touched"; Files.Touched
   IF Touch.Archive THEN
      PRINT "Archive files touched"; Archives.Touched
   END IF
   IF Touch.Hidden THEN
      PRINT "Hidden files touched"; Hidden.Touched
   END IF
   IF Touch.Readonly THEN
      PRINT "Read-only files touched"; Readonly.Touched
   END IF
   IF Touch.System THEN
      PRINT "System files touched"; System.Touched
   END IF
   Prompt$ = "Press <enter> to exit to DOS:"
   CALL MorePrompt(Prompt$, CHR$(13), Outpt$)
END IF

' restore key trapping
CALL RestInt

COLOR Plain, Black
END

' display program usage
Boot.Usage:
 ' restore key trapping
 CALL RestInt
 Var$=Inkey$
 COLOR White, Black
 PRINT "Touch v1.6a: File update utility;"
 COLOR Yellow, Black
 PRINT "Usage:"
 PRINT "   Touch [d:\path\filename.ext][//ahosy][/cdprtxz]"
 PRINT "Where:"
 PRINT "   /c  continuous display"
 PRINT "   /d  specify default date in form mm/dd/yyyy"
 PRINT "   /p  don't prompt before touching file"
 PRINT "   /r  recurse subdirectories"
 PRINT "   /t  specify default time in form hh:mm:ss"
 PRINT "   /x  create zero-length file"
 PRINT "   /z  suppress errors"
 PRINT "   touch dos 7.0 time switches:"
 PRINT "     /1  creation time, /2  access time, /3  modify time"
 PRINT "   touch files with attributes:"
 PRINT "     // prefix to not touch files with,"
 PRINT "     / prefix to touch files only with,"
 PRINT "       a  archive, h  hidden, o  read-only, s  system, y  none"
 COLOR Plain, Black
 END

Boot.Error:
 CALL RestInt
 Var$=Inkey$
 COLOR White, Black
 PRINT "Command line error. Type Touch /? 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 "Touch v1.6a: File update 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 WDTAfile AS WDTAtype
 DIM Wfile.Handle AS INTEGER

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

 ' 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 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) = &H0 THEN
    ' search directory names
    CALL Filenames(Directory.Search$, Filename.Search$)
    GOSUB Restore.DTA1
    ' check to recurse directories
    IF Recurse.Directories THEN
       ' recurse directories
       DO
          ' check control break
          IF BreakIS THEN
             Quit.Touching = True
          END IF

          ' check quit touching
          IF Quit.Touching THEN
             EXIT DO
          END IF

          ' 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 directory name
             IF Windows.Detected THEN
                Directory$ = WDTAfile.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$ + "\"
                ' recursively search subdirectories
                CALL Directories(Next.Directory$, Filename.Search$)
                GOSUB Restore.DTA1
             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
    END IF
 END IF
 GOSUB Restore.DTA1
 ' 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.DTA1:
 ' 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 ASCIIZ AS STRING * 260
 DIM WDTAfile AS WDTAtype
 DIM Wfile.Handle AS INTEGER

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

 ' 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 = &H27
    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
    ' check to create zero-length file
    IF Create.Zero THEN
       ' check windows dos
       IF Windows.Detected THEN
          ' create/truncate file
          InregsX.AX = &H716C
          InregsX.BX = &H0041
          InregsX.CX = 0
          InregsX.DX = &H0012
          InregsX.DS = VARSEG(ASCIIZ)
          InregsX.SI = VARPTR(ASCIIZ)
          InregsX.DI = &H1
          CALL InterruptX(&H21, InregsX, OutregsX)
       ELSE
          ' create/truncate file
          InregsX.AX = &H6C00
          InregsX.BX = &H0041
          InregsX.CX = 0
          InregsX.DX = &H0012
          InregsX.DS = VARSEG(ASCIIZ)
          InregsX.SI = VARPTR(ASCIIZ)
          CALL InterruptX(&H21, InregsX, OutregsX)
       END IF

       ' check file create error
       IF (OutregsX.Flags AND &H1) = &H0 THEN
          Filename$ = Directory.Search$ + Filename.Search$
          COLOR Yellow, Black
          IF Continuous.Display = False THEN
             PRINT "File " + Filename$ + " created."
          ELSE
             PRINT Filename$
          END IF
       END IF
    END IF
 ELSE

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

       ' check filename
       IF Filename$ <> "." AND Filename$ <> ".." THEN
          ' touch filename
          CALL TouchFiles(Directory.Search$ + Filename$)
          GOSUB Restore.DTA2

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

          ' check quit touching
          IF Quit.Touching THEN
             EXIT DO
          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 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
 END IF
 GOSUB Restore.DTA2
 ' 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.DTA2:
 ' 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 touch a filename
SUB TouchFiles (Touch.Filename$)
 ' declare subroutine variables
 DIM ASCIIZ AS STRING * 260
 DIM Attribute AS INTEGER
 DIM Handle AS INTEGER

 ' make filename
 ASCIIZ = Touch.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

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

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

    ' store file attribute
    Attribute = OutregsX.CX

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

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

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

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

    ' check for no attributes
    IF Touch.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.Touch.Any THEN
       IF (Attribute AND &H1) = &H0 THEN
          IF (Attribute AND &H2) = &H0 THEN
             IF (Attribute AND &H4) = &H0 THEN
                IF (Attribute AND &H20) = &H0 THEN
                   EXIT SUB
                END IF
             END IF
          END IF
       END IF
    END IF

    ' check for prompting
    IF Prompt.Files = False THEN
       Prompt$ = "Touch " + Touch.Filename$
       Prompt2$ = ""
       IF (Attribute AND &H20) = &H20 THEN
          Prompt2$ = Prompt2$ + "/a"
       END IF
       IF (Attribute AND &H1) = &H1 THEN
          Prompt2$ = Prompt2$ + "/o"
       END IF
       IF (Attribute AND &H2) = &H2 THEN
          Prompt2$ = Prompt2$ + "/h"
       END IF
       IF (Attribute AND &H4) = &H4 THEN
          Prompt2$ = Prompt2$ + "/s"
       END IF
       IF LEN(Prompt2$) THEN
          Prompt$ = Prompt$ + "{" + MID$(Prompt2$, 2) + "}"
       END IF
       Prompt$ = Prompt$ + "(y/n/c/q)?"
       CALL MorePrompt(Prompt$, "yncq", Outpt$)
       IF BreakIS THEN
          Outpt$ = "q"
       END IF
       SELECT CASE Outpt$
       CASE "c"
          Prompt.Files = True
       CASE "n"
          EXIT SUB
       CASE "q"
          Quit.Touching = True
          EXIT SUB
       END SELECT
    END IF

    ' display filename
    COLOR Yellow, Black
    PRINT Touch.Filename$

    ' reset touch flag
    Touched = False

    ' check windows dos
    IF Windows.Detected THEN

       ' touch file
       IF Touch.Create THEN
          IF Windows.DOS THEN
             InregsX.AX = &H7143
             InregsX.DS = VARSEG(ASCIIZ)
             InregsX.DX = VARPTR(ASCIIZ)
             InregsX.BX = &H07
             InregsX.DI = VAL("&H" + HEX$((YearTemp - 1980) * 512))
             InregsX.DI = InregsX.DI + MonthTemp * 32 + DayTemp
             InregsX.CX = VAL("&H" + HEX$(HourTemp * 2048))
             InregsX.CX = InregsX.CX + MinuteTemp * 32 + INT(SecondTemp / 2)
             InregsX.SI = Millisecond
             CALL InterruptX(&H21, InregsX, OutregsX)

             ' check extended write error.
             IF (OutregsX.Flags AND &H1) = &H1 THEN
                GOSUB Touch1
             ELSE
                Touched = True
             END IF
          END IF
       END IF
       IF Touch.Access THEN
          IF Windows.DOS THEN
             InregsX.AX = &H7143
             InregsX.BX = &H05
             InregsX.CX = &H0
             InregsX.DS = VARSEG(ASCIIZ)
             InregsX.DX = VARPTR(ASCIIZ)
             InregsX.DI = VAL("&H" + HEX$((YearTemp - 1980) * 512))
             InregsX.DI = InregsX.DI + MonthTemp * 32 + DayTemp
             CALL InterruptX(&H21, InregsX, OutregsX)

             ' check extended write error.
             IF (OutregsX.Flags AND &H1) = &H1 THEN
                GOSUB Touch2
             ELSE
                Touched = True
             END IF
          END IF
       END IF
       If Touch.Modify THEN
          InregsX.AX = &H7143
          InregsX.BX = &H03
          InregsX.DS = VARSEG(ASCIIZ)
          InregsX.DX = VARPTR(ASCIIZ)
          InregsX.DI = VAL("&H" + HEX$((YearTemp - 1980) * 512))
          InregsX.DI = InregsX.DI + MonthTemp * 32 + DayTemp
          InregsX.CX = VAL("&H" + HEX$(HourTemp * 2048))
          InregsX.CX = InregsX.CX + MinuteTemp * 32 + INT(SecondTemp / 2)
          CALL InterruptX(&H21, InregsX, OutregsX)

          ' check extended write error.
          IF (OutregsX.Flags AND &H1) = &H1 THEN
             GOSUB Touch3
          ELSE
             Touched = True
          END IF
       END IF
    ELSE
       IF Touch.Create THEN
          GOSUB Touch1
       END IF
       IF Touch.Access THEN
          GOSUB Touch2
       END IF
       IF Touch.Modify THEN
          GOSUB Touch3
       END IF
    END IF

    ' check touched flag
    IF Touched THEN
       Files.Touched = Files.Touched + 1#
       IF Touch.Archive THEN
          Archives.Touched = Archives.Touched + 1#
       END IF
       IF Touch.Hidden THEN
          Hidden.Touched = Hidden.Touched + 1#
       END IF
       IF Touch.Readonly THEN
          Readonly.Touched = Readonly.Touched + 1#
       END IF
       IF Touch.System THEN
          System.Touched = System.Touched + 1#
       END IF
    END IF
 END IF
 EXIT SUB

' touch using dos function.
Touch1:
 ' open file
 InregsX.AX = &H3D40
 InregsX.CX = &H27
 InregsX.DS = VARSEG(ASCIIZ)
 InregsX.DX = VARPTR(ASCIIZ)
 CALL InterruptX(&H21, InregsX, OutregsX)

 ' display any errors
 IF (OutregsX.Flags AND &H1) = &H1 THEN
    CALL DisplayError ("Error " + HEX$(OutregsX.AX) + "H opening filename creation date\time.")
 END IF

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

    ' store file handle
    Handle = OutregsX.AX

    ' touch using dos function
    InregsX.AX = &H5707
    InregsX.BX = Handle
    InregsX.CX = VAL("&H" + HEX$(HourTemp * 2048))
    InregsX.CX = InregsX.CX + MinuteTemp * 32 + INT(SecondTemp / 2)
    InregsX.DX = VAL("&H" + HEX$((YearTemp - 1980) * 512))
    InregsX.DX = InregsX.DX + MonthTemp * 32 + DayTemp
    InregsX.SI = Millisecond
    CALL InterruptX(&H21, InregsX, OutregsX)

    ' display any errors
    IF (OutregsX.Flags AND &H1) = &H0 THEN
       Touched = True
    ELSE
       CALL DisplayError ("Error " + HEX$(OutregsX.AX) + "H touching filename creation date\time.")
    END IF

    ' close file
    InregsX.AX = &H3E00
    InregsX.BX = Handle
    CALL InterruptX(&H21, InregsX, OutregsX)
 END IF
 RETURN

' touch using dos function.
Touch2:
 ' open file
 InregsX.AX = &H3D40
 InregsX.CX = &H27
 InregsX.DS = VARSEG(ASCIIZ)
 InregsX.DX = VARPTR(ASCIIZ)
 CALL InterruptX(&H21, InregsX, OutregsX)

 ' display any errors
 IF (OutregsX.Flags AND &H1) = &H1 THEN
    CALL DisplayError ("Error " + HEX$(OutregsX.AX) + "H opening filename last access date\time.")
 END IF

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

    ' store file handle
    Handle = OutregsX.AX

    InregsX.AX = &H5705
    InregsX.BX = Handle
    InregsX.CX = &H0
    InregsX.DX = VAL("&H" + HEX$((YearTemp - 1980) * 512))
    InregsX.DX = InregsX.DX + MonthTemp * 32 + DayTemp
    CALL InterruptX(&H21, InregsX, OutregsX)

    ' display any errors
    IF (OutregsX.Flags AND &H1) = &H0 THEN
       Touched = True
    ELSE
       CALL DisplayError ("Error " + HEX$(OutregsX.AX) + "H touching filename last access date\time.")
    END IF

    ' close file
    InregsX.AX = &H3E00
    InregsX.BX = Handle
    CALL InterruptX(&H21, InregsX, OutregsX)
 END IF
 RETURN

' touch using dos function.
Touch3:
 ' open file
 InregsX.AX = &H3D40
 InregsX.CX = &H27
 InregsX.DS = VARSEG(ASCIIZ)
 InregsX.DX = VARPTR(ASCIIZ)
 CALL InterruptX(&H21, InregsX, OutregsX)

 ' display any errors
 IF (OutregsX.Flags AND &H1) = &H1 THEN
    CALL DisplayError ("Error " + HEX$(OutregsX.AX) + "H opening filename modify date\time.")
 END IF

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

    ' store file handle
    Handle = OutregsX.AX

    ' set date\time
    InregsX.AX = &H5701
    InregsX.BX = Handle
    InregsX.CX = VAL("&H" + HEX$(HourTemp * 2048))
    InregsX.CX = InregsX.CX + MinuteTemp * 32 + INT(SecondTemp / 2)
    InregsX.DX = VAL("&H" + HEX$((YearTemp - 1980) * 512))
    InregsX.DX = InregsX.DX + MonthTemp * 32 + DayTemp
    CALL InterruptX(&H21, InregsX, OutregsX)

    ' display any errors
    IF (OutregsX.Flags AND &H1) = &H0 THEN
       Touched = True
    ELSE
       CALL DisplayError ("Error " + HEX$(OutregsX.AX) + "H touching filename modify date\time.")
    END IF

    ' close file
    InregsX.AX = &H3E00
    InregsX.BX = Handle
    CALL InterruptX(&H21, InregsX, OutregsX)
 END IF
 RETURN
END SUB

' critical error trap
Error.Routine:
 Data.Error = ERR
 IF Display.Errors THEN
    Error.Level = True
    OutregsX.Flags = &H1
    RESUME NEXT
 END IF
 SELECT CASE Data.Error
 CASE 53
    Temp.Outpt$ = "File not found."
 CASE 61
    Temp.Outpt$ = "Disk full."
 CASE 70
    Temp.Outpt$ = "Permission denied."
 CASE 71
    Temp.Outpt$ = "Disk not ready."
 CASE ELSE
    Temp.Outpt$ = "Untrapped error" + STR$(Data.Error) + "."
 END SELECT
 COLOR Green, Black
 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.Touch
 CASE "c"
    OutregsX.Flags = &H1
    RESUME NEXT
 END SELECT
 COLOR Plain, Black
 ' restore key trapping
 CALL RestInt
 END 0

' 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 

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