;	DDIR.ASM -- Double Column Sorted DIR Command
;	========
;			(C) Copyright Charles Petzold, 1985
;
;			COM file format
;
	
CSEG		Segment

		Assume	CS:CSEG, DS:CSEG

		Org	002Ch			; Offset of Environment
Environment	Label	Byte

		Org	007Bh			; Parameter for COMMAND.COM
NewParameter	Label	Byte

		Org	0080h			; Parameter passed to program
OldParameter	Label	Byte	

		Org	0100h			; Entry point
Entry:		Jmp	Begin

;	All Data
;	--------

		db	'(C) Copyright Charles Petzold, 1985'

DosVersMsg	db	"Needs DOS 2.0 +$"	; Error messages
MemAllocMsg	db	"Memory Problem$"
CommandMsg	db	"COMMAND Problem$"

Comspec		db	"COMSPEC="		; Search string in environment
CommandAsciiz	dd	?			; Eventual pointer to COMMAND 

ParamBlock	dw	?			; Parameter Block for EXEC
		dw	NewParameter,?		; First ? must be replaced
		dw	5Ch,?			;    with Environment segment;
		dw	6Ch,?			;    others with this segment

OldInterrupt21	dd	?			; For vector address storage

BufferPtr	dw	Offset FileBuffer	; For storing files listing
CharCounter	dw	0			; Keeps track of characters
NowDoingFile	db	0			; Flagged for file printed
WithinFileList	db	0			; Flagged for file list
FileCounter	dw	0			; Keeps track of files
LineCounter	db	0			; For pausing at screen end

PauseMessage	db	6 dup (205)," Press any key to continue "
		db	6 dup (205),181 
PauseMsgEnd	Label	Byte

;	Check DOS Version
;	-----------------

Begin:		Mov	AH,30h			; DOS Version function call
		Int	21h			; Call DOS
		Cmp	AL,2			; Check if version 2 
		Jae	DosVersOK		; If equal or over, all OK

		Mov	DX,Offset DosVersMsg	; Wrong DOS version message
ErrorExit:	Mov	AH,9			; Set up for string write
		Int	21h			; Call DOS for message

		Int	20h			; Dishonorable discharge

;	Adjust stack and un-allocate rest of memory 
;	-------------------------------------------

DosVersOK:	Mov	DI,Offset FileBuffer	; Place to save files
		Mov	CX,528 * 39		; Allow room for 528 files
		Mov	AL,' '			; Will clear with blanks
		Cld				; Forward direction
		Rep	Stosb			; Clear the area

		Mov 	BX,(Offset FileBuffer) + (528 * 39) + 100h
					 	; New end of program
		Mov	SP,BX			; Set the stack pointer
		Add	BX,15			; Add 15 for rounding
		Mov	CL,4			; Number of shifts
		Shr	BX,CL			; Convert AX to segment

		Mov	AH,4Ah			; DOS call to shrink down
		Int	21h			;    allocated memory

		Mov	DX,Offset MemAllocMsg	; Possible error message
		Jc	ErrorExit		; Only print it if Carry set

;	Search for Comspec in Environment
;	---------------------------------

		Mov	ES,[Environment]	; Environment Segment
		Sub	DI,DI			; Start search at beginning
		Cld				; String increment to forward

TryThis:	Cmp	Byte Ptr ES:[DI],0	; See if end of environment
		Jz	NoFindComSpec		; If so, we have failed
		
		Push	DI			; Save environment pointer
		Mov	SI,Offset ComSpec	; String to search for
		Mov	CX,8			; Characters in search string
		Repz	Cmpsb			; Check if strings are same
		Pop	DI			; Get back the pointer

		Jz	FoundComspec		; Found string only zero flag

		Sub	AL,AL			; Zero out AL
		Mov	CX,8000h		; Set for big search
		Repnz	Scasb			; Find the next zero in string
		Jmp	TryThis			; And do the search from there

NoFindComSpec:	Mov	DX,Offset CommandMsg	; Message for COMSPEC error
		Jmp	ErrorExit		; Print it and exit

FoundComspec:	Add	DI,8			; So points after 'COMSPEC='
		Mov	Word Ptr [CommandASCIIZ],DI	; Save the address of
		Mov	Word Ptr [CommandASCIIZ + 2],ES	;    COMMAND ASCIIZ

; 	Set up parameter block for EXEC call
;	------------------------------------

		Mov	[ParamBlock],ES		; Segment of Environment string
		Mov	[ParamBlock + 4],CS	; Segment of this program
		Mov	[ParamBlock + 8],CS	;    so points to FCB's
		Mov	[ParamBlock + 12],CS	;    and NewParameter

;	Save and set Interrupt 21h vector address
;	----------------------------------------- 

		Mov	AX,3521h		; DOS call to get Interrupt 21
		Int	21h			;    vector address
		Mov	Word Ptr [OldInterrupt21],BX		; Save offset
		Mov	Word Ptr [OldInterrupt21 + 2],ES	; And segment	

		Mov	DX,Offset NewInterrupt21; Address of new Interrupt 21 
		Mov	AX,2521h		; Do DOS call to
		Int	21h			;    set the new address

;	Fix up new parameter for "/C DIR" String
;	------------------------------------

		Mov	AL,[OldParameter]	; Number of parameter chars 	
		Add	AL,5			; We'll be adding five more
		Mov	[NewParameter],AL	; Save it
		Mov	Word Ptr [NewParameter + 1],'C/'	; i.e. "/C"
		Mov	Word Ptr [NewParameter + 3],'ID'	; Then "DI"	
		Mov	Byte Ptr [NewParameter + 5],'R'		; And "R"

; 	Load COMMAND.COM
; 	-----------------
		
		Push	CS			; Push this segment so we can
		Pop	ES			;    set ES to it
		Mov	BX,Offset ParamBlock	; ES:BX = address of block
		Lds	DX,[CommandAsciiz]	; DS:DX = address of ASCIIZ
		Mov	AX,4B00h		; EXEC call 4Bh, type 0
		Int	21h			; Load command processor

; 	Return from COMMAND.COM
;	-----------------------

		Mov	AX,CS		; Get this segment in AX
		Mov	DS,AX		; Set DS to it
		Mov	SS,AX		; And SS for stack segment
		Mov	SP,(Offset FileBuffer) + (528 * 39) + 100h
					; Set Stack again

		PushF			; Save Carry for error check 
		Push	DS		; Save DS during next call

		Mov	DX,Word Ptr [OldInterrupt21]	; Old Int 21 offset
		Mov	DS,Word Ptr [OldInterrupt21 + 2]; and segment
		Mov	AX,2521h		; Call DOS to set vector
		Int	21h			;    address to original	

		Pop	DS			; Restore DS to this segment
		PopF				; Get back Carry flage

		Jnc	NormalEnd		; Continue if no error

		Mov	DX,Offset CommandMsg	; Otherwise we'll print error
		Jmp	ErrorExit		;    message and exit

NormalEnd:	Int	20h			; Terminate program

;	New Interrupt 21h
;	-----------------

NewInterrupt21	Proc	Far

		Sti				; Allow further interrupts
		Cmp	AH,40h			; Check if file / device write
		Je	CheckHandle		; If so, continue checks

SkipIntercept:	Jmp	CS:[OldInterrupt21]	; Just jump to old interrupt

CheckHandle:	Cmp	BX,1			; Check if standard output
		Jne	SkipIntercept		; Not interested if not

		PushF				; Push all registers that
		Push	AX			;    we'll be messing with
		Push	CX
		Push	SI
		Push	DI
		Push	ES

		Push	CS			; Push the code segment
		Pop	ES			; So we can set ES to it
		Cld				; Forward for string transfers
		Mov	SI,DX			; Now DS:SI = text source
		Mov	DI,CS:[BufferPtr]	; And ES:DI = text destination

		Cmp	CX,2			; See if two chars to write
		Jne	RegularChars		; If not, can't be CR/LF

		Cmp	Word Ptr DS:[SI],0A0Dh	; See if CR/LF being written
		Jne	RegularChars		; Skip rest if not CR/LF

		Mov	CX,CS:[CharCounter]	; Get characters in line
		Mov	CS:[CharCounter],0	; Start at new line
		Cmp	CS:[NowDoingFile],1	; See if CR/LF terminates file
		Jnz	AllowTransfer		; If not, just write to screen

		Mov	AX,39			; Max characters per line
		Sub	AX,CX			; Subtract those passed 
		Add	CS:[BufferPtr],AX	; Kick up pointer by that
		Mov	CS:[NowDoingFile],0	; Finished with file
		Jmp	PopAndReturn		; So just return to COMMAND

RegularChars:	Add	CS:[CharCounter],CX	; Kick up counter by number
		Cmp	CS:[CharCounter],CX	; See if beginning of line
		Jne	NotLineBegin		; If not, must be in middle

		Cmp	Byte Ptr DS:[SI],' '	; See if first char is blank
		Jne	ItsAFile		; If not, it's a file line

		Cmp	CS:[WithinFileList],1	; See if doing file listing
		Jne	AllowTransfer		; If not, just print stuff

		Call	SortAndList		; Files done -- sort and list
		Mov	CS:[WithinFileList],0	; Not doing files now
		Jmp	Short AllowTransfer	; So just print the stuff

ItsAFile:	Cmp	CS:[FileCounter],528	; See if 11 buffer filled up
		Jb	NotTooManyFiles		; If not just continue

		Push	CX			; Otherwise, save this register
		Call	SortAndList		; Print all up to now
		Mov	CS:[FileCounter],0	; Reset the counter
		Mov	DI,Offset FileBuffer	; And the pointer
		Mov	CS:[BufferPtr],DI	; Save the pointer
		Mov	CX,528 * 39		; Will clear for 528 files
		Mov	AL,' '			; With a blank
		Rep	Stosb			; Clear it out
		Pop	CX			; And get back register

NotTooManyFiles:Mov	CS:[WithinFileList],1	; We're doing files now
		Mov	CS:[NowDoingFile],1	; And a file in particular
		Inc	CS:[FileCounter]	; So kick up this counter

NotLineBegin:	Cmp	CS:[NowDoingFile],1	; See if doing files
		Je	StoreCharacters		; If so, store the stuff

AllowTransfer:	Pop	ES			; Pop all the registers
		Pop	DI
		Pop	SI
		Pop	CX
		Pop	AX
		PopF

		Jmp	SkipIntercept		; And go to DOS for print

StoreCharacters:Mov	DI,CS:[BufferPtr]	; Set destination
		Rep	Movsb			; Move characters to buffer
		Mov	CS:[BufferPtr],DI	; And save new pointer	

PopAndReturn:	Pop	ES			; Pop all the registers
		Pop	DI
		Pop	SI
		Pop	CX
		Pop	AX
		PopF

		Mov	AX,CX			; Set for COMMAND.COM
		Clc				; No error here 
		Ret	2			; Return with CY flag cleared

NewInterrupt21	EndP

;	Sort Files
;	----------

SortAndList:	Push	BX			; Push a bunch of registers
		Push	DX
		Push	SI
		Push	DS

		Push	CS			; Push CS
		Pop	DS			;    so we can set DS to it
		Assume	DS:CSEG			; And inform the assembler

		Mov	DI,Offset FileBuffer	; This is the beginning
		Mov	CX,[FileCounter]	; Number of files to sort
		Dec	CX			; Loop needs one less than that 
		Jcxz	AllSorted		; But zero means only one file

SortLoop1:	Push	CX			; Save the file counter
		Mov	SI,DI			; Set source to destination

SortLoop2:	Add	SI,39			; Set source to next file

		Push	CX			; Save the counter,
		Push	SI			;    compare source,
		Push	DI			;    and compare destination

		Mov	CX,39			; 39 characters to compare
		Repz	Cmpsb			; Do the compare
		Jae	NoSwitch		; Jump if already in order

		Pop	DI			; Get back these registers
		Pop	SI

		Push	SI			; And push them again for move
		Push	DI

		Mov	CX,39			; 39 characters
SwitchLoop:	Mov	AL,ES:[DI]		; Character from destination 
		Movsb				; Source to destination
		Mov	DS:[SI - 1],AL		; Character to source
		Loop	SwitchLoop		; For the rest of the line

NoSwitch:	Pop	DI			; Get back the registers
		Pop	SI
		Pop	CX
		Loop	SortLoop2		; And loop for next file

		Pop	CX			; Get back file counter 
		Add	DI,39			; Compare with next file
		Loop	SortLoop1		; And loop again

;	Now Display Sorted Files
;	------------------------

AllSorted:	Mov	SI,Offset FileBuffer	; This is the beginning
		Mov	CX,[FileCounter]	; Number of files to list
		Inc	CX			; In case CX is odd
		Shr	CX,1			; CX now is number of lines

SetIncrement:	Mov	BX,24 * 39		; Increment for double list
		Cmp	CX,24			; But use it only if a full
		Jae	LineLoop		;    screen is printed	

		Mov	AX,39			; Otherwise find increment
		Mul	CX			;    by multiplying CX by 39
		Mov	BX,AX			; And make that the increment

LineLoop:	Call	PrintFile		; Print the first column file
		Mov	AL,' '			; Skip one space
		Call	PrintChar		;    by printing blank
		Mov	AL,179			; Put a line down the middle
		Call	PrintChar
		Mov	AL,' '			; Skip another space
		Call	PrintChar

		Add	SI,BX			; Bump up source by increment
		Sub	SI,39 			; But kick down by 39

		Call	PrintFile		; Print the second column file
		Call	CRLF			; And terminate line

		Sub	SI,BX			; Bring pointer back down

		Inc	[LineCounter]		; One more line completed
		Cmp	[LineCounter],24	; Have we done whole screen?
		Jz	PauseAtEnd		; If so, gotta pause now

		Loop	LineLoop		; Otherwise just loop
		Jmp	Short AllFinished	; And jump out when done

PauseAtEnd:	Mov	[LineCounter],0		; Reset the counter
		Add	SI,BX			; Go to next file

		Push	BX			; Save these registers
		Push	CX
		Mov	DX,Offset PauseMessage	; Test to print
		Mov	CX,Offset PauseMsgEnd - Offset PauseMessage
						; Number of characters
		Mov	BX,2			; Standard ERROR Output
		Mov	AH,40h			; Display to screen
		Int	21h			; By calling DOS 
		Pop	CX			; Retrieve pushed registers 
		Pop	BX

		Mov	AH,8			; Wait for character
		Int	21h			; Through DOS call

		Call	CRLF			; Go to next line 

		Loop	SetIncrement		; And recalculate increment

AllFinished:	Pop	DS			; Done with subroutine
		Pop	SI
		Pop	DX
		Pop	BX
		Ret				; So return to caller

;	Display Routines
;	----------------

PrintChar:	Mov	DL,AL			; Print character in AL
		Mov	AH,2			; By simple DOS call
		Int	21h
		Ret				; And return

CRLF:		Mov	AL,13			; Print a carriage return
		Call	PrintChar
		Mov	AL,10			; And a line feed
		Call	PrintChar
		Ret				; And return

PrintString:	Lodsb				; Get character from SI
		Call	PrintChar		; Print it
		Loop	PrintString		; Do that CX times
		Ret				; And return

PrintFile:	Push	CX			; Save the counter
		Mov	CX,32			; Bytes for Name, Size, & Date
		Call	PrintString		; Print it	
		Inc	SI			; Skip one space before time
		Mov	CX,6			; Bytes for Time
		Call	PrintString		; It's a print!
		Pop	CX		
		Ret				; And return

FileBuffer	Label	Byte			; Points to end of code

CSEG		EndS				; End of segment

		End	Entry			; Denotes entry point
