;       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,Word Ptr[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
