	page	,132
	title	DIR Internal Command
;/*
; *                      Microsoft Confidential
; *                      Copyright (C) Microsoft Corporation 1991
; *                      All Rights Reserved.
; */

;***	DIR.ASM - DIR internal command

comment	% =================================================================

This module replaces TCMD1A.ASM.  The old module was titled 
"PART4 COMMAND Transient routines".

From residual documentation, I surmise that TCMD.ASM originally
contained the internal commands DIR, PAUSE, ERASE, TYPE, VOL, and
VER.  The file seems to have been successively split:

  TCMD -> TCMD1,TCMD2 -> TCMD1A,TCMD1B,TCMD2A,TCMD2B

TCMD1A.ASM contained only the DIR command.

Usage:
------

DIR <filespec> /w /p /b /s /l /c /o<sortorder> /a<attriblist>

DIR /?


<filespec> may include any or none of:  drive; directory path;
           wildcarded filename.  If drive or directory path are
	   omitted, the current defaults are used.  If the
	   file name or extension is omitted, wildcards are
	   assumed.

/w	Wide listing format.  Files are displayed in compressed
	'name.ext' format.  Subdirectory files are enclosed in
	brackets, '[dirname]'.

/p	Paged, or prompted listing.  A screenful is displayed
	at a time.  The name of the directory being listed appears
	at the top of each page.

	Bugbug:  pages nead to be uniform length..?

/b	Bare listing format.  Turns off /w or /p.  Files are 
	listed in compressed 'name.ext' format, one per line,
	without additional information.  Good for making batch
	files or for piping.  When used with /s, complete
	pathnames are listed.

/s	Descend subdirectory tree.  Performs command on current
	or specified directory, then for each subdirectory below
	that directory.  Directory header and footer is displayed
	for each directory where matching files are found, unless
	used with /b.  /b suppresses headers and footers.

	Tree is explored depth first, alphabetically within the
	same level.

	Bugbug:  hidden directories aren't searched.

/l	Display file names, extensions and paths in lowercase.	;M010

/c	Display file compression ratio, if the file is on a MagicDrv
	compressed volume.

/o	Sort order.  /o alone sorts by default order (dirs-first, name,
	extension).  A sort order may be specified after /o.  Any of
	the following characters may be used: nedsgc (name, extension,
	date/time, size, group-dirs-first, compression ratio).	Placing
	a '-' before any letter causes a downward sort on that field.
	E.g., /oe-d means sort first by extension in alphabetical order,
	then within each extension sort by date and time in reverse
	chronological order.

/a	Attribute selection.  Without /a, hidden and system files
	are suppressed from the listing.  With /a alone, all files
	are listed.  An attribute list may follow /a, consisting of
	any of the following characters:  hsdar (hidden, system,
	directory, archive, read-only).  A '-' before any letter
	means 'not' that attribute.  E.g., /ar-d means files that
	are marked read-only and are not directory files.  Note
	that hidden or system files may be included in the listing.
	They are suppressed without /a but are treated like any other
	attribute with /a.

/?	Help listing.  Display DIR useage information.	;M008;Handled externally

/h has been removed.					;M008

DIRCMD	An environment variable named DIRCMD is parsed before the
	DIR command line.  Any command line options may be specified
	in DIRCMD, and become defaults.  /? will be ignored in DIRCMD.
	A filespec may be specified in DIRCMD and will be used unless
	a filespec is specified on the command line.  Any switch
	specified in DIRCMD may be overridden on the command line.
	If the original DIR default action is desired for a particular
	switch, the switch letter may be preceded by a '-' on the
	command line.  E.g.,

	  /-w	use long listing format
	  /-p	don't page the listing
	  /-b	don't use bare format
	  /-s	don't descend subdirectory tree
	  /-o	display files in disk order
	  /-a	suppress hidden and system files


Notes:
------

For sorted listings, file entries are loaded into the TPA buffer, which
is usually about 64K in size.  This allows sorts of up to 3000 files at
a time.  Each entry takes up 21 bytes in the buffer (see EntryStruc below).
The byte after the last entry is 0FFh.  The first byte of each entry is
a flag byte which is made zero when the entry is loaded, and made one
when the entry is used.


Revision History
================
M01	md	7/13/90 	Use ROM BIOS data area to obtain screen height
				in the absence of ANSI.SYS

M007	sa	8/1/90		Allow /p/b combination

M008	sa	8/1/90		Remove /h parameter.  Eliminate code used
				to internally handle /? message.

M010	sa	8/5/90		Add support for /l (lowercase) option.

M011	sa	8/5/90		Patch up bug where MS-DOS does not load the
				first FCB with the drive number when the drive
				letter in the command line is preceded by a
				switch.  Now dir manually loads the drive
				number after parsing.

M018	md	8/12/90 	Increment the screen height by 1 when obtained
				from the ROM BIOS.

M023	sa	8/31/90		Prevent DIR from failing if it encounters
				a subdirectory having len(pathname)>MAXPATH.
				Just skip over that subdirectory.

M028	dbo	9/24/90		When country=US, sort by strict character
				byte value, rather than collating table.
				This to match MS-DOS Shell's sort order.

========================================================================= %




;***	SYMBOLS & MACROS

	.xlist
	.xcref

	include version.inc
	include	comsw.asm	; get COMMAND version switches
	include dossym.inc	; get DOS basic symbol set
	include syscall.inc	; get DOS call names
	include doscntry.inc	; get extended country info symbols
	include bpb.inc
	include filemode.inc
	include find.inc
	include	comseg.asm	; define segment order
	include comequ.asm	; get equates for COMMAND routines
	include ioctl.inc	; get symbols for ioctl's
	include rombios.inc	; get ROM BIOS data definition

	.list
	.cref

				;M008;NUM_DIR_SWS reduced by 2 for /h,/? not used
				;M010;NUM_DIR_SWS increased by 2 for /l,/-l
ifdef DBLSPACE_HOOKS
NUM_DIR_SWS	equ	16	; # of dir switch synonyms in Dir_Sw_Ptrs list
else
NUM_DIR_SWS	equ	14	; # of dir switch synonyms in Dir_Sw_Ptrs list
endif
				;M010;'lcase' replaces removed 'help' in OptionRec
ifdef DBLSPACE_HOOKS
OptionRec	record	inmem:1,lcase:1,bare:1,subd:1,pagd:1,wide:1,cratio:1
else
OptionRec	record	inmem:1,lcase:1,bare:1,subd:1,pagd:1,wide:1
endif

;		on/off bit record for /l, /b, /s, /p, /w, /c options
;		(order is hard-coded; see OnOffSw)
;		Inmem is set when entries are loaded in memory.

NUM_ATTR_LTRS	equ	6	; length of attribute letter list

ifdef DBLSPACE_HOOKS
NUM_ORDER_LTRS	equ	6	; length of sort order letter list
CRATIO_ORDER	equ	6	; position of 'C' in ORDER_LTRS
else
NUM_ORDER_LTRS	equ	5	; length of sort order letter list
endif

ResultBuffer	struc		; structure of parse result buffer
ValueType	db	?
ValueTag	db	?
SynPtr		dw	?
ValuePtr	dd	?
ResultBuffer	ends

ErrorRec	record	baddir:1,dev:1
;		Error bits are:
;		  Invalid directory format
;		  File is device

EntryStruc	struc			; our private directory entry structure
used		db	?		; =0 until entry used, then =1
filename	db	8 dup (?)	; filename
fileext		db	3 dup (?)	; extension
fileattr	db	?		; file attributes
filetime	dw	?		; file time
filedate	dw	?		; file date
filesize	dd	?		; file size
ifdef DBLSPACE_HOOKS
compratio	db	?		; compression ratio
endif
EntryStruc	ends

shove	macro	val		; hose-bag 8086 doesn't push immediate
	mov	ax,val		; invisible, dangerous use of AX!
	push	ax
	endm


	
	
;***	DATA

DATARES	segment public byte

	extrn	Append_Flag:byte	; true when APPEND needs to be reset
	extrn	Append_State:word	; state to reset APPEND to

DATARES	ends


TRANDATA segment public byte

	extrn	AttrLtrs:byte		; list of attribute letters
	extrn	BadCd_Ptr:word		; "invalid directory" msg block
	extrn	Bytes_Ptr:word		; "%1 bytes" msg block
	extrn	BytMes_Ptr:word		; "%1 bytes free" msg block
	extrn	DirCont_Ptr:word	; "(continuing %1)" msg block
	extrn	DirDat_Yr:word		; year field of date msg block
	extrn	DirDat_Mo_Day:word	; month/day field of date msg block
	extrn	DirDatTim_Ptr:word	; date/time msg block
ifdef DBLSPACE_HOOKS
	extrn	DirCompRatio_Ptr:word	; compression ratio msg block
	extrn	AveCompRatio_Ptr:word	; average comp ratio msg block
endif
	extrn	DirEnvVar:byte		; DIR environment variable name
	extrn	DirHead_Ptr:word	; directory header message block
	extrn	DirMes_Ptr:word		; "%1 File(s)" msg block
	extrn	DirTim_Hr_Min:word	; time field of msg block
	extrn	Dir_Sw_Ptrs:word	; list of DIR switch synonym ptrs
	extrn	Disp_File_Size_Ptr:word	; file size message block
	extrn	DMes_Ptr:word		; <DIR> message block
	extrn	ErrParsEnv_Ptr:word	; "(Error occurred in env.." msg blk
	extrn	Extend_Buf_Ptr:word	; extended error message block
	extrn	Extend_Buf_Sub:byte	; # substitions in message block
	extrn	Msg_Disp_Class:byte	; message display class
	extrn	OrderLtrs:byte		; list of sort order letters
	extrn	Parse_Dir:byte		; DIR parse block
	extrn	String_Buf_Ptr:word	; message block ptr to string
	extrn	Tab_Ptr:word		; tab output block
	extrn	Total_Ptr:word		; "Total:" msg block

TRANDATA ends


TRANSPACE segment public byte

	extrn	AttrSelect:byte		; attribute select states -
	extrn	AttrSpecified:byte	; attribute mask -

;		Attribute conditions are recorded in two steps.  
;		AttrSpecified indicates which attributes are to be checked.
;		AttrSelect indicates which state the specified attributes
;		 must be in for a file to be included in the listing.
;		Attributes not indicated in AttrSpecified are ignored when
;		 deciding which files to include.

	extrn	Bits:word		; some option flags (see OptionRec)
	extrn	BwdBuf:byte		; 'build working dir string' buf
	extrn	BytCnt:word		; # bytes in TPA
	extrn	Bytes_Free:word		; #bytes free for BytMes_Ptr msg block
	extrn	CharBuf:byte		; character string buffer
	extrn	ComSw:word		; error bits (see ErrorRec)
	extrn	CountryPtrInfo:byte	; buffer for collating table ptr
	extrn	CountryPtrId:byte	; info ID for collating table ptr
	extrn	CountryPtr:dword	; collating table ptr
	extrn	CurDrv:byte		; current drive # (0-based)
	extrn	DestBuf:byte		; null-terminated sort codes -

;	Sort order is specified as a series of 0 to 5 sort code
;	bytes, followed by a zero byte.
;	Codes are 1=name, 2=extension, 3=date&time, 4=size, and
;	5=filetype (subdir or not).
;	Bit 7 of code is set for a downwards sort.

	extrn	DestIsDir:byte		; indicator of delim char's in path
	extrn	DestTail:word		; ptr to filename in pathname
	extrn	Dir_Num:word		; #files for DirMes_Ptr msg block
ifdef DBLSPACE_HOOKS
	extrn	Dir_CRatio_1:byte	; Compression ratio for msg block
	extrn	Dir_CRatio_2:byte	; Compression ratio for msg block
endif
	extrn	DirBuf:byte		; DTA buffer for DOS calls
	extrn	DirFlag:byte		; signal to PathCrunch routine
	extrn	Display_Ioctl:word	; display info block for IOCTL call
	extrn	EndDestBuf:byte		; end of DestBuf (sort order codes)
	extrn	File_Size_High:word	; field for file size message block
	extrn	File_Size_Low:word	; field for file size message block
	extrn	FileCnt:word		; file count in a single directory
	extrn	FileCntTotal:dword	; file count in all directories
	extrn	FileSiz:dword		; file sizes in a single directory
	extrn	FileSizTotal:dword	; file sizes in all directories
	extrn	InternatVars:byte	; buffer for international info
	extrn	LeftOnLine:byte		; entries left on current display line
	extrn	LeftOnPage:word		; lines left on page
	extrn	LinPerPag:word		; lines/page entry in Display_Ioctl
	extrn	Msg_Numb:word		; extended error code
	extrn	OldCtrlCHandler:dword	; old int 23 vector
	extrn	Parse1_Syn:word		; ptr to matched synonym
	extrn	PathCnt:word		; length of pathname (see PathPos)
	extrn	PathPos:word		; ptr to pathname buffer (see SrcBuf)
	extrn	PerLine:byte		; # entries per line
	extrn	ResSeg:word		; RESGROUP seg addr
	extrn	ScanBuf:byte		; buffer for environment value and
					;  subdirectory names
	extrn	SrcBuf:byte		; pathname buffer
	extrn	String_Ptr_2:word	; message substitution string ptr
	extrn	Tpa:word		; TPA buffer seg addr
ifdef DBLSPACE_HOOKS
	extrn	csecUsed:dword		; count of compressed sectors used-file
	extrn	csecUsedDir:dword	; count of compressed sectors used-dir
	extrn	csecUsedTotal:dword	; count of compressed sectors used-total
	extrn	ccluUsed:word		; count of DOS clusters used - file
	extrn	ccluUsedDir:word	; count of DOS clusters used - dir
	extrn	ccluUsedTotal:word	; count of DOS clusters used - total
	extrn	fUseHostSize:byte	; use host cluster size with /C
endif

TRANSPACE ends




;***	PRINCIPAL ROUTINES

TRANCODE segment public byte

	extrn	CError:near		; COMMAND error recycle point
ifdef DBLSPACE_HOOKS
	extrn	OpenCVF:near		; Open a MagicDrv compressed volume file
	extrn	CloseCVF:near		; Close a MagicDrv CVF
	extrn	CalcCompRatio:near	; Calculate Compression Ratio routine
	extrn	ComputeRatio:near	; Calculate ratio of two sector counts
endif

	public	Catalog		; our entry point

	break	<DIR (Catalog) principal routines>

	assume	cs:TRANGROUP,ds:TRANGROUP,es:nothing,ss:TRANGROUP

;	Bugbug:	Each routine should start with it's own ASSUME.




;***	Catalog - DIR command main routine
;
;	ENTRY	FCB #1 in PSP has drive# from cmd-line or default
;		Cmd-line tail text is at 81h, terminated by 0Dh
;		CS, DS, ES, SS = TRANGROUP seg addr
;		Tpa = TPA buffer seg addr
;		BytCnt = # bytes in TPA buffer
;
;	EXIT	nothing
;
;	USED	AX,BX,CX,DX,SI,DI,BP
;
;	ERROR EXITS
;
;	  Errors are handled by setting up error message pointers
;	   for Std_EPrintf and jumping to CError.  Syntax errors in
;	   the environment variable, however, are handled by printing
;	   an error message and continuing.
;
;	EFFECTS
;
;	  Directory listing is displayed (on standard output).
;	  APPEND is disabled.  HeadFix routine is expected to
;	   restore APPEND state.
;	  Working directory may be changed.  The user's default
;	   directory is saved and flagged for restoration by RestUDir
;	   during COMMAND cycle.
;	  Lots of variables may be changed in TRANSPACE segment.
;
;	NOTES
;
;	  ES = TRANGROUP seg addr except when used to address the
;	   the TPA buffer, where directory entries are loaded from disk.

Catalog	proc

	call	SetDefaults
	call	ParseEnvironment
	call	ParseCmdLine
	jnc	@F		; no parse error
	jmp	catErr		; error msg is set up
@@:	call	SetOptions
	call	SetCollatingTable

;	Drive # to operate on has already been placed in FCB by
;	COMMAND preprocessing.  OkVolArg & PathCrunch depend on that.

	test	Bits,mask bare
	jnz	@F		; don't display volume info for /b
	invoke	OkVolArg	; find & display volume info
	sub	LeftOnPage,2	; record display lines used by volume info
	jmp	short catCrunch

;	OkVolArg side effects:
;	APPEND is disabled;
;	DTA established at DirBuf;
;	Filename fields in FCB are wildcarded.

@@:
;	OkVolArg wasn't executed, so we have to do these ourselves.

	invoke	DisAppend	; disable APPEND

	mov	dx,offset TRANGROUP:DirBuf
	mov	ah,Set_DMA
	int	21h		; set DTA

	mov	di,FCB		; ES:DI = ptr to FCB
	inc	di		; ES:DI = ptr to filename field of FCB
	mov	al,'?'		; AL = wildcard character
	mov	cx,11
	rep	stosb		; wildcard filename field

catCrunch:
	call	CrunchPath	; crunch pathname to get directory and filename
	jc	catRecErr	; handle recorded or extended error

;	User's directory has been saved, we've changed to specified directory.
;	ComSw = error bits for later use
;	FCB contains parsed filename

	cmp	ComSw,0
	jne	catRecErr	; handle recorded error

	call	InstallCtrlC	; install control-C handler
ifdef DBLSPACE_HOOKS
	test	Bits, mask cratio	; compression ratio wanted?
	jz	@f

	call	OpenCVF 		; yes, try to open CVF file
	jnc	@f			; CY clear if open worked

	and	Bits, not (mask cratio) ; failed, clear cratio option bit
@@:
endif
	call	ZeroTotals	; zero grand totals
	call	ListDir		; list main directory
	jc	catExtErr

	test	Bits,mask subd
	jz	@F		; subdirectories option not set
	call	ListSubds	; list subdirectories
	jc	catExtErr
@@:
;	Check if any files were found.

	test	Bits,mask bare
	jnz	catRet		; don't bother for bare format

	mov	ax,word ptr FileCntTotal
	or	ax,ax
	jz	catNoFiles	; no files found

	call	DisplayTotals	; display trailing grand totals
	jmp	short catRet	; all done

catRecErr:

;	ComSw may have error bit set.  If not, do extended error.

	test	ComSw,mask dev
	jnz	catNoFiles	; filename is device, respond 'file not found'

	test	ComSw,mask baddir
	jz	catExtErr	; no ComSw error bits, must be extended error
	mov	dx,offset TRANGROUP:BadCd_Ptr	; invalid directory
	jmp	short catErr

catNoFiles:

;	Display header and force 'file not found' message.

	call	DisplayHeader
	mov	ax,ERROR_FILE_NOT_FOUND
	mov	Msg_Disp_Class,EXT_MSG_CLASS
	mov	dx,offset TRANGROUP:Extend_Buf_ptr
	mov	Extend_Buf_ptr,ax
	jmp	short catErr

catExtErr:

;	DOS has returned an error status.  Get the extended error#, and
;	set up an error message, changing 'No more files' error 
;	to 'File not found' error.

	invoke	Set_Ext_Error_Msg
	cmp	Extend_Buf_Ptr,ERROR_NO_MORE_FILES
	jne	@F
	mov	Extend_Buf_Ptr,ERROR_FILE_NOT_FOUND
@@:

;	Error exit.  Error message information has been set up
;	for Std_EPrintf.

catErr:
ifdef DBLSPACE_HOOKS
	test	Bits, mask cratio	; close Compressed Volume File if cratio
	jz	@f
	call	CloseCVF
@@:
endif
	jmp	CError		; go to COMMAND error recycle point

catRet:
ifdef DBLSPACE_HOOKS
	test	Bits, mask cratio	; close Compressed Volume File if cratio
	jz	@f
	call	CloseCVF
@@:
endif
	ret

Catalog	endp




;***	SetDefaults - set default pathname, options
;
;	ENTRY	DS = TRANGROUP seg addr
;
;	EXIT	nothing
;
;	USED	AX,DI
;
;	EFFECTS
;	  SrcBuf = '*',EOL - default pathname
;	  PathPos = ptr to pathname
;	  PathCnt = length of pathname



SetDefaults	proc

	mov	di,offset TRANGROUP:SrcBuf	; DI = ptr to pathname buffer
	mov	PathPos,di			; PathPos = ptr to pathname
	mov	al,STAR
	stosb
	mov	al,END_OF_LINE_IN
	stosb				; SrcBuf = '*',0Dh
	mov	PathCnt,1		; PathCnt = pathname length

	xor	ax,ax			; AX = 0
	mov	ComSw,ax		; = no error
	mov	Bits,ax			; = options off
	mov	DestBuf,al		; = no sort
	mov	AttrSpecified,ATTR_HIDDEN+ATTR_SYSTEM
	mov	AttrSelect,al		; exclude hidden, system files
ifdef DBLSPACE_HOOKS
	mov	fUseHostSize, al	; use CVF cluster size, not Host
endif

	ret

SetDefaults	endp




;***	ParseEnvironment - find and parse our environment variable
;
;	Find our environment variable and parse it.  If a parse
;	error occurs, issue an error message.  The parse results
;	up to the error will still have effect.  Always leave
;	the option variables in a useable state.
;
;	ENTRY	DS = TRANGROUP seg addr
;
;	EXIT	nothing
;
;	USED	AX,BX,CX,DX,SI,DI
;
;	EFFECTS
;
;	  Bits may contain new option settings.
;	  DestBuf may contain new series of sort codes.
;	  AttrSpecified, AttrSelect may contain new attribute conditions.
;	  SrcBuf may contain a new default pathname/filespec.
;	  PathPos, PathCnt updated for new pathname.
;
;	  If a parse error occurred, an error message will be issued.

ParseEnvironment	proc

	call	GetEnvValue		; get environment variable value
	jc	peRet			; name not found in environment

;	SI = ptr to value of environment variable, in TRANGROUP seg

	call	ParseLine		; parse environment value
	cmp	ax,END_OF_LINE
	je	peRet			; successful completion

;	Some kind of parse error occurred.
;	We're set up for a Std_EPrintf call.

	invoke	Std_EPrintf			; display the parse error
	mov	Msg_Disp_Class,UTIL_MSG_CLASS	; restore default msg class

	mov	dx,offset TRANGROUP:ErrParsEnv_Ptr
	invoke	Printf_Crlf		; "(Error occurred in environment.."

					;M008;Internal handling of /? removed
;peOk:	and	Bits,not mask help	; disallow /h in environment variable

peRet:	ret

ParseEnvironment	endp




;***	ParseCmdLine - parse and record command line parameters
;
;	ENTRY	PSP offset 81h is beginning of cmd line buffer
;		DS, ES, CS = TRANGROUP seg addr
;
;	EXIT	CY = set if parse error occurred
;
;		If parse error occurred, we're set up for Std_EPrintf call:
;		AX = system parser error code
;		DX = ptr to message block
;
;	USED	AX,BX,CX,DX,SI,DI
;
;	EFFECTS
;
;	  Bits may contain new option settings.
;	  DestBuf may contain new series of sort codes.
;	  AttrSpecified, AttrSelect may contain new attribute conditions.
;	  SrcBuf may contain a new default pathname/filespec.
;	  PathPos, PathCnt updated for new pathname.
;
;	  If parse error occurred, we're set up for Std_EPrintf call:
;	  Msg_Disp_Class = parse error class
;	  Byte after last parameter in text is zeroed to make ASCIIZ string
;	  Message block (see DX) is set up for parse error message

ParseCmdLine	proc

	mov	si,81h			; SI = ptr to cmd-line tail text
	call	ParseLine		; parse cmd line tail
	cmp	AX,END_OF_LINE
	je	pcOk			; parse completed successfully

;	A parse error occurred.  We're all set up for message output.

	stc		   		; return failure
	jmp	short pcRet

pcOk:	clc				; return success

pcRet:	ret

ParseCmdLine	endp




;***	SetCollatingTable - set up character collating table for sorting
;
;	If country is other than USA, try to get a collating table
;	for character sorting.  For USA, use straight byte values.
;	This is so DIR behaves like the MS-DOS Shell, which sorts
;	by straight byte values in the USA for better performance.
;
;	ENTRY	ES = TRANGROUP seg addr
;
;	EXIT	nothing
;
;	USED	AX,BX,CX,DX,DI
;
;	EFFECTS
;
;	  If collating table is set -
;	    CountryPtrId = 6.
;	    CountryPtr points to collating table.
;
;	  Otherwise -
;	    CountryPtrId = 0.

SetCollatingTable	proc

;	Begin modification M028

	mov	dx,offset TRANGROUP:InternatVars
				; DS:DX = ptr to international info buffer
	mov	ax,INTERNATIONAL shl 8
				; AX = 'Get current country info'
	int	21h		; call DOS
	jc	scNoTable	; error - so don't collate

;	BX = country code

	cmp	bx,1
	je	scNoTable	; we're in USA, don't collate

;	End modification M028

;*	Country code is other than USA.  Try to get a collating table.

	mov	ax,(GETEXTCNTRY shl 8) + SETCOLLATE
				; AH = 'Get Extended Country Info'
				; AL = 'Get Pointer to Collating Table'
	mov	bx,-1		; BX = code page of interest = CON
	mov	cx,5		; CX = length of info buffer
	mov	dx,bx		; DX = country ID = default
	mov	di,offset TRANGROUP:CountryPtrInfo
				; ES:DI = ptr to info buffer
	int	21h		; call DOS
	jnc	scRet		; success

;*	Set CountryPtrId = 0 to signal no collating table.

scNoTable:			;M028
	mov	CountryPtrId,0

scRet:	ret

SetCollatingTable	endp




;***	SetOptions - check and set options
;
;	ENTRY	nothing
;
;	EXIT	nothing
;
;	USED	AX,BX,CX,DX
;
;	EFFECTS
;
;	  Bits may contain modified option settings.
;	  Display_Ioctl table, including LinPerPag variable, is filled in.
;	  LeftOnPage is initialized to # lines till end of page is handled.
;	  PerLine is set according to /w presence.

SetOptions	proc

;	If bare listing requested, cancel wide listings.

	test	Bits,mask bare
	jz	@F
	and	Bits,not mask wide		;M007;Allow /p with /b

@@:
ifdef DBLSPACE_HOOKS
;	Cancel compression ratio if wide or bare listing

	test	Bits, (mask bare) or (mask wide)
	jz	@f
	and	Bits, not mask cratio
@@:
endif
;	Set # lines per display page.

;M01  Obtain screen height from ROM BIOS data area
;
;M01	mov	LinPerPag,LINESPERPAGE			; default value

ifndef JAPAN
	push	ds
	MOV	AX,ROMBIOS_DATA 	; Get ROM Data segment
	MOV	DS,AX			;
	Assume	DS:ROMBIOS_DATA

	MOV	al,CRT_Rows		; Get max rows
	pop	ds			;
	Assume	DS:Trangroup

	or	al,al			; If zero specified
	jnz	@F			;
endif
	MOV	al,LINESPERPAGE 	; assume 24 rows

@@:
	xor	ah,ah
ifndef JAPAN
	inc	al			; height + 1 ;M018
endif
	mov	LinPerPag,ax		; set the rows now

; Now the console driver can change the rows if it knows better (M01 end)

	mov	ax,(IOCTL shl 8)+GENERIC_IOCTL_HANDLE	; IOCTL for handles
	mov	bx,STDOUT				; handle #
	mov	ch,IOC_SC				; screen
	mov	cl,GET_GENERIC				; get display info
	mov	dx,offset TRANGROUP:Display_Ioctl	; info block
	int	21h					; call DOS

	mov	ax,LinPerPag		; AX = # lines per page
	mov	LeftOnPage,ax		; initialize # lines left on page

;	Set # entries per line.

	mov	PerLine,NORMPERLIN	; # entries per line without /w
	test	Bits,mask wide
	jz	@F
	mov	PerLine,WIDEPERLIN	; # entries per line with /w
@@:
				;M011;start;The following code checks if a drive
				;letter has been parsed into SrcBuf, and if
				;so, the correct drive number is loaded into
				;the first FCB, at offset 5C.

	cmp	TRANGROUP:[SrcBuf+1],COLON_CHAR	; is this a drive letter?
	jne	soRet
	mov	al,TRANGROUP:[SrcBuf]		; load drive letter into al
	and	al,not 20h			; capitalize ASCII drive letter (LowerCase-32)-->UpperCase
	sub	al,'@'				; convert to 1-based number (1=A)
	mov	ds:FCB,al 			; store in first FCB
						;M011;end
soRet:	ret

SetOptions	endp




;***	CrunchPath - analyze supplied or default pathname
;
;	ENTRY	PathPos = ptr to pathname buffer
;		PathCnt = length of pathname, not incl trailing delimiter
;		Pathname in buffer must end in delimiter (like CR) and
;		 must have space for another char after the delimiter.
;
;	EXIT	CY = clear if no error
;		We are changed to directory found in pathname
;		Previous directory ready to be restored via RestUDir
;		FCB filename fields contain filename (possibly w/ wildcards)
;
;		If error occurred,
;		CY = set
;		ComSw = error bits (see ErrorRec)
;		If ComSw not set,
;		Ready for DOS Get Extended Error call


CrunchPath	proc

	call	FileIsDevice
	jne	@F		; not a device, skip ahead
	or	ComSw,mask dev	; signal file is device
	jmp	short cpErr	; return error
@@:
	push	PathPos		; save ptr to pathname
	mov	DirFlag,-1	; tell PathCrunch not to parse file into FCB
	invoke	PathCrunch	; change to directory in pathname
	mov	DirFlag,0	; reset our little flag
	pop	si		; SI = ptr to pathname
	jc	cpNoDir		; didn't find directory path
	jz	cpRet		; found directory path w/ no filename
				;  - leave wildcard default in FCB and return

;*	We found a directory, and there was a filename attached.
;	DestTail = ptr to ASCIIZ filename

	mov	si,DestTail	; SI = ptr to filename
	jmp	short cpFile	; go parse the file into FCB

;*	PathCrunch failed to find a directory in the pathname.
;
;	Msg_Numb = error code
;	DestIsDir = nonzero if path delimiter char's occur in pathname
;	SI = ptr to pathname (now an ASCIIZ string)

cpNoDir:
	mov	ax,Msg_Numb	  ; AX = error code from PathCrunch
	or	ax,ax
	jnz	cpErr		  ; error occurred - return it
	cmp	DestIsDir,0
	je	cpMaybe		  ; no path delimiters seen, maybe it's a file
	or	ComSw,mask baddir ; signal invalid directory name
	jmp	short cpErr	  ; return error

cpMaybe:

;	SI = ptr to pathname

	cmp	byte ptr [si+1],COLON_CHAR
	jnz	@F		  ; no drive specifier, skip ahead
	lodsw			  ; SI = ptr past drive specifier "d:"
@@:	cmp	[si],".."
	jne	cpFile		  ; if not "..", treat as a file
	cmp	byte ptr [si+2],0
	jne	cpFile		  ; or if there's more after "..", treat as file
	or	ComSw,mask baddir ; signal invalid directory
	jmp	short cpErr	  ; return error

;	The preceding code was taken from the old DIR routine.
;	It's garbage, I'm afraid.  It's meant to check for ".."
;	occurring when we're at the root directory.  Too bad it
;	doesn't handle problems with "..\..", etc.


;	We're ready to parse a filename into the FCB.
;	SI = ptr to ASCIIZ filename

cpFile:	mov	di,FCB		; DI = ptr to FCB
	mov	ax,(PARSE_FILE_DESCRIPTOR shl 8) or 0Eh
				; wildcards already in FCB used as defaults
	int	21h
	clc			; return success
	jmp	short cpRet

cpErr:	stc			; return error

cpRet:	ret

CrunchPath	endp




;***	InstallCtrlC - install our private control-C handler
;
;	Put our control-c handler in front of command.com's default
;	handler, to make sure the user's default directory gets restored.
;	This shouldn't be necessary, but, for now, there are situations
;	where the TDATA segment is left in a modified state when a
;	control-c occurs.  This means that the transient will be
;	reloaded, and the user's directory cannot be restored.
;
;	Bugbug:  fix the wider problem?  Involves message services.  Ugly.
;
;	ENTRY	nothing
;
;	EXIT	nothing
;
;	USED	AX,BX,DX
;
;	EFFECTS
;
;	  CtrlCHandler address placed in int 23 vector.
;
;	NOTE
;
;	  Command.com's basic control-c handler will be restored
;	  to the int 23 vector by the HeadFix routine, after DIR finishes.

InstallCtrlC	proc

	push	es				 ; preserve ES
	mov	ax,(GET_INTERRUPT_VECTOR shl 8) + 23h
	int	21h
	mov	word ptr OldCtrlCHandler,bx	 ; save old int 23 vector
	mov	word ptr OldCtrlCHandler+2,es	 
	pop	es				 ; restore ES

	mov	dx,offset TRANGROUP:CtrlCHandler ; DS:DX = ptr to CtrlCHandler
	mov	ax,(SET_INTERRUPT_VECTOR shl 8) + 23h
	int	21h
	ret

InstallCtrlC	endp



;***	ListSubds - search and list files in subdirectories
;
;	ENTRY	Current directory (on selected drive) is top of subdir tree
;		FCB is still set up for file searches
;		Bits, AttrSpecified, AttrSelect, DestBuf all still set up
;
;	EXIT	CY = clear if no error
;		FileCnt = # files found & displayed
;		FileSiz = total size of files found
;
;		If error,
;		CY = set
;		Ready for DOS Get Extended Error call
;
;	USED	AX,BX,CX,DX,SI,DI,BP
;
;	EFFECTS
;
;	  FileCntTotal, FileSizTotal are updated.
;	  Subdirectories may be listed on standard output device.
;
;	NOTES
;
;	  ListSubds seeds the recursive entry point lsNode with a ptr
;	   to a buffer where we'll stack up subdirectory filenames.
;	   Each name is stored ASCIIZ.

ListSubds	proc

	invoke	SetRest1		; make sure user's dir gets restored

	mov	bx,offset TRANGROUP:ScanBuf   ; BX = ptr to child name buffer

lsNode:
	mov	byte ptr ds:[bx],0	; start with null child name
lsLoop:
	call	FindNextChild		; search for next subdirectory
	jc	lsErr			; search failed - examine error

	mov	dx,bx			; DX = ptr to child's name
	call	ChangeDir		; enter child directory

					; M023;start
	jnc	@F			; check for error
	cmp	ax,ERROR_PATH_NOT_FOUND	; error due to len(pathname)>MAXPATH?
	je	lsLoop			; yes, skip over this subdirectory
	jmp	SHORT lsRet		; no, other error: DIR must fail
					; M023;end

@@:	push	bx
	call	ListDir			; list the directory
	pop	bx

;	Note we're ignoring errors returned here.

	mov	di,bx			; DI = ptr to child's name
	mov	cx,13			; CX = max name length w/ null
	xor	al,al			; AL = zero byte to look for
	repne	scasb			; DI = ptr to next name pos'n in buf
	push	bx			; save ptr to child's name
	mov	bx,di			; BX = ptr to next name pos'n in buf
	call	lsNode			; recurse from new node
	pop	bx			; BX = ptr to child's name
	pushf				; save error condition
	
	shove	0
	shove	".."
	mov	dx,sp			; DX = ptr to "..",0 on stack
	call	ChangeDir		; return to parent directory
	pop	ax			; restore stack
	pop	ax

	popf				; restore error condition from child
	jc	lsRet			; return error

	jmp	lsLoop			; look for more children
lsErr:
	invoke	Get_Ext_Error_Number	; AX = extended error code
	cmp	ax,ERROR_FILE_NOT_FOUND
	je	lsRet			; file not found, we're ok
	cmp	ax,ERROR_NO_MORE_FILES
	je	lsRet			; no more files, we're ok
	stc				; return other errors

lsRet:	ret

ListSubds	endp




	break	<DIR support routines>

;***	SUPPORT ROUTINES




;***	CheckChild - check potential subdirectory name for FindNextChild
;
;	ENTRY	DirBuf contains DOS Find-buffer with potential child
;		BX = ptr to last child's name
;		BP = ptr to temp child's name
;
;	EXIT	nothing
;
;	USED	AX,CX,SI,DI
;
;	EFFECTS
;
;	  Filename pointed to by BP may be changed.
;
;	NOTES
;
;	  Potential filename replaces temp filename if:
;	   it's a subdirectory file;
;	   it doesn't start with a '.';
;	   it's alphanumerically greater than last child's name;
;	   and it's alphanumerically less than temp name.

CheckChild	proc

	test	DirBuf.find_buf_attr,ATTR_DIRECTORY
	jz	ccRet		; not a subdirectory file- return

	cmp	DirBuf.find_buf_pname,'.'
	je	ccRet		; starts with a dot- return

	mov	si,offset TRANGROUP:DirBuf+find_buf_pname
	mov	di,bx
	call	CmpAscz		; compare candidate to last child's name
	jna	ccRet		; it's not above it- return

	mov	si,offset TRANGROUP:DirBuf+find_buf_pname
	mov	di,bp
	call	CmpAscz		; compare candidate to temp name
	jnb	ccRet		; it's not below it- return

;	New kid is alright.  Copy to temp.

	mov	si,offset TRANGROUP:DirBuf+find_buf_pname
	mov	di,bp
	mov	cx,13
	rep	movsb

ccRet:	ret

CheckChild	endp




;***	CmpEntry - compare one directory entry to another in sort order
;
;	Compare one directory entry against another according to
;	the sort codes in DestBuf.  One or more comparisons
;	may be made of file name, extension, time/date, and
;	size.  Comparisons may be made for upward or downward
;	sort order.
;
;	ENTRY	ES:BX = ptr to entry to compare
;		ES:BP = ptr to entry to be compared against
;		DestBuf contains sort codes (see DestBuf)
;		DS = TRANGROUP seg addr
;
;	EXIT	BX = unchanged
;		BP = unchanged
;		Condition flags set for same, above, or below
;		 comparing BX entry against BP entry.
;		 'Same, above, below' translate to 'same, after, before'.
;
;	USED:	AX,CX,DX,SI,DI

CmpEntry	proc

	mov	si,offset TRANGROUP:DestBuf	; (DS:SI) = ptr to sort codes
ceLoop:
	xor	ax,ax			; AX = 0
	mov	al,[si]			; AL = sort code
	or	al,al
	jz	ceDone			; sort code is zero, we're done
	inc	si			; DS:SI = ptr to next sort code
	push	si			; save ptr to next sort code
	dec	al
	sal	al,1			; AX = index into cmp call table
					; CY set for downward sort order
	mov	si,ax			; SI = index into cmp call table
	mov	ax,cs:FieldCmps[si]	; AX = addr of compare routine
	jc	ceDn			; downwards sort - go swap entries
	call	ax 			; do upwards sort
	jmp	short @F
ceDn:
	xchg	bx,bp		; swap entry ptrs for downward sort order
	call	ax		; do sort
	xchg	bx,bp		; swap ptrs back
@@:
	pop	si		; SI = ptr to next sort code
	je	ceLoop		; compare showed no difference, keep trying

ceDone:

;	Get here either from unequal compare or sort code = 0.
;	In the latter case, condition codes indicate equality,
;	which is correct.

	ret

FieldCmps	label	word	; call table of entry comparisons
		dw	CmpName
		dw	CmpExt
		dw	CmpTime
		dw	CmpSize
		dw	CmpType
ifdef DBLSPACE_HOOKS
		dw	CmpCratio
endif

CmpEntry	endp




;***	CmpName - compare file name of two entries
;***	CmpExt - compare extension of two entries
;
;	ENTRY	ES:BX = ptr to one entry
;		ES:BP = ptr to another entry
;
;	EXIT	BX = unchanged
;		BP = unchanged
;		Condition flags set for same, above, or below
;		 comparing BX entry to BP entry.
;
;	USED:	AX,CX,DX,SI,DI

CmpName	proc

	mov	si,bx		; ES:SI = ptr to BX entry
	mov	di,bp		; ES:DI = ptr to BP entry
	add	si,filename	; ES:SI = ptr to BX name
	add	di,filename	; ES:DI = ptr to BP name
	mov	cx,size filename; CX = length of name
	jmp	short CmpStr

CmpExt:	mov	si,bx		; ES:SI = ptr to BX entry
	mov	di,bp		; ES:DI = ptr to BP entry
	add	si,fileext	; ES:SI = ptr to BX extension
	add	di,fileext	; ES:DI = ptr to BP extension
	mov	cx,size fileext	; CX = length of extension field

;	Bugbug:	use symbol for subfunction code.

CmpStr:	cmp	CountryPtrId,6
	jne	cnNoCollTable	; no collating table available

;*	Compare strings using collating table.
;
;	ES:SI = ptr to 1st string
;	ES:DI = ptr to 2nd string
;	CX = length

	push	bp			; preserve BP
	push	bx			; preserve BX
	push	ds			; preserve DS
	lds	bx,CountryPtr		; DS:BX = ptr to collating table
	assume	ds:NOTHING
	mov	bp,ds:[bx]		; BP = size of collating table
	inc	bx
	inc	bx			; DS:BX = ptr to collating values
					; DS:[BX]-2 = size of table
	xor	ax,ax			; AX = 0 for starters

;	Bugbug:	Investigate removing collating table length checks.

cnNextChar:
	mov	al,es:[di]		; AL = AX = char from 2nd string
	inc	di			; ES:DI = ptr to next char 2nd string
	cmp	ax,bp			; compare to collating table length
	jae	@F			; char not in table
	xlat				
@@:					; AL = AX = collating value
	mov	dx,ax			; DX = collating value from 2nd string
	lods	byte ptr es:[si]	; AL = AX = char from 1st string
					; ES:SI = ptr to next char 1st string
	cmp	ax,bp			; compare to collating table length
	jae	@F			; char not in table
	xlat				
@@:					; AL = AX = collating value
	cmp	ax,dx			; compare collating values

ifdef DBCS				; DBCS tail byte must not use
					; collating table
	jnz	cnNot_Same
	mov	al,es:[di-1]		; get previous 2nd string character
	invoke	testkanj
	jz	cnDo_Next		; if it was not DBCS lead byte
	mov	al,es:[di]		; get tail byte from 2nd string
	cmp	es:[si],al		; compare with 1st strings tail byte
	jnz	cnNot_Same
	inc	si			; pass tail byte
	inc	di
	dec	cx
cnDo_Next:
	loop	cnNextChar
cnNot_Same:

else					; Not DBCS

	loope	cnNextChar		; until unequal or no more left
endif

	pop	ds			; restore DS
	assume	ds:TRANGROUP
	pop	bx			; restore BX
	pop	bp			; restore BP
	ret

;*	If no collating table is available, simply compare raw ASCII values.
;	Don't we wish we could just do this all the time?  Sigh.

cnNoCollTable:
	rep	cmps byte ptr es:[si],[di]
	ret

CmpName	endp




;***	CmpTime - compare entries by date/time
;
;	ENTRY	ES:BX = ptr to one entry
;		ES:BP = ptr to another entry
;
;	EXIT	BX = unchanged
;		BP = unchanged
;		Condition flags set for same, above, or below
;		 comparing BX entry to BP entry.
;
;	USED:	CX,SI,DI
;
;	NOTE	Filetime and filedate fields in our private entry
;		structure must be adjacent and in that order.

CmpTime	proc

	mov	si,bx
	mov	di,bp
	add	si,filedate + size filedate - 1
	add	di,filedate + size filedate - 1
	mov	cx,size filetime + size filedate
	std
	repe	cmps byte ptr es:[si],[di]
	cld
	ret

CmpTime	endp




;***	CmpSize - compare entries by size
;
;	ENTRY	ES:BX = ptr to one entry
;		ES:BP = ptr to another entry
;
;	EXIT	BX = unchanged
;		BP = unchanged
;		Condition flags set for same, above, or below
;		 comparing BX entry to BP entry.
;
;	USED:	CX,SI,DI

CmpSize	proc

	mov	si,bx
	mov	di,bp
	add	si,filesize + size filesize - 1
	add	di,filesize + size filesize - 1
	mov	cx,size filesize
	std
	repe	cmps byte ptr es:[si],[di]
	cld
	ret

CmpSize	endp




;***	CmpType - compare entries by file type (subdirectory or not)
;
;	ENTRY	ES:BX = ptr to one entry
;		ES:BP = ptr to another entry
;
;	EXIT	BX = unchanged
;		BP = unchanged
;		Condition flags set for same, above, or below
;		 comparing BX entry to BP entry.
;
;	USED:	AX

CmpType	proc

	mov	al,es:[bx].fileattr
	mov	ah,es:[bp].fileattr
	and	ax,(ATTR_DIRECTORY shl 8) + ATTR_DIRECTORY
	cmp	ah,al
	ret

CmpType	endp


ifdef DBLSPACE_HOOKS
;***	CmpCratio - compare entries by compression ratio
;
;	ENTRY	ES:BX = ptr to one entry
;		ES:BP = ptr to another entry
;
;	EXIT	BX = unchanged
;		BP = unchanged
;		Condition flags set for same, above, or below
;		 comparing BX entry to BP entry.
;
;	USED:	AX

CmpCratio	proc

	mov	al, es:[bx].compratio
	cmp	al, es:[bp].compratio
	ret

CmpCratio	endp
endif

;***	DefaultAttr - set default attribute conditions
;
;	ENTRY	nothing
;
;	EXIT	CY clear
;
;	USED
;
;	EFFECTS
;
;	  AttrSpecified, AttrSelect are updated with new attribute conditions.

DefaultAttr	proc

	mov	AttrSpecified,ATTR_HIDDEN+ATTR_SYSTEM	; specify H and S
	mov	AttrSelect,0				; H and S must be off
	clc						; return success
	ret

DefaultAttr	endp




;***	DisplayTotals - display grand total stats
;
;	If we searched subdirectories, display the total # files found
;	 and total size of files found.
;	Display disk space remaining.
;
;	ENTRY	FileCntTotal, FileSizTotal contain correct values
;		Bits contains setting of /s
;		FCB contains drive #
;
;	EXIT	nothing
;
;	USES	AX,DX
;		FileSiz

DisplayTotals	proc

	test	Bits,mask subd
	jz	dtFree			; no subdirectories- do bytes free

	invoke	Crlf2			; start on new line
	call	UseLine

	mov	dx,offset TRANGROUP:Total_Ptr
	invoke	Std_Printf			; "Total:",cr,lf
	call	UseLine
ifdef DBLSPACE_HOOKS
	test	Bits, mask cratio
	jz	dtCntSize

	mov	ax, [ccluUsedTotal]	; setup compression ratio counts for
	mov	[ccluUsedDir], ax	;   display routine
	mov	si, offset TRANGROUP:csecUsedTotal
	mov	di, offset TRANGROUP:csecUsedDir
	movsw
	movsw

dtCntSize:
endif
	mov	ax,word ptr FileCntTotal	; AX = # files found mod 64K
	mov	si,offset TRANGROUP:FileSizTotal
	mov	di,offset TRANGROUP:FileSiz
	movsw
	movsw				; move total size to size variable
	call	DisplayCntSiz		; display file count & size &
dtFree: 				;   (maybe) compression ratio
	mov	ah,GET_DRIVE_FREESPACE	; AH = DOS Get Free Space function
	mov	dl,byte ptr ds:FCB	; DL = drive#
	int	21h			; call DOS
	cmp	ax,-1			; check 'invalid drive' return code
	jz	dtRet			; can't get drive space - return
	mul	cx
	mul	bx
	mov	Bytes_Free,ax
	mov	Bytes_Free+2,dx
	mov	dx,offset TRANGROUP:BytMes_Ptr
	invoke	Std_Printf		; "nnn bytes free",cr,lf
	call	UseLine

dtRet:	ret

DisplayTotals	endp




;***	FileIsDevice - see if file looks like a device
;
;	ENTRY	PathPos = ptr to pathname
;		PathCnt = length of pathname w/o terminating char
;		DirBuf is DOS DTA
;
;	EXIT	ZR = set if file looks like a device
;
;	USED	AX,BX,CX,DX,DI
;
;	EFFECTS
;
;	  DTA buffer holds results of Find First function
;
;	NOTES
;
;	  We try to flag devices in two ways.  First, we try
;	  the DOS Find First function.  It returns attribute bit 6
;	  set on a successful find if it identifies a device name.
;	  Unfortunately, it returns 'path not found' for a device
;	  name terminated with colon, such as "CON:".  So, we look
;	  for any colon in the pathname after the 2nd character,
;	  and flag the pathname as a device if we find one.

FileIsDevice	proc

	mov	dx,PathPos	 ; DX = ptr to pathname

	mov	di,dx
	add	di,PathCnt	 ; DI = ptr to byte after pathname
	xor	bl,bl		 ; BL = NUL to terminate pathname with
	xchg	bl,byte ptr [di] ; BL = saved pathname terminating char

	xor	cx,cx		 ; CX = attribute mask (normal search)
	mov	ah,FIND_FIRST	 ; AH = DOS Find First function code
	int	21h	 	 ; call DOS
	xchg	bl,byte ptr [di] ; restore pathname terminating char
	jc	piCol		 ; didn't find a dir entry, check for colon

;	Found a dir entry, see if Find First thinks it's a device.

	test	byte ptr DirBuf.Find_Buf_Attr,ATTR_DEVICE
	jz	piCol		 ; device attribute not set, look for colon
	xor	cx,cx		 ; it's a device, return ZR flag
	jmp	short piRet

;	Device attribute not returned by Find First function.  But
;	let's check for a colon anywhere in the pathname after the
;	second byte.
;
;	DI = ptr to byte after pathname

piCol:	dec	di		 ; DI = ptr to last char in pathname
	mov	al,COLON_CHAR	 ; AL = colon char to search for
	mov	cx,PathCnt	 ; CX = # chars to scan
	dec	cx
	dec	cx		 ; ignore 1st two chars of pathname
	or	cx,cx
	js	piRet		 ; if < 2 chars in pathname, just return
	or	di,di		 ; clear ZR in case CX = 0
	std			 ; scan downward
	repne	scasb
	cld			 ; restore default upward direction

;	After scanning, the ZR flag is set to indicate presence of a colon.

piRet:	ret

FileIsDevice	endp




;***	FindFirst - find first directory entry to display
;***	FindNext - find next directory entry to display
;
;	ENTRY	Bits<inmem> = set if entries are loaded in TPA
;		AttrSpecified, AttrSelect are set
;
;	EXIT	CY = clear if successful
;		BX = offset in TPA buffer of directory entry found
;
;		If unsuccessful,
;		CY = set
;		AX = DOS error code
;		DOS Get Extended Error call will get error code
;
;		NOTE:  if entries were loaded into TPA, AX contains
;		ERROR_NO_MORE_FILES when no more entries are available,
;		but DOS Get Extended Error call WON'T return the correct
;		error.  That's ok, because we'll see the value in AX
;		and recognize it as a non-error condition.
;
;	USED	AX,CX,DX,SI,DI
;
;	EFFECTS
;
;	  Entries in memory may be marked as output.
;	  If not sorted, entry is loaded at TPA.
;
;	NOTES
;
;	  If we don't find a qualifying file, we return after the final
;	   DOS Find File call.  A DOS Get Extended Error call will then
;	   indicate an appropriate condition.

FindFirst	proc

	mov	ax,offset TRANGROUP:GetFirst
	jmp	short ffFindEntry

FindNext:
	mov	ax,offset TRANGROUP:GetNext

;	AX = address of correct disk get routine to use.

ffFindEntry:
	push	es			; save TRANGROUP seg addr
	test	Bits,mask inmem
	jz	ffDisk			; entries not in memory, search disk

;	Entries are loaded in memory to sort out.  Find the first one.
;	There will always be one, or LoadEntries would've failed.

	call	FindInMem		; find first entry in TPA
	jmp	short ffRet		; return what TPA search returns

;	Get entry from disk.

ffDisk:
	call	ax			; get entry from disk
	jc	ffGetErr		; get & return error
	mov	es,Tpa			; ES = seg addr of TPA
	xor	di,di			; ES:DI = ptr to TPA
	mov	bx,di			; BX = offset of entry in TPA
	call	LoadEntry		; load entry to TPA
	clc				; return success
	jmp	short ffRet

ffGetErr:
	invoke	Get_Ext_Error_Number	; AX = DOS error code
	stc

ffRet:	pop	es			; ES = TRANGROUP seg addr again
	ret

FindFirst	endp




;***	FindInMem - find next directory entry in TPA buffer
;
;	ENTRY	TPA is loaded (see LoadEntries)
;
;	EXIT	BX = offset in TPA of entry found
;
;		If no more files,
;		CY = set
;		AX = DOS 'no more files' error code
;
;	USED	AX,BX,CX,DX,SI,DI,BP,ES
;
;	EFFECTS
;
;	  Entry found is flagged as 'used' (see EntryStruc).

FindInMem	proc

	mov	es,Tpa		; ES = TPA seg addr
	xor	bx,bx		; ES:BX = ptr to 1st entry in TPA
	cld			; make sure default string direction is up
	
	call	FindOneInMem	; locate an entry
	jc	fiNoMore	; none left, set up 'no more files' error

;	BX = ptr to entry in TPA

fiBest:
	mov	bp,bx		; BP = ptr to best entry so far
fiNext:
	call	FindNextInMem	; locate next entry
	jc	fiFound		; no more, best entry so far wins

;	BX = ptr to next entry

	call	CmpEntry	; compare it to best found so far (BP)
	jnb	fiNext		; it's not better, go look at next one
	jmp	fiBest		; it's better, go mark it as best so far

fiNoMore:

;	No more entries available in TPA.  Set up 'no more files' error.

	mov	ax,ERROR_NO_MORE_FILES	; AX = 'no more files' error code
	stc				; return error
	jmp	short fiRet

fiFound:
	mov	bx,bp			; BX = ptr to best entry found
	mov	byte ptr es:[bx],1	; mark entry 'used'
	clc				; return success
fiRet:	ret

FindInMem	endp




;***	FindNextChild - find next subdirectory in current directory
;
;	ENTRY	BX = ptr to last child found, ASCIIZ filename
;		DirBuf is established DTA
;
;	EXIT	BX = ptr (same addr) to next child found, ASCIIZ filename
;
;		If failure,
;		CY = set
;		DOS Get Extended Error call will get error
;
;	USED	AX,CX,DX,SI,DI,BP
;
;	EFFECTS
;
;	  DirBuf is used for find first/next calls.
;
;	NOTES
;
;	  We keep on checking files until DOS returns an error.  If
;	  the error is 'no more files' and the temp filename is not
;	  the initial high tag, copy the temp to the child's name spot
;	  and return success.  Otherwise, send the error back to caller.
;
;	  This routine depends on DS,ES,CS, & SS all being equal.

FindNextChild	proc

	sub	sp,12			; make temp filename buf on stack
	shove	00FFh			; temp filename = high tag
	mov	bp,sp			; BP = ptr to temp filename buf
	shove	"*"
	shove	".*"
	call	GetDriveLtr		; AX = "d:"
	push	ax
	mov	dx,sp			; DX = ptr to "d:*.*",0 on stack

;	See that the stack is restored properly at the end of this proc.

	mov	cx,ATTR_DIRECTORY	; CX = attributes for file search
	mov	ah,FIND_FIRST
	int	21h			; DOS- Find First matching file
	jc	fcRet			; return error

	call	CheckChild		; check child against last, temp

fcNext:	mov	cx,ATTR_DIRECTORY	; CX = attributes for file search
	mov	ah,FIND_NEXT
	int	21h			; DOS- Find Next matching file
	jc	fcErr			; examine error

	call	CheckChild		; check child against last, temp
	jmp	fcNext			; go find another child

fcErr:
	invoke	Get_Ext_Error_Number	; AX = extended error code
	cmp	ax,ERROR_NO_MORE_FILES	; no more files?
	jne	short fcNope		; some other error- return it

;	We ran out of files.  See if we qualified at least one.

	cmp	byte ptr [bp],0FFh
	je	fcNope			; temp filename is unused- no child

;	Move temp filename to child name position.

	mov	si,bp		; SI = ptr to temp filename
	mov	di,bx		; DI = ptr to child name pos'n
fcMove:	lodsb			; AL = next byte of filename
	stosb			; store byte
	or	al,al
	jz	fcRet		; byte was zero, return success (CY clear)
	jmp	fcMove		; go move another byte

fcNope:	stc			; return error
fcRet:	lahf
	add	sp,20		; restore stack
	sahf
	ret

FindNextChild	endp





;***	FindOneInMem - find the first available entry in TPA
;***	FindNextInMem - find the next available entry in TPA
;
;	ENTRY	ES = TPA seg addr
;		BX = ptr to entry in TPA
;
;	EXIT	BX = ptr to entry found
;		CY = set if no more entries available in TPA
;
;	USED	AL

FindOneInMem	proc

	mov	al,es:[bx]	; examine 'used' byte of starting entry
	cmp	al,1
	je	FindNextInMem	; entry has already been used
	cmp	al,0FFh
	je	foNoMore	; 0FFh, we're at the end of the list

;	BX = ptr to entry that hasn't been output yet.

	clc			; return success
	ret

FindNextInMem:
	add	bx,size EntryStruc	; BX = ptr to next entry
	jmp	FindOneInMem		; go look at it

foNoMore:
	stc				; ran out of entries, return failure
	ret

FindOneInMem	endp




;***	GetEnvValue - get value of our environment variable
;
;	ENTRY	DS, ES = TRANGROUP seg addr
;
;	EXIT	CY = set if environment variable not in environment
;
;		Otherwise:
;		SI = ptr to environment variable asciiz value in TRANGROUP
;
;	USED	AX,BX,CX,DX,DI
;		(We assume the (almost) worst, since we don't know about
;		Find_Name_In_Environment.)
;
;	EFFECTS
;
;	  ScanBuf is loaded with value text

GetEnvValue proc

	push	es				; save ES
	mov	si,offset TRANGROUP:DirEnvVar	; DS:SI = ptr to variable name
	invoke	Find_Name_In_Environment
	jc	geRet				; name not found in environment

;	ES:DI = ptr to value of environment variable
;	We're assuming DS, CS, and SS are unchanged.

	push	ds
	push	es
	pop	ds
	pop	es

	assume	ds:nothing

;	DS = seg addr of environment variable value (in environment segment)
;	ES = TRANGROUP seg addr

	mov	si,di				; DS:SI = ptr to value string
	mov	di,offset TRANGROUP:ScanBuf	; ES:DI = ptr to dest buffer
@@:	lodsb
	or	al,al
	stosb
	loopnz	@B		; move the string, including trailing null

	push	es
	pop	ds		; DS = TRANGROUP seg addr again
	assume	ds:TRANGROUP

	mov	si,offset TRANGROUP:ScanBuf	; SI = ptr to var value
geRet:	pop	es				; restore ES
	ret

GetEnvValue endp




;***	GetFirst - get first directory entry from disk
;
;	ENTRY	DOS DTA established at DirBuf
;		FCB contains drive # and filename
;		Current directory (on selected drive) is the one to search
;		AttrSpecified & AttrSelect masks set
;
;	EXIT	CY = clear if success
;		DirBuf contains extended FCB for file found
;
;		If unsuccessful,
;		CY = set
;		Ready for DOS Get Extended Error call
;
;	USED	AX,DX
;
;	EFFECTS
;
;	  FCB-7 = 0FFh to mark extended FCB
;	  FCB-1 = attribute mask to find all files
;	  These fields should remain unmodified for GetNext calls.
;
;
;***	GetNext - get next directory entry from disk
;
;	ENTRY	As for GetFirst, plus
;		FCB-7 set up as extended FCB w/ find-all attribute byte
;
;	EXIT	As for GetFirst
;
;	USED	AX,DX

GetFirst	proc

	mov	byte ptr ds:FCB-7,0FFh	; signal extended FCB
	mov	byte ptr ds:FCB-1,ATTR_ALL
					; find any file
	mov	dx,FCB-7		; DX = ptr to extended FCB
	mov	ah,DIR_SEARCH_FIRST	; AH = DOS Find First function code
	int	21h			; call DOS
	shl	al,1			; CY = set if error
	jc	gfRet			; return error
	jmp	short gfFound		; go look at attr's

GetNext:
	mov	dx,FCB-7		; DX = ptr to extended FCB
	mov	ah,DIR_SEARCH_NEXT	; AH = DOS Find Next function code
	int	21h			; call DOS
	shl	al,1			; CY = set if error
	jc	gfRet			; return error

;*	Found an entry.  Check attributes.

gfFound:
	mov	al,[DirBuf+8].dir_attr	; AL = file attributes
	mov	ah,AttrSpecified	; AH = mask of pertinent attr's
	and	al,ah			; AL = pertinent attr's of file
	and	ah,AttrSelect		; AH = attr settings to match
	cmp	al,ah
	jne	GetNext			; attr's don't match, look for another

gfRet:	ret

GetFirst	endp




;***	ListDir - search for and list files in the current directory
;
;	List header, files, and trailer for current directory on selected
;	drive.  Header & trailer are listed if at least one file is found.
;	If no qualifying files are found, no display output occurs.
;
;	ENTRY	Current directory (on selected drive) is the one to be listed
;		FCB contains selected drive # and filename spec
;		Option bits, attribute masks, and sort codes set up
;
;	EXIT	CY = clear if no error
;		FileCnt = # files found & displayed
;
;		If error,
;		CY = set
;		Ready for DOS Get Extended Error call
;
;	USED	AX,BX,CX,DX,SI,DI,BP
;		FileSiz
;
;	EFFECTS
;
;	  FileCntTotal, FileSizTotal are updated.
;	  Files found are listed.  A directory header and trailer are
;	   displayed only if files are found.

ListDir	proc

	xor	ax,ax
	mov	FileCnt,ax		; zero file count
	mov	word ptr FileSiz,ax	; zero file size accumulator
	mov	word ptr FileSiz+2,ax
ifdef DBLSPACE_HOOKS
	mov	ccluUsedDir, ax 	; zero count clusters used
	mov	word ptr csecUsedDir, ax ;zero count compressed sectors used
	mov	word ptr csecUsedDir+2, ax
endif
	cmp	DestBuf,0		; check for sort code
	je	@F			; no sort
	call	LoadEntries		; load entries for sorted listing
	jnc	@F			; no error - continue
	invoke	Get_Ext_Error_Number	; AX = DOS error code
	stc
	jmp	short ldErr		; return error
@@:
	call	FindFirst		; find first file
	jc	ldErr			; not found, return error

;	BX = offset in TPA buffer of entry found

	call	DisplayHeader		; if at least one file, display header
	call	DisplayFile		; display the file entry
ldNext:
	call	FindNext		; find another file
	jc	ldErr			; not found
	call	DisplayFile		; display entry
	jmp	ldNext			; go find another one

ldErr:
	cmp	ax,ERROR_FILE_NOT_FOUND
	je	ldDone			; file not found, we're done
	cmp	ax,ERROR_NO_MORE_FILES
	je	ldDone			; no more files, we're done
	stc
	jmp	short ldRet

ldDone:
	cmp	FileCnt,0
	je	@F			; no files found, just return
	call	DisplayTrailer		; display trailing info
@@:	clc				; return success

ldRet:	ret

ListDir	endp




;***	LoadEntries - attempt to load entries from current directory
;
;	Load all qualifying directory entries from the current directory
;	into the TPA.  If an error is returned by FindFirst/FindNext calls
;	other than 'no more files', return to caller with carry flag set.
;	If we run out of buffer space, display a message that we haven't
;	enough memory to sort this directory, but return without error.
;	Other routines know whether or not entries have been loaded by
;	the 'inmem' flag bit, which we set here.
;
;	The TPA is usually 64K - 512 bytes long.  At 20 bytes per entry,
;	this allows sorting over 3000 entries in a directory.
;
;	ENTRY	Tpa = buffer seg addr
;		BytCnt = buffer length, in bytes
;		Current directory (on selected drive) is the one to load
;		FCB contains drive # and filespec
;		Bits, AttrSpecified, AttrSelect, & DestBuf (sort codes) are set
;
;	EXIT	CY = set if error
;		If error, DOS Get Extended Error will get error info
;
;	USED	AX,CX,DX,SI,DI
;
;	EFFECTS
;
;	  Inmem bit of Bits = set if load succeeded.
;	  Tpa buffer contains directory entries.
;	  Byte after last entry = 0FFh.

LoadEntries	proc

	push	es			; save TRANGROUP seg addr
	mov	es,Tpa			; ES = TPA seg addr
	xor	di,di			; ES:DI = destination ptr
	and	Bits,not mask inmem	; signal entries not loaded

	call	GetFirst		; look for first file
	jc	leRet			; return any error
	call	LoadEntry		; load entry into TPA

leNext:	call	GetNext			; get another file
	jc	leLoaded		; assume any error is no more files
	mov	ax,BytCnt		; AX = size of TPA
	sub	ax,di			; AX = bytes left in TPA
	cmp	ax,size EntryStruc+2	; insist on entry size + 2 bytes
	jb	leOk			; not enough memory left, give up
	call	LoadEntry		; load entry into TPA
	jmp	leNext			; go get another file

leLoaded:
	mov	byte ptr es:[di],0FFh	; mark end of entry list
	or	Bits,mask inmem		; signal entries loaded in memory
leOk:	clc				; return no error

leRet:	pop	es			; ES = TRANGROUP seg addr again
	ret

LoadEntries	endp




;***	LoadEntry - load directory entry from DirBuf ext'd FCB
;
;	ENTRY	ES:DI = ptr to load point in TPA
;		DirBuf contains extended FCB of entry to load
;
;	EXIT	ES:DI = ptr to next byte available in TPA
;
;	USED	AX,CX,SI
;
;	NOTES
;
;	  I could've used symbolic offsets and sizes of fields from
;	   the dir_entry struc to do this, but this is time-critical,
;	   so I hard-wired the structure of the DOS 4.x returned FCB,
;	   as well as our private directory entry structure.
;
;	  We force a zero size for subdirectory files.  A zero size is
;	   ordinarily returned for subdirectories, but with Novell
;	   Netware 286 or 386 loaded, we can't depend on it.  Bug #1594.

LoadEntry	proc

	mov	si,offset TRANGROUP:Dirbuf+8	; DS:SI = ptr to filename
	xor	al,al				; AL = 0
	stosb					; 'used' byte = false
	mov	cx,11
	rep	movsb				; transfer filename & extension
	lodsb					; AL = attrib byte
	stosb					; store attrib byte
	add	si,dir_time-dir_attr-1		; skip to time field
	movsw					; transfer time
	movsw					; transfer date
ifdef DBLSPACE_HOOKS
	mov	cl, al				; attrib to cl
	lodsw					; allocation unit (cluster)
else
	inc	si				; skip alloc unit
	inc	si
endif

ifdef DBLSPACE_HOOKS
	and	cl,ATTR_DIRECTORY
else
	and	al,ATTR_DIRECTORY
endif
	jnz	leSetDirSize			; force zero size for subdir

	movsw
	movsw					; transfer size

ifdef DBLSPACE_HOOKS
	test	Bits, mask cratio		; compression ratio report?
	jnz	leCalcRatio			; yup

	xor	ax, ax
	stosb					; dummy compression ratio
endif
	ret

ifdef DBLSPACE_HOOKS
leCalcRatio:
	call	CalcCompRatio			; takes cluster in AX
	or	ax, ax				;   returns ratio in AX
	jz	leNoRatio			; 0 means couldn't calculate
	dec	ah				; pack 1.0 - 16.0 comp ratio
	mov	cl, 4				;   into 2 nibbles.  Store
	shl	ah, cl				;   1-16 as 0-15 in hi nibble,
	or	al, ah				;   tenths (0-9) in low nibble
	stosb
	ret
endif

leSetDirSize:
	xor	ax,ax
	stosw
	stosw					; store zero size

ifdef DBLSPACE_HOOKS
leNoRatio:
	dec	al				; al = FFh = special invalid
	stosb					; compression ratio
endif
	ret

LoadEntry	endp




;***	NoOrder - turn sorting off
;
;	ENTRY	nothing
;
;	EXIT	CY clear
;
;	USED	AX
;
;	EFFECTS
;
;	  DestBuf is updated with sort code bytes.  See DestBuf description.

NoOrder	proc

	mov	DestBuf,0	; no sort
	clc			; no error
	ret

NoOrder	endp




;***	OnOffSw - record occurence of on/off option switch
;
;	ENTRY	DI = index into word list of switches
;
;	EXIT	CY clear
;
;	USED	AX,CX
;
;	EFFECTS
;
;	  Bits modified to indicate option state.

OnOffSw	proc

	mov	cx,di		; CX = index into word list of options
	shr	cx,1
	shr	cx,1		; CX = bit position of option
	mov	ax,1		
	shl	ax,cl		; AX = bit mask of option
	test	di,2		; check if it is a negated option
	jz	@F		; it's negated
	or	Bits,ax		; turn option on
	jmp	short ooRet

@@:	not	ax		; AX = complemented bit mask of option
	and	Bits,ax		; turn option off

ooRet:	clc			; always return success
	ret

OnOffSw	endp




;***	ParseAttr - parse and record /A option
;
;	ENTRY	BX = ptr to system parser result buffer for /A occurence
;
;	EXIT	CY = set if error occurs parsing attribute conditions
;
;		For parse error, we set up for Std_EPrintf call:
;		AX = parse error code, like system parser
;		DX = ptr to message block
;
;	USED	AX,CX,DX,DI
;
;	EFFECTS
;
;	  AttrSpecified, AttrSelect are updated with new attribute conditions.
;	  If parse error occurs, attribute conditions parsed so far hold.
;
;	  For parse error, we set up for Std_EPrintf call:
;	  Msg_Disp_Class = parse error message class
;	  Message block (see DX) is set up for parse error message

ParseAttr	proc

	push	si			; save SI
	mov	AttrSpecified,0		; cancel all attribute conditions

;	Each /A invocation starts by assuming all files are to be listed.

	mov	si,word ptr [bx].ValuePtr ; SI = ptr to string after /A

paLoop:	mov	dx,1			; DX = 1 (for un-negated attribute)

	lodsb				; AL = next char in string
	or	al,al
	jz	paOk			; it's terminating null, we're done
	cmp	al,'-'
	jne	@F			; not '-', go look for letter
	dec	dx			; DX = 0 (for negated attribute)
	lodsb				; AL = next char
@@:	
	mov	di,offset TRANGROUP:AttrLtrs  ; DI = ptr to attrib letter list
	mov	cx,NUM_ATTR_LTRS	; CX = length of attrib letter list
	repne	scasb			; look for our letter in the list
	jne	paErr			; not found, return error

	not	cx
	add	cx,NUM_ATTR_LTRS	; CX = attrib bit #, 0-5

;	Note that we rely on AttrLtrs to be in the attribute bit order,
;	starting from bit 0.

;	Record this attribute bit in AttrSpecified.

	mov	al,1
	shl	al,cl			; AL = mask for our bit
	or	AttrSpecified,al	; set it in the 'specified' mask

;	Record the selected state for this attribute in AttrSelect.
;	DX = 0 or 1, the selected state for this attribute.

	not	al			; AL = mask for all other bits
	and	AttrSelect,al		; clear our bit
	shl	dl,cl			; DL = our bit state in position
	or	AttrSelect,dl		; set selected attr state
	jmp	paLoop			; go look at next char

;	The attribute letter string is invalid.

paErr:	
	call	SetupParamError		; set message up for Std_EPrintf
	stc		   		; return error
	jmp	short paRet

paOk:	clc				; return success		

paRet:	pop	si			; restore SI
	ret

ParseAttr	endp




;***	ParseLine - parse a line of text
;
;	Parse text until an EOL (CR or NUL) is found, or until a parse
;	error occurs.
;
;	ENTRY	DS:SI = ptr to text
;		CS, DS, ES = TRANGROUP seg addr
;
;	EXIT	AX = last return code from system parser
;		CX = # positional parameters (pathnames) found - 0 or 1
;
;		If parse error occurred, we're set up for Std_EPrintf call:
;		DX = ptr to message block
;
;	USED	BX,CX,DX,SI,DI
;
;	EFFECTS
;
;	  Bits may contain new option settings.
;	  DestBuf may contain new series of sort codes.
;	  AttrSpecified, AttrSelect may contain new attribute conditions.
;	  SrcBuf may contain a new default pathname/filespec.
;	  PathPos, PathCnt updated for new pathname.
;
;	  If parse error occurred, we're set up for Std_EPrintf call:
;	  Msg_Disp_Class = parse error class
;	  Byte after last parameter in text is zeroed to make ASCIIZ string
;	  Message block (see DX) is set up for parse error message

ParseLine	proc

	mov	di,offset TRANGROUP:Parse_Dir	 ; ES:DI = ptr to parse block
	xor	cx,cx				 ; CX = # positionals found
plPars:
	invoke	Parse_With_Msg		; call parser
	cmp	ax,END_OF_LINE
	je	plRet			; EOL encountered, return
	cmp	ax,RESULT_NO_ERROR
	jne	plRet			; parse error occurred, return

;	Parse call succeeded.  We have a filespec or a switch.
;	DX = ptr to result buffer

	mov	bx,dx			; BX = ptr to parse result buffer
	cmp	byte ptr [bx],RESULT_FILESPEC
	je	plFil			; we have a filespec

	call	ParseSwitch		; else we have a switch
	jc	plRet			; error parsing switch, return
	jmp	plPars			; parse more

plFil:	call	CopyPathname		; copy pathname into our buffer
	jmp	plPars			; parse more

plRet:	ret

ParseLine	endp




;***	ParseOrder - parse and record /O option
;
;	ENTRY	BX = ptr to system parser result buffer for /O occurence
;
;	EXIT	CY = set if error occurs parsing order
;
;		For parse error, we set up for Std_EPrintf call:
;		AX = parse error code, like system parser
;		DX = ptr to message block
;
;	USED	AX,CX,DX,DI
;
;	EFFECTS
;
;	  DestBuf is updated with sort code bytes.  See DestBuf description.
;
;	  For parse error, we set up for Std_EPrintf call:
;	  Msg_Disp_Class = parse error message class
;	  Message block (see DX) is set up for parse error message

ParseOrder	proc

	push	si				; save SI
	push	bx				; save ptr to result buffer

	mov	si,word ptr [bx].ValuePtr	; SI = ptr to order letters
	mov	bx,offset TRANGROUP:DestBuf	; BX = ptr to sort code buffer
	mov	al,[si]				; AL = 1st char of order string
	or	al,al
	jnz	poLtr			; not NUL, go parse letters

;	We have /O alone.  Set standard sort order.
;	Note hardwired dependency on character order in OrderLtrs.

	mov	byte ptr [bx],5		; sort 1st by group (subdirs 1st)
	inc	bx
	mov	byte ptr [bx],1		; then by name
	inc	bx
	mov	byte ptr [bx],2		; then by extension
	inc	bx
	jmp	short poOk		; return success

;	We have /O<something>.  Parse sort order letters.

poLtr:	xor	dl,dl			; DL = 0 (upward sort)
	lodsb				; AL = next sort order letter
	or	al,al
	jz	poOk			; NUL found, return success

	cmp	al,'-'
	jne	@F			; not '-', go look for letter
	mov	dl,80h			; DL = downward sort mask
	lodsb				; AL = next char
@@:
	mov	di,offset TRANGROUP:OrderLtrs  ; DI = ptr to list of letters
	mov	cx,NUM_ORDER_LTRS	; CX = length of list
	repne	scasb			; look for our letter in the list
	jne	poErr			; not found, return error

	neg	cx
	add	cx,NUM_ORDER_LTRS	; CL = sort order code, 1-6
ifdef DBLSPACE_HOOKS
	cmp	cl, CRATIO_ORDER	; force compression ratio reporting
	jne	poSet			;   on if sorting by ratio selected.
	or	Bits, mask cratio
poSet:
endif
	or	cl,dl			; CL = sort code with up/dn bit
	mov	[bx],cl			; store sort order code in buffer
	inc	bx			; BX = ptr to next spot in buffer
	cmp	bx,offset TRANGROUP:EndDestBuf
	jae	poErr			; too many letters

	jmp	poLtr			; go look at next char

;	The sort order string is invalid.  

poErr:	pop	bx			; BX = ptr to result buffer
	call	SetupParamError		; set message up for Std_EPrintf
	stc				; return failure
	jmp	short poRet

poOk:	mov	byte ptr [bx],0		; mark end of sort code list
	pop	bx			; BX = ptr to result buffer
	clc				; return success

poRet:	pop	si			; restore SI
	ret

ParseOrder	endp


ifdef DBLSPACE_HOOKS
;***	ParseRatio - parse and record /C[H] option
;
;	ENTRY	BX = ptr to system parser result buffer for /C occurence
;		DI = index into word list of switches
;
;	EXIT	CY = set if error occurs parsing order
;
;		For parse error, we set up for Std_EPrintf call:
;		AX = parse error code, like system parser
;		DX = ptr to message block
;
;	USED	AX,CX,DX,DI
;
;	EFFECTS
;
;	  Bits modified to indicate option state.
;	  fUseHostSize is set to zero for /C, non-zero for /CH.
;
;	  For parse error, we set up for Std_EPrintf call:
;	  Msg_Disp_Class = parse error message class
;	  Message block (see DX) is set up for parse error message

ParseRatio	proc

	call	OnOffSw 	; turn on option bit

	push	si				; save SI

	mov	si,word ptr [bx].ValuePtr	; SI = ptr to possible H option
	mov	al,[si] 			; AL = null or 'H'
	or	al,al
	jz	prDone			; if null, no H option to check

	cmp	al, 'H' 		; only H is allowed, make sure that's
	je	prDone			;   what it is

	call	SetupParamError		; set message up for Std_EPrintf
	stc				; return failure
	jmp	short prRet

prDone:
	mov	fUseHostSize, al	; set Host cluster size flag
	clc

prRet:	pop	si
	ret

ParseRatio	endp
endif



;***	ParseSwitch - parse a switch
;
;	ENTRY	BX = ptr to parse result buffer after system parser processed
;		     a switch
;
;	EXIT	CY = set if parse error occurred
;
;		If parse error occurred, we're set up for Std_EPrintf call:
;		AX = parse error code, like system parser
;		DX = ptr to message block
;
;	USED	AX,BX,DX
;
;	EFFECTS
;
;	  Bits may contain new option settings.
;	  DestBuf may contain new series of sort codes.
;	  AttrSpecified, AttrSelect may contain new attribute conditions.
;
;	  If parse error occurred, we're set up for Std_EPrintf call:
;	  Msg_Disp_Class = parse error class
;	  Byte after last parameter in text is zeroed to make ASCIIZ string
;	  Message block (see DX) is set up for parse error message

ParseSwitch	proc

	push	cx			; save CX
	push	di			; save DI

	mov	ax,[bx].SynPtr		; AX = synonym ptr
	mov	di,offset TRANGROUP:Dir_Sw_Ptrs
					; ES:DI = ptr to list of synonym ptrs
	mov	cx,NUM_DIR_SWS		; CX = # of dir switches in list
	cld				; scan direction = upward
	repne	scasw			; locate synonym ptr in list
	sub	di,offset TRANGROUP:Dir_Sw_Ptrs + 2

;	DI = index into word list of synonym ptrs

	call	cs:SwHandlers[di]	; use same index into call table

	pop	di			; restore DI
	pop	cx			; restore CX

	ret

;	Order in this table must correspond to order in Dir_Sw_Ptrs list.
;	Simple on/off switches must occur first in both lists, and must be
;	  in order of option bits in Bits, starting with bit 0.

SwHandlers	label	word
ifdef DBLSPACE_HOOKS
	dw	OnOffSw 	; /-C
	dw	ParseRatio	; /C[H]
endif
	dw	OnOffSw		; /-W
	dw	OnOffSw		; /W
	dw	OnOffSw		; /-P
	dw	OnOffSw		; /P
	dw	OnOffSw		; /-S
	dw	OnOffSw		; /S
	dw	OnOffSw		; /-B
	dw	OnOffSw		; /B
	dw	OnOffSw		; /-L	;M010
	dw	OnOffSw		; /L	;M010
	dw	NoOrder		; /-O
	dw	ParseOrder	; /O
	dw	DefaultAttr	; /-A
	dw	ParseAttr	; /A

ParseSwitch	endp



	break	<DIR utility routines>

;***	UTILITY ROUTINES




;***	ChangeDir - change directory on target drive
;
;	ENTRY	FCB contains drive #
;		DS:DX = ptr to ASCIIZ string w/o drive specifier
;
;	EXIT	Changed current directory on drive
;
;		If error,
;		CY = set
;		DOS Get Extended Error call will get error
;
;	USED	AX,DX,SI,DI
;
;	EFFECTS
;
;	  DirBuf is used to build "d:string".

ChangeDir	proc

	mov	di,offset TRANGROUP:DirBuf
	call	GetDriveLtr	; AX = "d:"
	stosw			; put drive specifier in buffer
	mov	si,dx		; SI = ptr to argument string
cdLoop:
	lodsb
	stosb			; move byte to buffer
	or	al,al
	jne	cdLoop		; continue until null transferred

	mov	dx,offset TRANGROUP:DirBuf	; DX = ptr to "d:string"
	mov	ah,CHDIR
	int	21h				; change directory
	ret					; return what CHDIR returns

ChangeDir	endp




;***	CmpAscz - compare two ASCIIZ strings alphanumerically
;
;	ENTRY	DS:SI = ptr to one ASCIIZ string
;		ES:DI = ptr to another ASCIIZ string
;
;	EXIT	flags set after REPE CMPSB
;
;	USED	AL,CX,SI,DI
;
;	NOTES
;
;	Maximum run of comparison is length of DS:SI string.
;	This ensures that two identical strings followed by
;	random characters will compare correctly.

CmpAscz	proc

	push	di

	mov	di,si
	xor	al,al
	mov	cx,0FFFFh
	repne	scasb
	not	cx

	pop	di
	repe	cmpsb
	ret

CmpAscz	endp




;***	CopyPathname - copy pathname to our buffer
;
;	ENTRY	BX = ptr to parse result buffer after system parser processed
;		     a filespec
;
;	EXIT	nothing
;
;	USED	AX
;
;	EFFECTS
;
;	  SrcBuf may contain a new pathname/filespec.
;	  PathPos, PathCnt updated for new pathname.

CopyPathname	proc

	push	si
	lds	si,dword ptr [bx].ValuePtr  ; load far ptr from result buffer
	invoke	Move_To_SrcBuf		    ; copy pathname to SrcBuf
	pop	si
	ret

CopyPathname	endp



;***	CountFile - update counters with current file
;
;	ENTRY	BX = offset of entry in TPA buffer
;
;	EXIT	nothing
;
;	USED	AX,DX
;
;	EFFECTS
;
;	  FileCnt, FileCntTotal, FileSiz, FileSizTotal are updated.

CountFile	proc

	push	es			; save TRANGROUP seg addr
	mov	es,Tpa			; ES = TPA seg addr

	inc	FileCnt			; # files this directory
	inc	word ptr FileCntTotal	; # files total
	jnz	@F
	inc	word ptr FileCntTotal+2
@@:
	mov	ax,word ptr es:[bx].filesize	; AX = low word of file size
	mov	dx,word ptr es:[bx].filesize+2	; DX = high word of file size
	add	word ptr FileSiz,ax
	adc	word ptr FileSiz+2,dx		; size of this directory
	add	word ptr FileSizTotal,ax
	adc	word ptr FileSizTotal+2,dx	; total size of files listed

	pop	es			; ES = TRANGROUP seg addr again
	ret

CountFile	endp



;***	DisplayBare - display filename in bare format
;
;	ENTRY	BX = offset of entry in TPA buffer
;
;	EXIT	DX = # char's displayed, including dot
;
;	USED	AX,CX,SI,DI
;
;	EFFECTS
;
;	  Filename is displayed in name.ext format, followed by cr/lf.
;	  If /s is on, complete pathname is displayed.
;
;	NOTE
;
;	  Directory pseudofiles . and .. and suppressed in bare listing.

DisplayBare	proc

;	Suppress . and .. files from bare listing.

	mov	cx,ds			; CX = saved TRANGROUP seg addr
	mov	ds,Tpa			; DS:BX = ptr to file entry
	assume	ds:NOTHING
	cmp	ds:[bx].filename,'.'	; check 1st char of filename
	mov	ds,cx			; DS = TRANGROUP seg addr again
	assume	ds:TRANGROUP
	je	dbRet			; it's . or .. - don't display

	test	Bits,mask subd
	jz	dbNameExt		; not /s - display filename only

	invoke	Build_Dir_String
	mov	di,offset TRANGROUP:BwdBuf	; ES:DI = ptr to dir string
   
	test	Bits,mask lcase		;M010;check for lowercase option
	jz	@F			;M010;lowercase not needed
	mov	si,di			;M010;DS:SI --> ASCIIZ string in BwdBuf	
	call	LowercaseString		;M010;path string is in BwdBuf

@@:	xor	al,al			; AL = 0
	mov	cx,0FFFFh
	cld
	repne	scasb			; ES:DI = ptr to byte after null
	dec	di			; ES:DI = ptr to null byte

ifdef DBCS
	push	si
	push	di
	mov	si,offset TRANGROUP:BwdBuf
	dec	di
	call	CheckDBCSTailByte
	pop	di
	pop	si
	jz	dbTailByte		; if last char is double byte
endif

	cmp	byte ptr es:[di-1],'\'
	je	@F			; already terminated w/ '\'

ifdef DBCS
dbTailByte:
endif

	mov	ax,'\'			; AX = '\',0
	stosw				; add to dir string
@@:
	mov	String_Ptr_2,offset TRANGROUP:BwdBuf
	mov	dx,offset TRANGROUP:String_Buf_Ptr
	invoke	Std_Printf		; display device & directory path

dbNameExt:
	call	DisplayDotForm		; display name.ext
	invoke	CrLf2			; display cr/lf
	call	UseLine			;M007;Allow /p with /b
dbRet:	ret

DisplayBare	endp




;***	DisplayDotForm - display filename in compressed dot format
;
;	Display name.ext, with no cr/lf's.  Dot is displayed only
;	if the filename has a nonblank extension.
;
;	ENTRY	BX = offset of entry in TPA buffer
;
;	EXIT	DX = # char's displayed, including dot
;
;	USED	AX,CX,SI,DI
;
;	EFFECTS
;
;	  Filename is displayed in name.ext format.
;
;	NOTE
;
;	  We allow for bogus filenames that have blanks embedded
;	  in the name or extension.

;	Bugbug:	might be a good performance gain if we buffered
;	up the output and used DOS function 9.

DisplayDotForm	proc

	push	ds			; save TRANGROUP seg addr
	push	es			; save ES
	mov	ax,cs:Tpa		; AX = TPA seg addr
	mov	ds,ax			; DS:BX = ptr to entry
	assume	ds:nothing
	mov	es,ax			; ES:BX = ptr to entry

	mov	di,bx			; ES:DI = ptr to entry
	add	di,filename + size filename - 1
					; ES:DI = ptr to last char in name field
	mov	cx,size filename	; CX = length of name field
	mov	al,' '
	std				; scan down
	repe	scasb			; scan for nonblank

;	Assume file name has at least one character.

	inc	cx			; CX = # chars in name
	mov	dx,cx			; DX = # chars to be displayed

	mov	si,bx			; DS:SI = ptr to entry
	add	si,filename		; DS:SI = ptr to name

NextNameChar:
	cld
	lodsb				; AL = next char

ifdef DBCS
	invoke	testkanj
	jz	@f			; if this is not lead byte
	invoke	Print_Char		; display lead byte
	dec	cx
	jz	ExtChar			; if this is end
	lodsb				; get tail byte
	jmp	short NameChar10	; display tail byte
@@:
endif

	test	Bits,mask lcase		;M010;check for lowercase option
	jz	@F			;M010;lowercase not required
	call	LowerCase		;M010;filename char is in AL

ifdef DBCS
NameChar10:
endif

@@:	invoke	Print_Char		; display it
	loop	NextNameChar

ifdef DBCS
ExtChar:
endif

;	Now do extension.

	mov	di,bx			; ES:DI = ptr to entry
	add	di,fileext + size fileext - 1
					; ES:DI = ptr to last char in ext field
	mov	cx,size fileext		; CX = length of ext field
	mov	al,' '
	std				; scan down
	repe	scasb			; scan for nonblank
	je	ddDone			; no nonblank chars in ext

	inc	cx			; CX = # chars in ext
	add	dx,cx			; DX = total # chars to be displayed
	inc	dx			;      including dot

	mov	al,'.'
	invoke	Print_Char
	mov	si,bx			; DS:SI = ptr to entry
	add	si,fileext		; DS:SI = ptr to ext

NextExtChar:
	cld
	lodsb				; AL = next char

ifdef DBCS
	invoke	testkanj
	jz	@f			; if this is not lead byte
	invoke	Print_Char		; display lead byte
	dec	cx
	jz	ddDone			; if this is end
	lodsb				; get tail byte
	jmp	short ExtChar10		; display tail byte
@@:
endif

	test	CS:Bits,mask lcase	;M010;check for lowercase option
	jz	@F			;M010;lowercase not required
	call	LowerCase		;M010;fileext char is in AL

ifdef DBCS
ExtChar10:
endif

@@:	invoke	Print_Char		; display it
	loop	NextExtChar

ddDone:	pop	es			; restore ES
	pop	ds			; DS = TRANGROUP seg addr again
	assume	ds:TRANGROUP
	cld				; leave direction flag = up
	ret

DisplayDotForm	endp




;***	DisplayFile - display file entry, update counters
;
;	ENTRY	BX = offset of entry in TPA buffer
;		Bits contains /w, /p settings
;
;	EXIT	nothing
;
;	USED	AX,CX,DX,SI,DI,BP
;
;	EFFECTS
;
;	  Entry is displayed.  
;	  If not /b,
;	    Cursor is left at end of entry on screen.
;	    FileCnt, FileCntTotal, FileSiz, FileSizTotal are updated.
;	  If /b,
;	    Cursor is left at beginning of next line.
;	    Cnt's and Siz's aren't updated.

DisplayFile	proc

	test	Bits,mask bare
	jz	dfNorm			; not /b - do normal display

	call	DisplayBare		; display file in bare format
	jmp	short dfRet

dfNorm:	call	DisplayNext		; pos'n cursor for next entry
	test	Bits,mask wide
	jz	dfFull			; full format
	call	DisplayWide		; wide format
	jmp	short dfCnt

dfFull:	call	DisplayName		; display filename & extension
	call	DisplayTheRest		; display size, date, time
ifdef DBLSPACE_HOOKS
	test	Bits,mask cratio	; display compression ratio
	jz	dfCnt
	call	DisplayCompRatio
endif
dfCnt:	call	CountFile		; update file counters
dfRet:	ret

DisplayFile	endp




;***	DisplayHeader - display directory header of working directory
;
;	ENTRY	Current directory (on selected drive) is the one to display
;		LeftOnPage = # lines left on display page
;
;	EXIT	nothing
;
;	ERROR EXIT
;
;	  Build_Dir_String will exit through CError with "Invalid drive
;	   specification" if there's a problem obtaining the current 
;	   directory pathname.
;
;	USED	AX,DX,SI,DI
;
;	EFFECTS
;
;	  BwdBuf (which is really the same buffer as DirBuf, which
;	   we are using for the DTA) contains the directory string.
;	  LeftOnPage is adjusted.

DisplayHeader	proc

	test	Bits,mask bare
	jnz	dhRet			; /b - don't display header

	test	Bits,mask subd
	jz	dhNorm			; not /s

;	For subdirectory listings, put a blank line before the header.

	invoke	Crlf2			; start with a blank line
	call	UseLine
	jmp	short dhCom
dhNorm:
	mov	al,BLANK		; if not /s, precede by a blank
	invoke	Print_Char		; print a leading blank
dhCom:
	invoke	Build_Dir_String
	mov	dx,offset TRANGROUP:DirHead_ptr
	invoke	Std_Printf		; print header & cr/lf
	call	UseLine
	invoke	Crlf2			; another cr/lf
	call	UseLine
dhRet:	ret

DisplayHeader	endp




;***	DisplayName - display file name & extension
;
;	ENTRY	BX = offset of entry in TPA buffer
;
;	EXIT	nothing
;
;	USED	AX,CX,DX,SI,DI
;
;	EFFECTS
;
;	  Filename & extension are displayed in spread format.
;	  Cursor is left at end of extension.

DisplayName	proc

	push	ds				; save TRANGROUP seg addr
	mov	ds,Tpa				; DS:BX = ptr to entry
	assume	ds:nothing
	mov	si,bx				; DS:SI = ptr to entry
	add	si,filename			; DS:SI = ptr to filename
	mov	di,offset TRANGROUP:CharBuf	; ES:DI = ptr to CharBuf

	mov	cx,8
	cld
	rep	movsb				; move filename to CharBuf
	mov	al,' '
	stosb					; add a blank
	mov	cx,3
	rep	movsb				; add extension
	xor	al,al
	stosb					; add a NULL

	pop	ds				; DS = TRANGROUP seg addr again
	assume	ds:TRANGROUP

	test	Bits,mask lcase			;M010;check for lowercase option
	jz	@F			        ;M010;lowercase not required
	mov	si,offset TRANGROUP:CharBuf	;M010;DS:SI --> ASCIIZ string
	call	LowercaseString			;M010;filename.ext string is in CharBuf

@@:	mov	String_Ptr_2,offset TRANGROUP:CharBuf
	mov	dx,offset TRANGROUP:String_Buf_Ptr
	invoke	Std_Printf			; print filename & extension
	ret

DisplayName	endp




;***	DisplayNext - move display cursor to next entry position
;
;	ENTRY	LeftOnLine = # entries can still be printed on this line
;		LeftOnPage = # lines can still be printed for this page
;		FileCnt = # files in this dir displayed before this one
;		Bits contains /w setting
;
;	EXIT	nothing
;
;	USED	AX,DX
;
;	EFFECTS
;
;	  LeftOnLine will be updated to reflect the entry about to be
;	   displayed.
;	  LeftOnPage may be updated.

DisplayNext	proc

	cmp	FileCnt,0
	je	dn1st			; 1st file in directory
	cmp	LeftOnLine,0
	jng	dnEol			; no more room on this line

;	We are in wide mode (LeftOnLine is always 0 otherwise) and
;	we still have room for more on this line.
;	Tab to next position.

	mov	dx,offset TRANGROUP:Tab_Ptr
	invoke	Std_Printf
	jmp	short dnDone

dnEol:	

;	Start this entry on a new line.

	invoke	Crlf2		; start on new line
	call	UseLine
dn1st:	mov	al,PerLine
	mov	LeftOnLine,al	; reset # entries left on line

dnDone:	dec	LeftOnLine	; reflect the entry about to be displayed
	ret

DisplayNext	endp




;***	DisplayTheRest - display file size/dir, date, time
;
;	ENTRY	BX = offset of entry in TPA buffer
;		Display cursor is at end of file extension
;
;	EXIT	nothing
;
;	USED	AX,CX,DX,SI,DI,BP
;
;	EFFECTS
;
;	  File size, date, & time are displayed.

DisplayTheRest	proc

	push	es			; save TRANGROUP seg addr
	mov	es,Tpa			; ES = TPA seg addr
	mov	bp,bx			; BP = offset of entry in TPA
	test	es:[bp].fileattr,ATTR_DIRECTORY
	jz	drNonDir		; not a directory file

;	For a directory file, display <DIR> instead of size.

	mov	dx,offset TRANGROUP:DMes_Ptr
	invoke	Std_Printf
	jmp	short drCom		; skip to common fields

drNonDir:

;	For a non-directory file, display file size.

	mov	dx,word ptr es:[bp].filesize
	mov	File_Size_Low,dx
	mov	dx,word ptr es:[bp].filesize+2
	mov	File_Size_High,dx
	mov	dx,offset TRANGROUP:Disp_File_Size_Ptr
	invoke	Std_Printf

drCom:

;	For all files, display date & time.

	mov	ax,es:[bp].filedate	; AX = date word
	or	ax,ax			; test for null date (DOS 1.x)
	jz	drDone			; no date, skip date/time display
	mov	bx,ax			; BX = date word
	and	ax,1Fh			; AX = day of month
	mov	dl,al			; DL = day of month
	mov	ax,bx			; AX = date word
	mov	cl,5
	shr	ax,cl			; shift day out
	and	al,0Fh			; AL = month
	mov	dh,al			; DH = month
	mov	cl,bh
	shr	cl,1			; CL = year - 1980
	xor	ch,ch			; CX = year - 1980
	add	cx,80			; CX = 2-digit year
	cmp	cl,100
	jb	@F			; not year 2000 yet, skip ahead
	sub	cl,100			; adjust for 21st century
@@:	xchg	dh,dl			; DX = month/day
	mov	DirDat_Yr,cx		; move year to msg block
	mov	DirDat_Mo_Day,dx	; move month/day to msg block
	mov	cx,es:[bp].filetime	; CX = file time
	jcxz	drPrint			; no time field - go print
	shr	cx,1
	shr	cx,1
	shr	cx,1			; CH = hours
	shr	cl,1
	shr	cl,1			; CL = minutes
	xchg	ch,cl			; CX = hr/min
	mov	DirTim_Hr_Min,cx	; move time to msg block
drPrint:mov	dx,offset TRANGROUP:DirDatTim_Ptr
	invoke	Std_Printf		; print date & time

drDone:	pop	es			; ES = TRANGROUP seg addr again	
	mov	bx,bp			; BX = offset of entry in TPA again
	ret

DisplayTheRest	endp

ifdef DBLSPACE_HOOKS
;***	DisplayCompRatio - display compression ratio
;
;	ENTRY	BX = offset of entry in TPA buffer
;
;	EXIT	nothing
;
;	USED	AX,CX,DX
;
;	EFFECTS
;
;	  File compression ratio is displayed.


DisplayCompRatio	proc

	push	es			; save TRANGROUP seg addr
	mov	es,Tpa			; ES = TPA seg addr
	mov	al, es:[bx].compratio
	cmp	al, 0FFh		; invalid/no compression ratio?
	je	dcrRet

	mov	ah, al			; unpack compression ratio
	mov	cl, 4
	shr	ah, cl			; isolate whole number portion
	inc	ah			; 0-15 = 1-16
	and	al, 0Fh 		; isolate tenths

	mov	Dir_CRatio_1, ah
	mov	Dir_CRatio_2, al
	mov	dx, offset TRANGROUP:DirCompRatio_Ptr
	invoke	Std_Printf
dcrRet:
	pop	es

	ret

DisplayCompRatio	endp
endif

;***	DisplayTrailer - display trailing lines for directory listing
;
;	ENTRY	LeftOnPage = # lines left on display page
;		FileCnt = # files listed
;		FileSiz = total size of files listed
;
;	EXIT	nothing
;
;	USED
;
;	EFFECTS
;
;	  Trailing info lines are displayed

DisplayTrailer	proc

	test	Bits,mask bare
	jnz	dtrRet				; /b - don't display trailer

	invoke	Crlf2				; start on new line
	call	UseLine
	mov	ax,FileCnt			; AX = # files found

;	DisplayTotals uses this entry point.
;
DisplayCntSiz:
ifdef DBLSPACE_HOOKS
	test	Bits, mask cratio
	jz	dtrSize

	push	ax				; save file size

	mov	ax, [ccluUsedDir]
	or	ax, ax
	jz	dtrSkipRatio

	mov	cx, word ptr [csecUsedDir+2]
	mov	bx, word ptr [csecUsedDir] ; cx:bx = # compressed sectors used

	call	ComputeRatio		; ax=clusters used, cx:bx=sectors used

	mov	Dir_CRatio_1, ah			; 1 - 16
	mov	Dir_CRatio_2, al			; tenths
	mov	dx,offset TRANGROUP:AveCompRatio_Ptr
	invoke	Std_Printf
	call	UseLine

dtrSkipRatio:
	pop	ax				; restore file size

dtrSize:
endif
;	AX = # files
;	FileSiz = dword total size of files

	mov	Dir_Num,ax			; load # files
	mov	dx,offset TRANGROUP:DirMes_Ptr	; DX = ptr to message block
	invoke	Std_Printf			; "nnn File(s)"

	mov	dx,offset TRANGROUP:Bytes_Ptr
	invoke	Std_Printf			; "nnn bytes",cr,lf
	call	UseLine

dtrRet:	ret

DisplayTrailer	endp




;***	DisplayWide - display filename in wide format
;
;	ENTRY	BX = offset of entry in TPA buffer
;
;	EXIT	nothing
;
;	USED	AX,CX,DX,SI,DI
;
;	EFFECTS
;
;	  Name.ext is displayed.  Cursor left at end of field (padded
;	  with blanks).  Subdirectory files are displayed as [name.ext].

DisplayWide	proc

	push	ds				; save TRANGROUP seg addr
	mov	ds,Tpa				; DS:BX = ptr to entry
	assume	ds:nothing

	test	ds:[bx].fileattr,ATTR_DIRECTORY
	jz	@F				; not a subdirectory file
	mov	al,'['
	invoke	Print_Char			; prefix subdirectory

@@:	call	DisplayDotForm			; display name.ext

;	DX = # chars displayed in name.ext

	test	ds:[bx].fileattr,ATTR_DIRECTORY
	jz	@F				; not a subdirectory file
	mov	al,']'
	invoke	Print_Char			; postfix subdirectory
@@:
;	Pad field with blanks.

	mov	cx,size filename + size fileext + 1
						; CX = field size
	sub	cx,dx				; CX = # pad char's
	jcxz	dwDone
	mov	al,' '

@@:	invoke	Print_Char
	loop	@B

dwDone:	pop	ds			; DS = TRANGROUP seg addr again
	assume	ds:TRANGROUP
	ret

DisplayWide	endp




;***	EndPage - end the current display page
;
;	ENTRY	LeftOnPage = # lines left on display page
;		Current directory (on selected drive) is the one being listed
;		Bits contains /p setting
;
;	EXIT	LeftOnPage = # lines left for next page
;
;	USED	AX,DX
;
;	EFFECTS
;
;	  Pause is invoked to display a message and wait for a keystroke.
;	  BwdBuf (same as DirBuf) used to build directory string.

EndPage	proc

	test	Bits,mask pagd
	jz	epNew			; paged display isn't enabled

	push	bx			; save BX
	push	cx			; save CX

	invoke	Pause			; "Press any key to continue..."

	invoke	Build_Dir_String
	mov	dx,offset TRANGROUP:DirCont_Ptr
	invoke	Printf_Crlf		; "(continuing <dir>)", cr/lf

	pop	cx			; restore CX
	pop	bx			; restore BX

epNew:	mov	ax,LinPerPag		; AX = # lines per page
	dec	ax			; AX = # lines till next EndPage
	mov	LeftOnPage,ax		; LeftOnPage = countdown variable

	ret

EndPage	endp




;***	GetDriveLtr - get target drive letter
;
;	ENTRY	FCB contains drive #
;
;	EXIT	AX = "d:"
;
;	USED	nothing

GetDriveLtr	proc

	mov	al,ds:Fcb	; AL = target drive #
	or	al,al
	jnz	@F		; not current drive default, skip ahead
	mov	al,ds:CurDrv	; AL = current drive #
	inc	al		; AL = 1-based drive #
@@:	add	al,'A'-1	; AL = target drive letter
	mov	ah,':'		; AX = "d:"
	ret

GetDriveLtr	endp





;***	SetupParamError - set up for Std_EPrintf parameter parse error message
;
;	Do for our /O and /A string parsers what Parse_With_Msg does
;	for system parser calls.  Set up a message substitution block,
;	etc. for invalid value strings.  I copied the procedure from
;	Setup_Parse_Error_Msg.
;
;	ENTRY	BX = ptr to system parser result buffer (contains ptr to str)
;		
;
;	EXIT	AX = system parser error return code for bad param format
;		DX = ptr to message description block for Std_EPrintf
;
;	USED	SI
;
;	EFFECTS
;
;	  Msg_Disp_Class = parse error message class
;	  Message block (see DX) is set up for parse error message

SetupParamError	proc

	mov	ax,9			; parse error #
	mov	Msg_Disp_Class,PARSE_MSG_CLASS
	mov	Extend_Buf_Ptr,ax
	mov	si,word ptr [bx].ValuePtr
	mov	String_Ptr_2,si
	mov	Extend_Buf_Sub,ONE_SUBST
	mov	dx,offset TRANGROUP:Extend_Buf_Ptr
	ret

SetupParamError	endp




;***	UseLine - use a display line, start a new page if none left
;
;	ENTRY	nothing
;
;	EXIT	nothing
;
;	USED	flags

UseLine	proc

	dec	LeftOnPage
	cmp	LeftOnPage,2
	ja	ulRet
	call	EndPage
ulRet:	ret

UseLine	endp




;***	ZeroTotals - zero grand total file count, size
;
;	ENTRY	nothing
;
;	EXIT	nothing
;
;	USED	AX
;
;	EFFECTS
;
;	  FileCntTotal & FileSizTotal are zeroed.
;
;	NOTES
;
;	  FileCntTotal throuth csecUsedTotal must be together!
ifdef DBLSPACE_HOOKS
csecSIZE	EQU	size csecUsed + size csecUsedDir + size csecUsedTotal
ccluSIZE	EQU	size ccluUsed + size ccluUsedDir + size ccluUsedTotal
endif
ZeroTotals	proc

	mov	di,offset TRANGROUP:FileCntTotal
ifdef DBLSPACE_HOOKS
	mov	cx,size FileCntTotal+size FileSizTotal+csecSIZE+ccluSIZE
else
	mov	cx,size FileCntTotal+size FileSizTotal
endif
	xor	al,al
	rep	stosb
	ret	

ZeroTotals	endp




;***	CtrlCHandler - our own control-c handler
;
;	Make sure user's default directory gets restored.  See notes
;	at InstallCtrlCHandler.
;
;	ENTRY	control-c
;
;	EXIT	to OldCtrlCHandler
;
;	USED	DS,flags
;
;	EFFECTS
;
;	  Restore user's default directory.
;
;	NOTES
;
;	  This handler is only installed after calling PathCrunch,
;	  which sets UserDir1, so the restoration will work.
;
;	  The original control-c vector will be restored, whether
;	  or not this one is invoked, in the HeadFix routine.

CtrlCHandler	proc	far

;SR;
; Save all registers used: ds, dx, ax. I know ax is being used by the 
;CtrlC handler, am not sure about ds & dx. Save them to be safe
;

	push	ds
	push	cs
	pop	ds			; DS = TRANGROUP seg addr
	push	ax
ifdef DBLSPACE_HOOKS
	push	bx
endif
	push	dx

ifdef DBLSPACE_HOOKS
	call	CloseCVF		; close CVF file if open
endif
	invoke	RestUDir		; restore user's default directory

	pop	dx
ifdef DBLSPACE_HOOKS
	pop	bx
endif
	pop	ax
	pop	ds
	jmp	cs:OldCtrlCHandler		; go to previous int 23 handler

CtrlCHandler	endp


;M010;start
;***	LowerCase - convert ASCII character in AL to lowercase
;
;	ENTRY	AL = character to be displayed
;
;	EXIT	AL is lowercase
;
;	USED	nothing

LowerCase	proc

	assume	ds:NOTHING,es:NOTHING

	cmp	al,'A'			; ensure AL is in range 'A'-'Z'
	jb	lcRet
	cmp	al,'Z'
	ja	lcRet

	or	al,20h			; convert to ASCII lowercase (UpperCase+32)-->LowerCase

lcRet:	ret

LowerCase	endp




;***	LowercaseString - convert ASCIIZ string at DS:SI to lowercase
;
;	ENTRY	DS:SI points to start of ASCIIZ string
;		ES = DS
;
;	EXIT	nothing
;	
;	USED	AL,SI

LowercaseString	proc

	assume	ds:NOTHING,es:NOTHING

	push	di			; save di
	mov	di,si			; ES:DI --> ASCIIZ string
	cld

NextChar: 
	lodsb				; get character from string into al
	or	al,al			; are we at end of string?
	jz	EndOfString

ifdef DBCS
	invoke	testkanj
	jz	@f			; if this is not lead byte
	stosb				; store lead byte
	lodsb				; get tail byte
	or	al,al
	jz	EndOfString		; if end
	stosb				; store tail byte
	jmp	short NextChar
@@:
endif

	call	LowerCase		; convert character to lowercase
	stosb				; store character back into buffer
	jmp	SHORT NextChar		; repeat until end of string

EndOfString:
	pop	di			; restore di
	ret

LowercaseString	endp
;M010;end	

ifdef DBCS
;
;	Check if the character position is at Tail Byte of DBCS
;
;	input:	ds:si = start address of the string
;		ds:di = character position to check
;	output:	ZF = 1 if at Tail Byte
;
CheckDBCSTailByte	proc	near
	push	ax
	push	cx
	push	di
	mov	cx,di			; save character position
cdtb_check:
	cmp	di,si
	jz	cdtb_next		; if at the top
	dec	di			; go back
	mov	al,[di]			; get character
	invoke	testkanj
	jnz	cdtb_check		; if DBCS lead byte do next
	inc	di			; adjust
cdtb_next:
	sub	cx,di			; if the length is odd then
	xor	cl,1			; the character position is
	test	cl,1			; at the tail byte
	pop	di
	pop	cx
	pop	ax
	ret
CheckDBCSTailByte	endp
endif


TRANCODE ends

	end

