Metropoli BBS
VIEWER: vtree.asm MODE: TEXT (ASCII)
;	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

[ RETURN TO DIRECTORY ]