;       VTREE -- A Visual Tree-structured subdirectory list
;       =====
;
;               (c) copyright Charles Petzold, 1985
;
;               Assembler file: COM format
;               Requirements:   DOS 2.0 or higher
;               Parameters:     Optional Drive Specification

CSEG            Segment
                Assume  CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG

                Org     005Ch
FCB             Label   Byte            ; Will contain drive parameter

                Org     0080h
DefaultDTA      Label   Byte            ; Used for look-ahead searches

                Org     0100h
Entry:          Jmp     Begin           ; Program entry point

;       Most Data
;       ---------

SearchAsciiz    db      ?,":\*.*",0             ; ? gets drive spec

                db      '(C) Copyright Charles Petzold, 1985'

DriveError:     db      'Invalid disk drive$'   ; Error message
DosVersError    db      'Requires DOS 2.0 +$'   ; Another error message

FirstOrNext     db      0                       ; Flag for searches
LevelsIn        dw      0                       ; Directory level (0 = root)
SearchString    db      "\*.*",0                ; Asciiz for "all files"
SearchPointer   dw      3 + Offset SearchAsciiz ; End of SearchAsciiz string
DtaPointer      dw      Offset EndProg          ; Place for current DTA
DashCount       dw      ?                       ; For horizontal line count

;       Check drive validity and DOS Version
;       ------------------------------------

Begin:          Cmp     AL,0FFh         ; Check for Valid Drive Specification
                Jnz     DriveSpecOK     ; If OK, proceed

                Lea     DX,DriveError   ; Otherwise print an error message!
ErrorExit:      Mov     AH,9            ; Print string
                Int     21h             ;   by calling DOS

                Int     20h             ; And exit

DriveSpecOK:    Mov     AH,30h          ; Use DOS to get the
                Int     21h             ;   Version Number

                Cmp     AL,2            ; See if it's 2.0 or above
                Jae     DosVersOK       ; If so, proceed

                Lea     DX,DosVersError ; Otherwise print an error message!
                Jmp     ErrorExit       ; Print message & exit

;       Get disk drive for tree
;       -----------------------

DosVersOK:      Mov     AL,[FCB]        ; Get drive parameter from FCB
                Or      AL,AL           ; See if it's zero (default)
                Jnz     NotDefault      ; If not, skip a few instructions

                Mov     AH,19h          ; Get default drive
                Int     21h             ;   by calling DOS

                Inc     AL              ; Turns drive A: = 0 to drive A: = 1

NotDefault:     Mov     DL,AL           ; Set DL to drive number (A: = 1)
                Add     AL,'@'          ; Convert to character "A", etc
                Mov     [SearchAsciiz],AL       ; Put it in our search string
                Cld                     ; All string directions forward

;       Do *.* search for sub-directories
;       ---------------------------------

MainLoop:       Mov     DX,[DTAPointer]         ; Get current DTA address
                Mov     AH,1Ah                  ; Set the DTA
                Int     21h                     ;   by calling DOS

                Mov     BX,[LevelsIn]           ; BX represents level
                Add     BX,BX                   ; Double it for addressing
                Cmp     [FirstOrNext],0         ; See if this is first search
                Jnz     FindNextFile            ; If not, skip some code

                Mov     Word Ptr [SubDirCounter + BX],0 ; Count starts at 0

                Mov     DX,Offset SearchAsciiZ  ; Directory to search for
                Mov     CX,10h                  ; Directory attribute search
                Mov     AH,4Eh                  ; Find first file
                Int     21h                     ;   by calling DOS

                Jmp     Short TestMatch         ; See if we've got match

FindNextFile:   Mov     AH,4Fh                  ; Otherwise find next file
                Int     21h                     ;   by calling DOS

TestMatch:      Jnc     TestAttr                ; If CY flag not set, continue
                Jmp     NoMoreFiles             ; Otherwise, no more files

TestAttr:       Mov     SI,[DTAPointer]         ; SI now points to DTA
                Cmp     Byte Ptr [SI + 21],10h  ; Test if a directory
                Jnz     FindNextFile            ; If not, loop up for next

FoundDirEntry:  Add     SI,30                   ; SI points to directory name
                Cmp     Byte Ptr [SI],'.'       ; See if it's . or .. entry
                Jz      FindNextFile            ; If so, loop up for next

;       Have found a valid directory entry
;       ----------------------------------

                Inc     Word Ptr [SubDirCounter + BX]   ; Got another one
                Mov     CX,[LevelsIn]           ; Number of levels deep
                Jcxz    LookAheadSearch         ; If root, skip indentation

                Cmp     Word Ptr [SubDirCounter + BX],1 ; See if first found
                Jz      NoSpaceIn               ; If so, no indentations

                Sub     BX,BX                   ; Start index at zero

IndentLoop:     Mov     AL,179                  ; Vertical line character
                Test    Word Ptr [SubDirCounter + BX],8000h
                Jz      GotContinueChar         ; Use if still more dirs left

                Mov     AL,' '                  ; Otherwise use a blank
GotContinueChar:Call    PrintChar               ; And print it

                Push    CX                      ; Save the levels count

                Mov     CX,16                   ; Need 16 blanks indentation
BlankLoop:      Mov     AL,' '                  ; This is the blank
                Call    PrintChar               ; We print it
                Loop    BlankLoop               ; And loop for the next

                Pop     CX                      ; Retrieve the levels count

                Inc     BX                      ; Kick up the levels index
                Inc     BX                      ; Twice because word access

                Loop    IndentLoop              ; Loop for all the levels

NoSpaceIn:      Cmp     Word Ptr [SubDirCounter + BX],1 ; See if first one
                Jnz     LookAheadSearch         ; If not skip a little

                Mov     CX,[DashCount]          ; Number of lines to print
DashPrint:      Mov     AL,196                  ; Horizontal line character
                Call    PrintChar               ; Print them
                Loop    DashPrint               ; And loop for whole count

;       Check for more directories to determine proper line characters
;       --------------------------------------------------------------

LookAheadSearch:Push    SI                      ; Save ptr to directory name

                Mov     SI,[DtaPointer]         ; Set source to DTA
                Mov     DI,Offset DefaultDTA    ; Set destination to 80h
                Mov     DX,DI                   ; Also DX (used later)
                Mov     CX,43                   ; 43 characters to transfer
                Rep     Movsb                   ; Move them in

                Pop     SI                      ; Get back directory name ptr

                Mov     AH,1Ah                  ; Set a new DTA
                Int     21h                     ;   by calling DOS

CheckIfAnyMore: Mov     AH,4Fh                  ; Find the next file
                Int     21h                     ;   by calling DOS
                Jc      CantFindAnother         ; CY set if can't find one

                Cmp     Byte Ptr [DefaultDTA + 21],10h  ; Test if a directory
                Jnz     CheckIfAnyMore          ; If not, gotta try again

                Mov     AL,194                  ; Horizontal w/ vertical below
                Cmp     Word Ptr [SubDirCounter + BX],1
                Jz      GotGoodChar             ; This is good if it's first

                Mov     AL,195                  ; Vertical w/ horizontal right
                Jmp     Short GotGoodChar       ; Other than first found

CantFindAnother:Mov     AL,196                  ; Horizontal line character
                Cmp     Word Ptr [SubDirCounter + BX],1
                Jz      GotGoodChar             ; This is good if first one

                Mov     AL,192                  ; Lower left corner
                Or      Word Ptr [SubDirCounter + BX],8000h     ; Flag no more

GotGoodChar:    Call    PrintChar               ; Print that character also

                Mov     AL,196                  ; Horizontal line character
                Call    PrintChar               ; Another print

                Mov     AL,' '                  ; Space before file name
                Call    PrintChar               ; Print the space

;       Now print name of directory and append to SearchPointer string
;       --------------------------------------------------------------

                Mov     CX,13           ; Number of characters in name
                Mov     DI,[SearchPointer]      ; End of current search asciiz

PrintNameLoop:  Lodsb                   ; Get the directory name character
                Or      AL,AL           ; Check if it's zero terminator
                Jz      EndOfName       ; If so, we're at the end

                Stosb                   ; Save on end of search asciiz
                Call    PrintChar       ; And print it also

                Loop    PrintNameLoop   ; Loop for maximum number of chars

EndOfName:      Mov     AL,' '          ; Stick a blank at the end
                Call    PrintChar       ; Print the blank
                Mov     [DashCount],CX  ; Save for later dashes at end

FixUpSearch:    Mov     [SearchPointer],DI      ; New end of asciiz string
                Inc     [SearchPointer]         ; Point to next character
                Mov     SI,Offset SearchString  ; Will move in \*.*,0 string
                Mov     CX,5                    ; It's only five characters
                Rep     Movsb                   ; Move it ine

                Inc     [LevelsIn]              ; We're one level deeper now
                Mov     [FirstOrNext],0         ; Prepare for search first
                Add     [DtaPointer],43         ; New DTA will be needed
                Jmp     MainLoop                ; Back up to beginning

;       When no more files are found, time to back up to previous subdirectory
;       ----------------------------------------------------------------------

NoMoreFiles:    Cmp     [LevelsIn],0            ; See if we're back in root
                Jz      Terminate               ; If so, we're all done!

                Test    Word Ptr [SubDirCounter + BX],7FFFh
                Jnz     BackUpOneDir            ; If at least one file found

                Mov     AL,13                   ; A carriage return
                Call    PrintChar               ;   is printed
                Mov     AL,10                   ; A line feed
                Call    PrintChar               ;   makes a new line

BackUpOneDir:   Mov     DI,Offset SearchAsciiz  ; Let's look at search string
                Mov     CX,70                   ; It could have 70 characters
                Mov     AL,0                    ; We'll search for a zero
                Repnz   Scasb                   ; Let's do it

                Dec     DI                      ; So points to zero

                Mov     CX,64                   ; Now we'll search backwards
                Mov     AL,'\'                  ; For last slash
                Std                             ; Backwards search
                Repnz   Scasb                   ; Do it once
                Repnz   Scasb                   ; Again for dir we're leaving

                Inc     DI                      ; So points to slash
                Mov     [SearchPointer],DI      ; New end of asciiz string
                Inc     [SearchPointer]         ; Actually one character more

                Mov     SI,Offset SearchString  ; Now append \*.*,0 to it
                Mov     CX,5                    ; Five characters
                Cld                             ; In forward direction
                Rep     Movsb                   ; Move them int

                Dec     [LevelsIn]              ; We go back one level
                Mov     [FirstOrNext],1         ; Have to search next, not 1st
                Sub     [DtaPointer],43         ; Previous DTA will be used
                Jmp     MainLoop                ; Back up to beginning

Terminate:      Int     20h                     ; Terminate program

;       PrintChar subroutine -- prints a character on the screen
;       --------------------------------------------------------

PrintChar:      Push    DX

                Mov     DL,AL           ; DL gets the character
                Mov     AH,2            ; Print character on display
                Int     21h             ;   by calling DOS

                Pop     DX

                Ret

;       Other data areas here at end so COM file short as possible
;       ----------------------------------------------------------

SubDirCounter   Label   Word                            ; Counts dir entries

EndProg         equ     64 + Offset SubDirCounter       ; DTAs go here

CSEG            EndS

                End Entry
