Category : Files from Magazines
Archive   : VOL6N16.ZIP
Filename : RN.ASM

 
Output of file : RN.ASM contained in archive : VOL6N16.ZIP
; RN.ASM
; RN [d:][/I] /I option installs resident database

CODE SEGMENT ;*************************
ASSUME CS:CODE,DS:CODE ;* *
ORG 100H ;* REMEMBER TO EXE2BIN *
;* *
START: JMP BEGINNING ;*************************

; DATA AREA
; ---------
INSTALL_MSG DB 'RN sucessfully installed',13,10
COPYRIGHT DB 'Copyright 1987 Ziff-Davis Publishing Co.',13,10
PROGRAMMER DB 'Michael J. Mefford$',1AH
NO_FREE DB 'Too many resident programs$'
REQUIRES DB 'Requires DOS 2.x$'
NOT_ENOUGH DB 'Requires 128K free RAM$'
UNKNOWN DB 'Drive not supported$'
HEADING DB 'Directories of drive ',0
DIR_MSG DB ' directories',0
FILE_COUNT DB ' files in ',0
USING DB ' using ',0
LOADING DB 'Loading and sorting directories',13,10,0
RENAME_MSG DB 'Enter new name for ',0
PATH_LENGTH DB 'Path length is now ',0
DB '. Maximum is 63.',0
MKDIR_MSG DB 'Enter new subdirectory for ',0
RMDIR_MSG DB 'Warning: All files will be lost.',0
DB 'Do you still wish to remove it? Y/N ',0
HIDE_MSG DB 'H to hide or U to unhide ',0
READ_ONLY_MSG DB 'R to mark read-only U to undo read-only',0
ARCHIVE_MSG DB 'S to set R to reset archive bit',0
HAVE_BEEN DB 'have been marked ',0
DB 'not to be BACKUPed.',0
READ_MSG DB 'not read-only.',0
BS DB 8,32,8,0

DATA_INT DB 0
DATA_SEGMENT DW ?

DOS_VERSION DB ?
ROOT DB '\ (ROOT)'
ROOT_DIR DB '\',0
DOT_DOT DB '..',0
STAR_DOT_STAR DB '*.*',0
DEFAULT_DRIVE DB ?
DRIVE DB ?
ROOT_SECTORS DW ?
CLUST_RECORDS DW ?
CLUST_SECTORS DW ?
CLUST_BYTES DW ?
CLUST_COUNT DW ?
LAST_CLUSTER DW ?
LAST_ENTRY DW ?
END_OFFSET DW ?
STRIP_MASK DB ?
ADD_MASK DB ?

STATUS_REG DW ?
VIDEO_SEG DW 0B000H
NORMAL DB 07H
INVERSE DB 70H
BAR_ATTRIBUTE DB 70H
CURSOR_TYPE DW ?
CUR_OFFSET DW DIRECTORIES
CUR_RECORD DW ?
LINE DW 323
PAGE_END DW 21*160+323
DIR_COUNT DW 1
COUNT DW ?
BAR_START DW 323
BAR_LENGTH DW 8
SEARCH_COUNT DW ?
DIR_NAME DW ?

COMSPEC DB 'COMSPEC='
PARAMETER DB 7,'/C DR/O',13

ENVIRONMENT DW ?
COM_LINE_PTR DD OFFSET PARAMETER
STACK_SEG DW ?
STACK_PTR DW ?

DIR_RECORD EQU 40
DATA_RECORD EQU 20

ROOT_DIRECTORY EQU 0
FAT EQU ROOT_DIRECTORY
WORKSPACE EQU 65535-16384

DISPATCH_KEY DB 1,1CH,3BH,3CH,3DH,3EH,3FH,40H,41H,42H,43H
DB 44H,47H,48H,49H,4FH,50H,51H,76H,84H

DISPATCH_TABLE DW ESC_EXIT, CHANGE_DIR, CHANGE_DIR, RENAME, MKDIR
DW RMDIR, HIDE_UNHIDE, READ_ONLY, SET_RESET, REREAD
DW FILE_SUM, DR, HOME_BAR, UP_ARROW, PG_UP
DW END_BAR, DN_ARROW, PG_DN, BOTTOM_BAR, TOP_BAR

MENU LABEL BYTE
DB 201,20 DUP (205),187,186,' PC Magazine RN.COM ',186
DB 186,' (C) Copr. 1987 ZD ',186,186,' Michael J. Mefford ',186
DB 199,20 DUP (196),182,186,' F1 ChDir (or ',17,196,217,')',186
DB 186,' F2 Rename ',186,186,' F3 MkDir ',186
DB 186,' F4 RmDir ',186,186,' F5 Hide/Unhide ',186
DB 186,' F6 Do/Undo R-O ',186,186,' F7 Set/Reset Arc ',186
DB 186,' F8 Reread ',186,186,' F9 File count ',186
DB 186,' F10 DR ',186,186,' Esc to Exit ',186
DB 200,20 DUP (205),188,' Use: ',24,32,25,' PgUp PgDn '
DB ' ^PgUp ^PgDn Home End '

;---------------------------------------------------------------------;
; Store our data segment. We will be writing directly to the screen ;
; buffer so we need the display card address and the status register. ;
;---------------------------------------------------------------------;

; CODE AREA
; ---------
BEGINNING: CLD ;String moves in forward direction.
PUSH DS
POP DATA_SEGMENT ;Store our data segment.
MOV AX,3301H ;Turn Control Break off.
MOV DL,0
INT 21H

MOV AX,40H ;Point to the ROM BIOS data area
MOV DS,AX ; and get base address of active
MOV AX,DS:[63H] ; display card.
ADD AX,6 ;Add six to get status register
PUSH CS ;Done there, so restore data segment.
POP DS
MOV STATUS_REG,AX ;Store status register.
CMP AX,3BAH ;Status port of MONO card is 3BAh.
JZ MONO ;If that's what we got, it's MONO
MOV VIDEO_SEG,0B800H ; else COLOR so add 800h.
XOR BH,BH ;Get current attribute
MOV AH,8 ; of display page zero.
INT 10H
MOV NORMAL,AH ;Store it.
XOR AH,1110111B ;Flip color bits.
MOV INVERSE,AH ;Save it.
MOV BAR_ATTRIBUTE,AH
MONO: XOR BH,BH
MOV AH,3 ;Retrieve cursor type.
INT 10H
MOV CURSOR_TYPE,CX ;And save it.
CALL CURSOR_OFF

;---------------------------------------------;
; Check DOS version and allocate 128K memory. ;
;---------------------------------------------;

MOV AH,30H ;Get DOS version.
INT 21H
MOV DOS_VERSION,AL ;And save.
CMP AL,2 ;Is it 2.x or above?
JAE CONTINUE ;If yes, continue.

MOV DX,OFFSET REQUIRES ;Else, exit with message.
QUIT: JMP MSG_EXIT

CONTINUE: CMP AL,3 ;Is it DOS 3.x or above?
JAE RENAME_OK ;If yes continue.
MOV BYTE PTR MENU+(22*6)+14,'N' ;Else mark rename function
MOV BYTE PTR MENU+(22*6)+15,'/' ; Not/Available.
MOV BYTE PTR MENU+(22*6)+16,'A'
RENAME_OK: MOV AH,49H ;Return our memory segment.
INT 21H
MOV BX,2000H ;Request 128K.
MOV AH,4AH
INT 21H
MOV DX,OFFSET NOT_ENOUGH ;If not available, exit
JC QUIT ; with message.

MOV AH,19H ;Get default drive and save.
INT 21H
MOV DEFAULT_DRIVE,AL

;-----------------------------------------------------;
; Check for drive request. Retrieve Disk Block info. ;
;-----------------------------------------------------;

MOV AL,DS:[5CH] ;Get drive request.
MOV BL,AL ;Save it.
DEC AL ;Adjust.
OR BL,BL ;If it was zero, no request.
JNZ STORE_DRIVE

MOV AH,19H ;Then get default drive.
INT 21H
STORE_DRIVE: MOV DRIVE,AL ;Store it.
MOV DL,AL
MOV AH,0EH ;Change drive to requested.
INT 21H

MOV DL,DRIVE ;Retrieve drive.
INC DL ;Adjust.
MOV AH,32H ;Retrieve Disk Block.
INT 21H
OR AL,AL ;Does disk request exist?
JZ EXISTS
JMP DISK_EXIT ;If no, exit with message.
EXISTS: MOV SI,BX ;Store Disk Block.
MOV DI,OFFSET DISK_BLOCK
MOV CX,18
REP MOVSB
PUSH CS ;Restore data segment.
POP DS

CMP DRIVE,1 ;Is drive request A: or B:?
JA CK_FREE
JMP READ ;If yes, automatic read.

;---------------------------------------------------;
; Check to see if our database has been installed. ;
;---------------------------------------------------;

CK_FREE: MOV AL,60H - 1 ;Available vectors are 60H - 67H.
FREE_USER_INT: INC AL
MOV AH,35H ;Get vector address.
INT 21H
CMP BX,0 ;Is offset being used?
JNZ CK_SIGNATURE ;If yes, see if it's us.
MOV DX,ES
CMP DX,0 ;Is segment being used?
JNZ CK_SIGNATURE ;If yes, see if it's us.
MOV DATA_INT,AL ;If available, save INT number.
JMP SHORT NEXT_USER ;Check all 7.

CK_SIGNATURE: MOV DI,BX ;See if INT has our signature.
MOV SI,OFFSET INSTALL_MSG
MOV CX,30/2
REPZ CMPSW
JZ CK_SUBST ;If yes, already installed.

NEXT_USER: CMP AL,67H ;Have we checked all 7?
JNZ FREE_USER_INT ;If no, next one.
CMP DATA_INT,0 ;Did we find one that was free?
JNZ CK_SWITCH
MOV DX,OFFSET NO_FREE ;If no, exit with message.
JMP ERROR_EXIT

CK_SWITCH: MOV SI,80H ;See if request to install.
LODSB
CMP AL,0 ;If no parameters, read data.
JZ READ
NEXT_SWITCH: LODSB
CMP AL,13 ;End of parameters?
JZ READ ;If yes, read data.
CMP AL,'/' ;Switch character?
JNZ NEXT_SWITCH
LODSB
AND AL,5FH
CMP AL,'I' ;If yes, is it Install switch?
JNZ READ

INSTALL: MOV DX,103H ;If yes, point to signature.
MOV AL,DATA_INT ;Retrieve free INT number.
MOV AH,25H ;And install.
INT 21H
CALL FIRST_READ ;Read disk data.
CALL CLS
MOV DX,OFFSET INSTALL_MSG ;Display install message.
MOV AH,9
INT 21H
CALL RESTORE_DRIVE ;Restore defaults.
CALL CURSOR_ON
MOV DX,OFFSET END_RESIDENT
INT 27H ;Terminate and stay resident.

;-----------------------------------------------------;
; Check to see if drive request is a substitute. ;
; Also if different from current resident drive data. ;
;-----------------------------------------------------;

CK_SUBST: MOV DATA_SEGMENT,ES ;Save the location of disk data.
PUSH ES:CLUST_BYTES ;Retrieve from resident database
POP CLUST_BYTES ; cluster size in bytes.
MOV AL,DRIVE
MOV DX,OFFSET UNKNOWN ;Is it a substituted drive?
CMP AL,BYTE PTR DISK_BLOCK
JZ CK_NEW_DRIVE ;If yes, exit with message.
JMP DISK_EXIT

CK_NEW_DRIVE: CMP AL,ES:DRIVE ;Is it a new drive request?
JZ FORMAT ;If no, format resident data.
MOV ES:DRIVE,AL ;Else, save new drive.

;--------------------------------------------------------------------;
; Read disk directory data, if necessary. Format it into a tree. ;
; Mark the current default directory. Clear the screen and display. ;
;--------------------------------------------------------------------;

READ: CALL FIRST_READ
FORMAT: CALL FORMAT_IT
CALL GET_CURRENT
CALL CLS
CALL START_DISPLAY

;-----------------------------------------;
; We are now ready for business. We will ;
; loop here, waiting for user keystokes. ;
;-----------------------------------------;

GET_KEY: CALL UPDATE_SCREEN
CALL READ_KEY ;Get a keystroke.
MOV BX,AX ;Save returned key.
CMP AH,1 ;Is it Esc or below?
JBE FUNCTION ;If yes, function.
CMP AH,36H ;Is it right shift or above?
JAE FUNCTION ;If yes, function.
CMP AH,1CH ;Is it CR?
JZ FUNCTION ;If yes, function.
CALL SEARCH ;Else, search request.
JMP SHORT GET_KEY
FUNCTION: MOV DI,OFFSET DISPATCH_KEY
MOV AL,BH
MOV CX,20 ;20 valid commands.
REPNZ SCASB
JNZ GET_KEY ;If no match, get another.
MOV DI,OFFSET DISPATCH_TABLE+38
SHL CX,1
SUB DI,CX
CALL DS:[DI] ;Else, do subroutine.
JMP SHORT GET_KEY ;Update screen; get next command.

;-------------------------------------------------------------------;
; This is the exit routine. Restore defaults the way we found them. ;
;-------------------------------------------------------------------;

ESC_EXIT: MOV DX,OFFSET CURRENT_DIR
CALL CD
EXIT: CALL RESTORE_DRIVE
CALL CLS
CALL CURSOR_ON
XOR DX,DX ;Home cursor.
CALL SET_CURSOR
INT 20H ;And terminate.

;*************;
; SUBROUTINES ;
;*************;

;------------------------------------------------------------------------;
; This subroutine searches for a directory with a specific first letter. ;
;------------------------------------------------------------------------;

SEARCH: CMP BL,'a' ;Capitalize if lower case.
JB SEARCH_IT
CMP BL,'z'
JA SEARCH_IT
AND BL,5FH
SEARCH_IT: CMP BL,'\' ;Searching for root?
JNZ SEARCH_NAME
CALL HOME_BAR ;If yes, home the bar.
RET

SEARCH_NAME: CALL GET_NAME ;Get current position.
XOR DX,DX ;Zero out file counter.
MOV DI,DIR_NAME ;Store current position in DI.
MOV SI,OFFSET DIRECTORIES ;Point to top of listing.
CMP BYTE PTR [DI],BL ;Are we currently at a match?
JNZ INC_SEARCH ;If no, start from top.
FIND_START: INC DX ;Increment count.
ADD SI,DIR_RECORD ;Increment record.
CMP SI,END_OFFSET ;End of tree?
JAE SEARCH_BEEP ;If yes, no match.
CMP SI,CUR_RECORD ;New record?
JBE FIND_START ;If no, find it.

NEXT_SEARCH: XOR BP,BP ;Zero in directory level pointer.
FIND_DASH: INC BP
CMP BYTE PTR [SI+BP-1],196 ;Is it the dash?
JNZ FIND_DASH ;If no, find it.

CMP BYTE PTR [SI+BP],BL ;Got a match?
JZ FOUND_IT ;If yes, process.
INC_SEARCH: ADD SI,DIR_RECORD ;Else, point to next record.
INC DX
CMP SI,END_OFFSET ;End of listing?
JB NEXT_SEARCH ;If no, keep searching.
SEARCH_BEEP: CALL BEEP ;No matches, so beep.
RET

FOUND_IT: MOV CX,DIR_COUNT ;Retrieve directory count.
SUB CX,DX ;Subtract search count.
MOV SEARCH_COUNT,CX ;And store.
MOV CL,NORMAL ;Turn off bar for now.
MOV BAR_ATTRIBUTE,CL
CALL END_BAR ;First move to end.
JMP SHORT MARK_IT
NEXT_UP: CALL UP_ARROW ;Move up to matching filename.
MARK_IT: DEC SEARCH_COUNT
JNZ NEXT_UP
MOV CL,INVERSE ;Turn bar back on and display.
MOV BAR_ATTRIBUTE,CL
XOR BP,BP
CALL SCROLL_BAR
RET

;-------------------------------------------------------------------------;
; This subroutine changes the directory to the one highlighted and exits. ;
;-------------------------------------------------------------------------;

CHANGE_DIR: CALL GET_NAME
CALL NEW_DIR
JMP EXIT ;Exit.

;----------------------------------------------------;
; This subroutine renames the highlighted directory. ;
;----------------------------------------------------;

RENAME: CMP DOS_VERSION,3 ;Can't rename directories before
JB RENAME_ERROR ; DOS 3.x
CALL GET_NAME
CMP SI,OFFSET DIRECTORIES ;Can't rename the root directory.
JZ RENAME_ERROR
CALL NEW_DIR ;Change to the requested directory.
JC RENAME_READ ;If nonexistant, reread data.

MOV DX,162CH ;Row 22, column 44.
MOV SI,OFFSET RENAME_MSG ;Display rename message.
CALL QUERY ;Get new name from user.
JC RENAME_END ;If carry, Esc was pressed.

MOV DX,OFFSET DOT_DOT ;Change to parent directory.
CALL CD
MOV DX,DIR_NAME ;Point to old name.
MOV DI,80H
MOV AH,56H ;Rename.
INT 21H
JC RENAME_ERROR ;Beep if illegal.
RENAME_READ: CALL REREAD ;Reread data.
RENAME_END: CALL CLEAR_MSG
RET

RENAME_ERROR: CALL BEEP ;Error exit.
CALL CLEAR_MSG
RET

;-----------------------------------------------------------------------;
; This subroutine makes a subdirectory under the highlighted directory. ;
;-----------------------------------------------------------------------;

MKDIR: CALL GET_NAME
CALL NEW_DIR ;Change to the requested directory.
JC MKDIR_READ ;If nonexistant, reread data.
MOV DX,1629H ;Row 22; column 41.
MOV SI,OFFSET PATH_LENGTH ;Display path message.
CALL DISPLAY_TEXT
CALL GET_COUNT ;And the path length.
CALL GET_TEXT ;And end of message.
CMP COUNT,63 ;Is it already 63 or more chars?
JB MKDIR_QUERY
CALL BEEP ;If yes, refuse to make directory.
CALL DELAY
JMP SHORT MKDIR_END

MKDIR_QUERY: MOV DX,1729H ;Point to next row.
CALL QUERY ;Get name from user.
JC MKDIR_END ;If carry, Esc was pressed.
MOV DX,80H
MOV AH,39H ;Create the directory.
INT 21H
JC MKDIR_ERROR ;Beep if illegal.
MKDIR_READ: CALL REREAD ;Reread data.

MKDIR_END: CALL CLEAR_MSG
RET

MKDIR_ERROR: CALL BEEP ;Error exit.
CALL CLEAR_MSG
RET

;-------------------------------------------------------;
; This subroutine removes the highlighted subdirectory. ;
;-------------------------------------------------------;

RMDIR: CALL GET_NAME
MOV SI,DIR_NAME
CMP SI,OFFSET DIRECTORIES ;Can't remove root directory.
JZ RMDIR_ERROR
CMP BYTE PTR [SI+DIR_RECORD+1],196 ;Can't remove directory
JZ RMDIR_ERROR ; with a subdirectory.
CALL NEW_DIR ;Change to requested directory.
JC RMDIR_READ ;If nonexistant, reread data.
CALL COUNT_THEM ;Count and display file count.
CMP COUNT,0 ;If empty, remove directory.
JZ REMOVE

MOV DX,1729H ;Else, display file loss Warning.
MOV SI,OFFSET RMDIR_MSG
CALL DISPLAY_TEXT
MOV DX,1829H
CALL DISPLAY_TEXT
CALL CURSOR_ON
CALL READ_KEY ;Get response.
CMP AH,15H ;If not "Y", exit.
JNZ RMDIR_RETURN
CALL WRITE_TEXT ;Else, display it.
CALL READ_KEY ;Get next response.
CMP AH,1CH ;If not carriage return, exit.
JNZ RMDIR_RETURN

CALL FIRST_MATCH ;Else, find all files and delete.
CALL DELETE
NEXT_DELETE: CALL NEXT_MATCHING
JC REMOVE
CALL DELETE
JMP SHORT NEXT_DELETE

REMOVE: MOV DX,OFFSET DOT_DOT ;Move to parent directory.
CALL CD
MOV DX,DIR_NAME
MOV AH,3AH ;Remove requested directory.
INT 21H
JC RMDIR_ERROR
RMDIR_READ: CALL REREAD ;Reread data.
RMDIR_RETURN: CALL CLEAR_MSG
RET

RMDIR_ERROR: CALL BEEP ;Error exit.
CALL CLEAR_MSG
RET

;------------------------------------------------------------------;
; These two subroutines hide and unhide the highlighted directory. ;
;------------------------------------------------------------------;

HIDE_UNHIDE: CALL GET_NAME
CMP SI,OFFSET DIRECTORIES ;Can't hide root directory.
JZ HIDE_ERROR
MOV DX,1629H ;Row 22; column 41.
MOV SI,OFFSET HIDE_MSG ;Display hide message.
CALL DISPLAY_TEXT
MOV SI,DIR_NAME
CALL GET_TEXT
MOV BP,2 ;Hidden attribute.
CALL READ_KEY ;Get a keystroke.
CMP AH,23H ;Is it "H"?
JZ CHANGE_IT ;If yes, change attribute.
XOR BP,BP ;Turn off hidden attribute.
CMP AH,16H ;Is it "U"?
JNZ HIDE_END ;If no, exit.

CHANGE_IT: CALL NEW_DIR
JC HIDE_READ ;If directory nonexistant, read.
MOV DX,OFFSET DOT_DOT ;Change to parent directory.
CALL CD
MOV DX,DIR_NAME
MOV CX,BP ;Retrieve requested attribute.
CALL CHMOD ;And change it.
HIDE_READ: CALL REREAD
RET

HIDE_ERROR: CALL BEEP
HIDE_END: CALL CLEAR_MSG
RET

;--------------------------------------------------------;
; This subroutine sets or resets the read-only attribute ;
; of all the files of the highlighted directory. ;
;--------------------------------------------------------;

READ_ONLY: CALL GET_NAME
CALL NEW_DIR
JC SET_READ ;If nonexistant directory, reread.
CALL COUNT_THEM ;Display file count.
CMP COUNT,0 ;Is directory empty?
JZ READ_END ;If yes, nothing to mark.
MOV DX,1729H ;Row 23; column 41.
MOV SI,OFFSET READ_ONLY_MSG ;Display read-only message.
CALL DISPLAY_TEXT
MOV STRIP_MASK,11111111B ;If read-only request, nothing
MOV ADD_MASK, 00000001B ; to strip and add bit 1.
CALL READ_KEY
CMP AH,13H ;Is it "R"?
JZ DO_READ ;If yes, turn on read-only bit.
MOV STRIP_MASK,11111110B ;If NOT read-only request, strip
MOV ADD_MASK,0 ; read-only bit and add nothing.
CMP AH,16H ;Is it "U"?
JNZ READ_RETURN ;If no, done here.

DO_READ: CALL DO_ATTRIB ;Change the attribute.
CALL CLEAR_LINE ;Clear keystroke prompt line.
MOV DX,1729H ;Row 23; column 41.
MOV SI,OFFSET HAVE_BEEN ;Display "have been" message.
CALL DISPLAY_TEXT
MOV SI,OFFSET READ_MSG ;Display rest of message.
JMP SHORT SET_MSG

READ_RETURN: CALL CLEAR_MSG
RET

READ_END: CALL DELAY
RET

SET_READ: CALL REREAD
RET

;------------------------------------------------------;
; These two subroutines set and reset the archive bit. ;
;------------------------------------------------------;

SET_RESET: CALL GET_NAME
CALL NEW_DIR
JC SET_READ ;If nonexistant directory, reread.
CALL COUNT_THEM ;Display file count.
CMP COUNT,0 ;Is directory empty?
JZ SET_END ;If yes, nothing to mark.
MOV DX,1729H ;Row 23; column 41.
MOV SI,OFFSET ARCHIVE_MSG ;Display archive message.
CALL DISPLAY_TEXT
MOV STRIP_MASK,11111111B ;If set archive request, nothing
MOV ADD_MASK, 00100000B ; to strip and add bit 1.
CALL READ_KEY
CMP AH,1FH ;Is it "S"?
JZ DO_ARCHIVE ;If yes, turn on archive bit.
MOV STRIP_MASK,11011111B ;If NOT archive request, strip
MOV ADD_MASK,0 ; archive bit and add nothing.
CMP AH,13H ;Is it "R"?
JNZ READ_RETURN ;If no, done here.

DO_ARCHIVE: CALL DO_ATTRIB ;Change the attribute.
CALL CLEAR_LINE ;Clear keystroke prompt line.
MOV DX,1729H ;Row 23; column 41.
MOV SI,OFFSET HAVE_BEEN ;Display "have been" message.
CALL DISPLAY_TEXT

SET_MSG: CMP ADD_MASK,0 ;If add nothing, display strip
JZ DISPLAY_MSG ; message.
ADD SI,4 ;Else, display bit on message.
DISPLAY_MSG: CALL GET_TEXT
SET_END: CALL DELAY ;Wait for keystroke, then remove
RET ; message.

;-----------------------------------------------;
; This subroutine displays the file count and ;
; displacement of the highlighted subdirectory. ;
;-----------------------------------------------;

FILE_SUM: CALL GET_NAME
CALL NEW_DIR ;Change to requested directory.
JC REREAD ;If nonexistant, reread data.
CALL COUNT_THEM ;Get file count and displacement.
CALL DELAY ;Display count until keystroke.
RET

;-------------------------------------------------------------------;
; This subroutine rereads the directory tree information from disk. ;
;-------------------------------------------------------------------;

REREAD: MOV PAGE_END,21*160+323 ;Reset default values.
MOV DIR_COUNT,1
CALL CLEAR_MSG ;Clear the last message.
MOV DX,162DH ;Row 22, column 45.
MOV SI,OFFSET LOADING ;Display loading message.
CALL DISPLAY_TEXT
CALL READ_DATA ;Read the data.
CALL FORMAT_IT ;Construct new tree.
CALL START_DISPLAY ;Display menu.
CALL CLEAR_MSG ;Clear sorting message.
XOR BP,BP ;Update directory display.
CALL SCROLL
MOV SI,PAGE_END ;Move bar if below directory
SUB SI,160 ; display.
CMP SI,LINE
JA REREAD_END
CALL MOVE_BAR
REREAD_END: RET

;----------------------------;
; This subroutine spawns DR. ;
;----------------------------;

DR: CALL GET_NAME
CALL NEW_DIR ;Change the default directory.
MOV BX,1000H ;Shrink memory down to one segment.
MOV AH,4AH
INT 21H

PUSH DS ;Save segment registers.
PUSH ES
CALL CLS ;Clear screen.
CLI
MOV STACK_SEG,SS ;Save stack segment and pointer.
MOV STACK_PTR,SP
STI

MOV WORD PTR COM_LINE_PTR + 2,DS
MOV AX,DS:[2CH] ;Retrieve environment segment.
MOV ENVIRONMENT,AX ;And put in paramter block.
MOV DS,AX ;And data segment.

XOR AX,AX ;Zero out pointer.
FIND_COMSPEC: MOV SI,AX ;Point to environment offset.
INC AX ;Next offset.
MOV DI,OFFSET COMSPEC ;Find "Comspec=".
MOV CX,8
REP CMPSB
JNZ FIND_COMSPEC
MOV DX,SI ;What follows is Command.com path.
MOV BX,OFFSET ENVIRONMENT ;Point to parameter block.
MOV AX,4B00H ;Execute.
INT 21H

CLI
MOV SP,CS:STACK_PTR ;Restore stack segment and pointer.
MOV SS,CS:STACK_SEG
STI
POP ES ;Restore segment registers.
POP DS

MOV DX,80H ;Restore Disk Transfer Address.
MOV AH,1AH
INT 21H
MOV BX,2000H ;Restore dual memory segments.
MOV AH,4AH
INT 21H
CALL CLS ;Clear screen.
CALL START_DISPLAY ;Restore menu.
RET

;--------------------------------------------------------------------------;
; These six subroutines control the bar and page of the directory listing. ;
;--------------------------------------------------------------------------;

UP_ARROW: MOV BP,-160 ;Move bar up one line.
JMP SHORT BAR_MOVE

DN_ARROW: MOV BP,160 ;Move bar down one line.
BAR_MOVE: CALL SCROLL_BAR
RET

PG_UP: MOV BP,-21*40 ;Move up 21 lines.
CALL SCROLL
JMP SHORT BOTTOM_BAR

PG_DN: MOV BP,21*40 ;Move down 21 lines.
MOVE_PAGE: CALL SCROLL
JMP SHORT TOP_BAR ;Move bar to top.

HOME_BAR: MOV CUR_OFFSET,OFFSET DIRECTORIES ;Move listing to beginning.
TOP_BAR: MOV SI,323 ;And move bar to top.
CALL MOVE_BAR
RET

END_BAR: MOV BX,END_OFFSET ;Move listing to last page.
SUB BX,21*DIR_RECORD
CMP BX,OFFSET DIRECTORIES
JBE BOTTOM_BAR
MOV CUR_OFFSET,BX
BOTTOM_BAR: MOV SI,PAGE_END ;And move bar to bottom.
SUB SI,160
CALL MOVE_BAR
RET

;----------------------------------------------;
; These subroutines support the ten functions. ;
;----------------------------------------------;

CHMOD: MOV AX,4301H
INT 21H
RET

;--------------------------------------;

DELAY: MOV AH,1
INT 16H ;Loop here until keystroke.
JZ DELAY
CALL CLEAR_MSG
RET

;-------------------------------------;

DELETE: MOV DX,80H+30 ;Point to filename.
MOV AH,41H
INT 21H
RET

;--------------------------------------;

DO_ATTRIB: CALL FIRST_MATCH ;Find first file.
NEXT_SET: MOV CL,DS:[80H+21] ;Get old attribute.
AND CL,STRIP_MASK ;Strip request.
OR CL,ADD_MASK ;Add request.
XOR CH,CH ;Zero in high half.
MOV DX,80H+30 ;Point to filename.
CALL CHMOD ;And change the attribute.
CALL NEXT_MATCHING ;Do it to all files.
JNC NEXT_SET
RET

;--------------------------------------;

FIRST_MATCH: MOV DX,OFFSET STAR_DOT_STAR
MOV CX,7
MOV AH,4EH
INT 21H
RET

NEXT_MATCHING: MOV AH,4FH
INT 21H
RET

;------------------------------------------------------;
; This subroutine gets a directory name from the user. ;
;------------------------------------------------------;

QUERY: CALL DISPLAY_TEXT ;Display caller's message.
MOV SI,DIR_NAME ;Add directory name.
CALL GET_TEXT
INC DH ;Next row.
CALL SET_CURSOR ;Move cursor.
CALL CURSOR_ON ;Turn it on.
MOV DI,80H ;Write nulls over old entry.
XOR AX,AX
MOV CX,18
REP STOSW
MOV DI,80H ;Initiate pointer for entry.

GET_QUERY: CALL READ_KEY ;Get a character.
CMP AL,27 ;Is it Esc?
JZ QUERY_DONT ;Is yes, exit subroutine.
CMP AL,':' ;Ignore ":\?*".
JZ GET_QUERY
CMP AL,'\'
JZ GET_QUERY
CMP AL,'?'
JZ GET_QUERY
CMP AL,'*'
JZ GET_QUERY
CMP AL,13 ;Is it carriage return?
JZ QUERY_DO ;If yes, return, do request.
CMP AL,8 ;Is it backspace?
JNZ NOT_BS ;If no, skip.
CMP DI,80H ;At beginning of field?
JZ GET_QUERY ;If yes, skip.
DEC DI ;Else, decrement pointer.
MOV SI,OFFSET BS ;Erase last character.
CALL GET_TEXT
JMP SHORT GET_QUERY
NOT_BS: CMP AL,32 ;Is it above space?
JBE GET_QUERY ;If no, ignore.
CMP DI,8CH ;End of entry field?
JZ GET_QUERY ;If yes, ignore.
STOSB ;Else, store byte.
CALL WRITE_TEXT ;And write it to screen.
JMP SHORT GET_QUERY ;Get next keystroke.

QUERY_DO: MOV BYTE PTR DS:[DI],0 ;Convert to ASCIIZ.
CLC
RET

QUERY_DONT: STC ;Indicate Esc was pressed.
RET

;--------------------------------------------;
; This subroutine gets a file count and ;
; displacement of the highlighted directory. ;
;--------------------------------------------;

COUNT_THEM: MOV COUNT,0 ;Zero out file counter.
MOV CLUST_COUNT,0 ;And cluster counter.
CALL FIRST_MATCH ;Get first file.
JC DISPLAY_COUNT

FIND_NEXT: INC COUNT ;Increment file count.
MOV AX,DS:[80H+26] ;Retrieve low word
MOV DX,DS:[80H+28] ; and high word of file size.
DIV CLUST_BYTES ;Divide to get cluster count.
OR DX,DX ;If remainder, increment count.
JZ NO_REMAINDER
INC CLUST_COUNT
NO_REMAINDER: ADD CLUST_COUNT,AX ;Add cluster count.
CALL NEXT_MATCHING ;Find rest of files.
JNC FIND_NEXT

DISPLAY_COUNT: MOV DX,1629H ;Row 22; column 41.
CALL SET_CURSOR
CALL GET_COUNT ;Display count.
MOV SI,OFFSET FILE_COUNT ;And message.
CALL GET_TEXT
MOV SI,DIR_NAME ;Display parent directory.
CALL GET_TEXT
MOV SI,OFFSET USING ;Display rest of message.
CALL GET_TEXT
MOV AX,CLUST_COUNT ;Retrieve cluster count.
XOR DX,DX ;Zero in high half.
MOV BX,CLUST_BYTES
MUL BX ;Multiply to get total bytes.
MOV BX,1024
DIV BX ;Divide by one K bytes.
MOV COUNT,AX
CALL GET_COUNT ;Convert count to ASCII.
MOV AL,'K' ;Tack on "K".
CALL WRITE_TEXT
RET

;------------------------------------------------------;
; This subroutine displays the loading message and ;
; reads the disk on reads requested from command line. ;
;------------------------------------------------------;

FIRST_READ: CALL CLS
MOV DX,0C18H
MOV SI,OFFSET LOADING
CALL DISPLAY_TEXT

;-------------------------------------------------------------------;
; This subroutine reads the root directory and FAT from disk. ;
; Then finds subdirectories stacking them in the database one level ;
; at a time. Finally, the database is alphabetized by level. ;
;-------------------------------------------------------------------;

READ_DATA: MOV AX,DISK_BLOCK[2] ;Retrieve bytes per sector.
MOV BL,BYTE PTR DISK_BLOCK[4] ;And sectors per cluster -1.
INC BL ;Adjust.
XOR BH,BH ;Zero high byte.
MOV CLUST_SECTORS,BX ;And save.
MUL BX ;Get total bytes per cluster.
MOV CLUST_BYTES,AX ;And save.
MOV CX,32 ;Divide by bytes per directory.
DIV CX
MOV CLUST_RECORDS,AX ;And save.

GET_ROOT: MOV AX,DISK_BLOCK[9] ;Retrieve number of root entries.
MOV CX,32 ;Times 32 byte record length.
MUL CX
JNC NO_ERROR ;Problem if carry.
DISK_EXIT: MOV DX,OFFSET UNKNOWN ;Display message and exit.
ERROR_EXIT: CALL RESTORE_DRIVE
MSG_EXIT: MOV AH,9
INT 21H
CALL CURSOR_ON
INT 20H

NO_ERROR: MOV BX,DISK_BLOCK[2] ;Divide by bytes per sector.
DIV BX
MOV ROOT_SECTORS,AX ;That's root length in sectors.
MOV CX,AX ;Root length.
MOV DX,DISK_BLOCK[16] ;Directory starting sector.
MOV BX,OFFSET ROOT_DIRECTORY ;Read the root directory.
CALL READ_DISK

MOV AX,DATA_SEGMENT ;Get data storage segment.
MOV ES,AX
XOR BP,BP ;Tree level counter.
XOR BX,BX ;Entry number counter.
MOV SI,OFFSET ROOT_DIRECTORY ;Point to root directory.
MOV DI,OFFSET DATA_BUFFER ;Point to database storage.
MOV BYTE PTR ES:[DI],0FFH ;Initialize with end signature.
MOV ES:LEVEL_ADDRESS,DI ;Store starting level address.
MOV CX,DISK_BLOCK[9] ;Retrieve no. root directories.
CALL STORE_RECORD ;Store them in database.
CMP BX,0 ;BX returns with no. entries.
JZ DATA_END ;If none, done here.

GET_FAT: PUSH BX ;Save entry count.
MOV AL,BYTE PTR DISK_BLOCK[15] ;Retrieve sectors for FAT.
XOR AH,AH ;Zero in high byte.

MOV CX,AX ;Save.
MOV BX,DISK_BLOCK[2] ;Retrieve byte per sector.
MUL BX ;Multiply to get total bytes.
JC DISK_EXIT ;Problem if carry.
CMP AX,65535-8192 ;Too big if over 56K.
JA DISK_EXIT ;Display message and exit.
MOV DX,DISK_BLOCK[6] ;Retrieve reserved sectors.
MOV BX,OFFSET FAT ;Read the FAT.
CALL READ_DISK
JC ERROR_EXIT ;Exit if problem.

POP BX ;Retrieve directory count.
MOV AX,DATA_SEGMENT ;Point to database segment.
MOV ES,AX
MOV SI,OFFSET DATA_BUFFER ;And point to database offset.
UP_LEVEL: INC BP ;Bump point to next level.
INC BP
MOV ES:[OFFSET LEVEL_ADDRESS+BP],DI ;Save level address.
MOV CX,BX ;Retrieve record count.
XOR BX,BX ;Zero out entry counter.
NEXT_SECTOR: MOV DX,ES:[SI+16] ;Retrieve starting cluster.

NEXT_CLUSTER: MOV LAST_CLUSTER,DX ;And save.
PUSH CX ;Save some registers.
PUSH BX
CALL READ_CLUSTER ;Retrieve directory sector.
POP BX
MOV DX,ES:[SI+1] ;Retrieve parent entry number.
PUSH SI
MOV SI,OFFSET WORKSPACE ;Point to new directory.
MOV CX,CLUST_RECORDS ;Retrieve records per cluster.
CALL STORE_RECORD ;Store subdirectories in database.
POP SI
POP CX
JC END_LEVEL ;If carry, last entry found.
CALL CK_FAT ;Else, get next cluster.
JNC NEXT_CLUSTER ;If carry, last cluster found.

END_LEVEL: ADD SI,DATA_RECORD ;Else, point to next root entry.
LOOP NEXT_SECTOR ;Find next subdirectory.
CMP SI,DI ;Did we find any at this level?
JNZ UP_LEVEL ;If yes, continue.
MOV AX,0FFFFH ;Else, done; mark with signature.
STOSB
MOV ES:[OFFSET LEVEL_ADDRESS+BP+2],AX ;Mark level address.
CALL SORT ;Alphabetize by level.
DATA_END: PUSH CS ;Restore segment registers.
PUSH CS
POP DS
POP ES
RET

;-------------------------------------------------;
; This subroutine does the direct sector reading. ;
;-------------------------------------------------;

READ_DISK: PUSH BP ;Call destroys all registers
PUSH SI ; except segment registers
PUSH DI ; so we have to preserve.
PUSH DS

MOV AL,DRIVE ;Retrieve drive.
MOV SI,DS ;Point to second segment.
ADD SI,1000H
MOV DS,SI
INT 25H ;Read the sectors.
POP AX ;Get rid off flags left on stack.

POP DS ;Restore registers.
POP DI
POP SI
POP BP
RET

;----------------------------------------;
; This subroutine will read CX clusters. ;
;----------------------------------------;

READ_CLUSTER: MOV AX,LAST_CLUSTER ;Retrieve cluster number.
MOV CX,CLUST_SECTORS ;Retrieve sectors per cluster.
MUL CX ;Multiply to get sector start.
MOV DX,AX
ADD DX,DISK_BLOCK[11] ;Add start of first data cluster.
SUB DX,CX ;Subtract to reflect cluster
SUB DX,CX ; 2 as first logical data sector.
MOV BX,OFFSET WORKSPACE ;Point to workspace.
CALL READ_DISK ;And get data.
RET

;------------------------------------------------------------------;
; This subroutine stores the level number, entry number, parent ;
; entry number, the directory name and attribute byte in database. ;
;------------------------------------------------------------------;

STORE_RECORD: PUSH DS ;Point to second segment.
MOV AX,CS
ADD AX,1000H
MOV DS,AX

NEXT_RECORD: CMP BYTE PTR [SI],0 ;No more entries?
JZ STORE_RETURN ;If yes, done here.
TEST BYTE PTR [SI+11],10H ;Is it a directory?
JZ LOOP_STORE ;If no, next entry.
CMP BYTE PTR [SI],0E5H ;Is it a removed directory?
JZ LOOP_STORE ;If yes, skip.
CMP BYTE PTR [SI],'.' ;Is it a dot or dot-dot entry?
JZ LOOP_STORE ;If yes, skip.

PUSH SI
MOV AX,BP ;Store level number.
STOSB
MOV AX,BX ;Store entry number.
STOSW
MOV AX,DX ;Store parent entry number.
STOSW
PUSH CX
MOV CX,11 ;Store 11 bytes of name.
REP MOVSB
POP CX
ADD SI,15 ;Store starting cluster.
MOVSW
INC BX ;Increment entry count.
POP SI
XOR AX,AX ;Assume not a hidden directory.
TEST BYTE PTR [SI+11],2 ;Is it hidden?
JZ STORE_ATTRIB ;If no, store null.
MOV AX,'H' ;Else, store "H".
STORE_ATTRIB: STOSW

LOOP_STORE: ADD SI,32 ;Point to next entry.
LOOP NEXT_RECORD
POP DS ;Restore segment.
CLC ;Indicate full sector.
RET

STORE_RETURN: POP DS ;Indicate not full sector.
STC
RET

;-------------------------------------------------------------------;
; This subroutine finds the next cluster in the chain from the FAT. ;
;-------------------------------------------------------------------;

CK_FAT: PUSH ES ;Save some registers.
PUSH BX
PUSH CX
MOV AX,CS ;Point to second segment.
ADD AX,1000H
MOV ES,AX

MOV AX,LAST_CLUSTER ;Retrieve last cluster.
PUSH AX ;Save.
XOR DX,DX ;Zero in high half.
CMP DISK_BLOCK[13],0FF6H ;Number of clusters above 4085?
JA SIXTEEN_BIT ;If yes, 16 bit FAT.
TWELVE_BIT: MOV BX,15 ;Else, 12 bit FAT.
MUL BX ;Multiply by 1.5 (or 15/10).
MOV BX,10
DIV BX
MOV BX,AX
MOV DX,ES:FAT[BX] ;Retrieve cluster pointer.
POP AX ;Retrieve last cluster.
TEST AX,1 ;Was last cluster even number?
JZ LOW_ORDER ;If yes, use low order 12 bits.
MOV CL,4 ;Else, high order 12 bits.
SHR DX,CL ;Shift right 4 bits.
JMP SHORT CK_12BIT
LOW_ORDER: AND DX,0000111111111111B ;Mask off top 4 bits.
CK_12BIT: CMP DX,0FF8H ;Is it end of chain?
JAE CLUSTER_END ;If yes, indicate so.
JMP SHORT RETURN_CLUST ;Else, return with cluster in DX.

SIXTEEN_BIT: POP BX ;Retrieve last cluster.
SHL BX,1 ;Multiply by 2.
MOV DX,ES:FAT[BX] ;Retrieve next cluster number.
CMP DX,0FFF8H ;Is it end of chain?
JAE CLUSTER_END ;If yes, indicate so.

RETURN_CLUST: POP CX ;Restore registers.
POP BX
POP ES
CLC ;Indicate not end of chain.
RET

CLUSTER_END: POP CX
POP BX
POP ES
STC ;Indicate end of cluster chain.
RET

;--------------------------------------------------------;
; This subroutine alphabetizes the directories by level. ;
;--------------------------------------------------------;

SORT: PUSH DS
MOV AX,DATA_SEGMENT ;Point to database segment.
MOV ES,AX
MOV DS,AX
XOR BP,BP ;Zero in level pointer.
LEVEL_SORT: MOV DX,ES:[OFFSET LEVEL_ADDRESS+BP+2] ;Retreive level offset.
CMP DX,0FFFFH ;End signature?
JZ SORT_RETURN ;If yes, done here.
SUB DX,DATA_RECORD ;Point to end of lower level.
PUSH BP ;Save level pointer.

NEXT_PASS: POP BP
PUSH BP
MOV BX,ES:[OFFSET LEVEL_ADDRESS+BP] ;Point to start of level.
XOR BP,BP ;Zero in exchange flag.
CMP BX,DX ;End of level?
JZ SORT_SET ;If yes, next level.

NEXT_SORT: MOV SI,BX ;Source and destination.
ADD SI,5 ;Point to name
MOV DI,BX ; in each record.
ADD DI,DATA_RECORD+5
MOV CX,11
REPZ CMPSB ;Compare names.
JBE END_SORT ;If already in order, skip.

MOV SI,BX ;Else, recover pointers.
MOV DI,BX
ADD DI,DATA_RECORD
MOV CX,DATA_RECORD/2 ;Exchange the records.
NEXT_SWAP: MOV AX,[DI]
MOVSW
MOV [SI-2],AX
LOOP NEXT_SWAP
MOV BP,1 ;Flag that exchange was made.

END_SORT: ADD BX,DATA_RECORD ;Point to next record.
CMP BX,DX ;End of top?
JB NEXT_SORT ;If no, bubble sort next.
SUB DX,DATA_RECORD ;Else, move top down one record.
CMP BP,0 ;Was there exchange made?
JNZ NEXT_PASS

SORT_SET: POP BP
INC BP ;Next level.
INC BP
JMP SHORT LEVEL_SORT ;Sort it.

SORT_RETURN: POP DS ;Restore data segment.
RET

;-------------------------------------------------------------;
; This subroutine formats the database into a directory tree. ;
;-------------------------------------------------------------;

FORMAT_IT: PUSH DATA_SEGMENT ;Point to database segment.
POP DS
MOV AX,CS ;Point to second segment.
ADD AX,1000H
MOV ES,AX
MOV SI,OFFSET LEVEL_ADDRESS ;Move resident database
MOV DI,SI ; into our second segment.
MOV CX,100+(DATA_RECORD*500/2)
REP MOVSW

PUSH CS ;Restore segment registers.
PUSH CS
POP DS
POP ES
CALL STORE_ROOT ;Initialize with "\ (ROOT)".

XOR BP,BP ;Zero in level pointer.
MOV SI,OFFSET DATA_BUFFER ;Point to database.
MOV DI,OFFSET DIRECTORIES+DIR_RECORD+1 ;Point to storage.
MOV LAST_ENTRY,DI ;Save current entry.
MOV AX,CS ;Point to second segment.
ADD AX,1000H
MOV DS,AX
CMP BYTE PTR [SI],0FFH ;No subs signature?
JZ SKIP_FORMAT ;If yes, skip format.

NEXT_FORMAT: CALL STORE_NAME ;Format the directory name.
ADD SI,DATA_RECORD ;Point to next record.
CMP BYTE PTR [SI],0 ;End of root directory?
JZ NEXT_FORMAT ;If no, get next entry.
SKIP_FORMAT: PUSH CS ;Else, done.
POP DS ;Restore data segment.
DEC DI ;Adjust end pointer.
MOV END_OFFSET,DI ;And save.
MOV BX,DIR_COUNT ;Retrieve directory count.
CMP BX,21 ;Enough to fill one page?
JAE FORMAT_END ;If yes, use default setting.
MOV AX,160 ;Calculate last record.
MUL BL
ADD AX,323 ;Add bar offset.
MOV PAGE_END,AX ;And save.
FORMAT_END: RET

;------------------------------------------------------;
; This subroutine initializes the directory tree image ;
; with nulls and makes the first record "\ (ROOT)". ;
;------------------------------------------------------;

STORE_ROOT: MOV AX,0
MOV DI,OFFSET DIRECTORIES
MOV CX,30000 / 2
REP STOSW

MOV DI,OFFSET DIRECTORIES
MOV SI,OFFSET ROOT
MOV CX,8
REP MOVSB
RET

;-----------------------------------------------------------;
; This subroutine recursively builds the subdirectory tree. ;
;-----------------------------------------------------------;

STORE_NAME: PUSH SI ;Save a couple registers.
PUSH BP

PUSH DI ;Save pointers.
PUSH SI
PUSH SI
MOV SI,CS:LAST_ENTRY ;Retrieve last entry.
MOV DX,DI ;Retrieve current position.
CMP SI,DX ;Have stored any subdirectories?
JZ STORE_DIR ;If no, skip tree structuring.
MOV BYTE PTR CS:[SI+BP],195 ;Store connecting T character.
PLACE_BAR: ADD SI,DIR_RECORD ;Move to next record.
CMP SI,DX ;Are we at the current record?
JZ STORE_DIR ;If yes, done here.
MOV BYTE PTR CS:[SI+BP],179 ;Else, store vertical line char.
JMP SHORT PLACE_BAR ;Repeat until up to date.

STORE_DIR: MOV CS:LAST_ENTRY,SI ;Store current position.
POP SI ;Restore current record pointer.
INC CS:DIR_COUNT ;Increment directory counter.
ADD DI,BP ;Add level pointer.
MOV AL,192 ;Store L line character.
STOSB
MOV AL,196 ;Store horizontal line character.
STOSB
ADD SI,5 ;Point to directory name.
MOV CX,8
CALL STORE_BYTES ;And store in tree.
CMP BYTE PTR [SI],32
JZ END_NAME
MOV AL,'.' ;If exists, append extension.
STOSB
MOV CX,3
CALL STORE_BYTES
END_NAME: POP SI ;Restore current record pointer.
INC DI ;Bump pointer for space.
MOV AL,[SI+18] ;Retrieve "hidden" field.
STOSB ;And store it.
NEXT_DEST: POP DI ;Restore destination pointer.
ADD DI,DIR_RECORD ;Point to next tree record.

PUSH CS:LAST_ENTRY ;Save current position pointer.
MOV CS:LAST_ENTRY,DI ;Save our current position.
INC BP ;Bump pointer to next level.
INC BP
MOV BX,DS:[OFFSET LEVEL_ADDRESS+BP] ;Get level offset.

NEXT_SUB: MOV AL,DS:[BX] ;Retrieve level pointer.
XOR AH,AH
CMP AX,BP ;Is it our level?
JA STORE_END ;If next level, done here.
MOV AX,DS:[BX+3] ;Retrieve parent entry number.
CMP AX,DS:[SI+1] ;Is it our entry number?
JNZ SKIP_SUB ;If no, check next record.

GET_SUB: PUSH BX ;Else, save pointers.
PUSH SI
MOV SI,BX ;Point to our offset.
CALL STORE_NAME ;Store subdirectory.
POP SI ;Restore pointers.
POP BX

SKIP_SUB: ADD BX,DATA_RECORD ;Next record.
JMP SHORT NEXT_SUB

STORE_END: POP CS:LAST_ENTRY ;Restore last entry pointer.
POP BP
POP SI
RET

;--------------------------------------;

STORE_BYTES: LODSB ;Store all non space name
CMP AL,32 ; characters.
JZ SKIP_STORE
STOSB
SKIP_STORE: LOOP STORE_BYTES
RET

;-----------------------------------------------------------------;
; This subdirectory works its way back to the root directory from ;
; the currently highlighted directory and then retraces back up ;
; the tree changing the default directory one level at a time. ;
;-----------------------------------------------------------------;

NEW_DIR: MOV COUNT,0 ;Zero out path counter.
MOV DX,OFFSET ROOT_DIR ;Change to root directory.
CALL CD
XOR CX,CX ;Zero in level counter.
MOV DI,CUR_RECORD ;Point to highlighted record.
CMP DI,OFFSET DIRECTORIES ;Is it the root?
JZ NEW_DIR_END ;If yes, done here.
MOV BX,DIR_NAME ;Point to directory name.
DEC BX ;Point to horizontal character.

NEXT_SUBDIR: MOV SI,BX
FIND_DIR: LODSB ;Get a byte.
CMP AL,196 ;Is it horizonatal character?
JNZ FIND_DIR ;If no, find it.
PUSH SI ;Save directory.
INC CX ;Also keep track of level.
NEXT_PATH: INC COUNT ;Increment path length.
LODSB
CMP AL,0 ;Are we at end of directory name?
JNZ NEXT_PATH ;If no, find it.
CMP BYTE PTR [DI+2],196 ;Is there a level under us?
JZ CHANGE_THEM ;If no, done here.
DEC BX ;Else, decrement level pointer.
DEC BX
NEXT_LEVEL: SUB DI,DIR_RECORD ;Look for next lower level.
SUB BX,DIR_RECORD
CMP BYTE PTR [BX],196 ;Have we found parent record?
JNZ NEXT_LEVEL ;If no, find it.
JMP SHORT NEXT_SUBDIR ;And find name and length.

CHANGE_THEM: POP DX ;Retrieve directory name pointer.
CALL CD ;Change directories.
JC RESTORE_STACK ;If nonexistant, restore stack
LOOP CHANGE_THEM ;Else, continue up the tree.
NEW_DIR_END: RET

NEXT_STACK: POP DX ;If error, restore stack
RESTORE_STACK: LOOP NEXT_STACK ; before returning.
RET

;--------------------------------------;

CD: MOV AH,3BH
INT 21H
RET

;--------------------------------------------------;
; This subroutine finds the highlighted directory. ;
;--------------------------------------------------;

GET_NAME: MOV SI,CUR_OFFSET ;Get top of page.
MOV AX,LINE ;Get location of bar.
SUB AX,323 ;Adjust.
MOV CL,160 ;80 characters + 80 attributes.
DIV CL ;Divide to get line number.
MOV CL,DIR_RECORD ;Times directory record length.
MUL CL
ADD SI,AX ;Add to current offset.
MOV CUR_RECORD,SI ;And save pointer.
CMP CUR_RECORD,OFFSET DIRECTORIES ;Is it top line?
JZ NAME_RETURN ;If yes, done here.
FIND_TOP: LODSB
CMP AL,196 ;Is it the horizontal line char?
JNZ FIND_TOP ;If no, find it.
NAME_RETURN: MOV DIR_NAME,SI ;And store name pointer.
RET

;----------------------------------------------------------;
; This subdirectory searches through the graphic tree for ;
; the name of the DOS default directory and highlights it. ;
;----------------------------------------------------------;

GET_CURRENT: MOV SI,OFFSET CURRENT_DIR ;Point to default directory.
MOV BYTE PTR [SI],'\' ;DOS fails to include starting "\".
INC SI ;Point to first level.
MOV DL,DRIVE ;Retrieve drive.
INC DL ;Convert to DOS format.
MOV AH,47H ;Retrieve current directory.
INT 21H
CALL CAPITALIZE ;Capitalize it.

MOV BX,OFFSET CURRENT_DIR+1 ;Point to default directory.
CMP BYTE PTR [BX],0 ;Are we at end?
JZ CURRENT_END ;If yes, done here.
MOV BP,OFFSET DIRECTORIES+DIR_RECORD+3 ;Store pointer.
XOR DX,DX ;Zero entry counter.
MOV CX,500 ;Search a maximum of 500 records.

NEXT_DEFAULT: INC DX ;Increment entry counter.
MOV SI,BX ;Point to default directory.
MOV DI,BP ;Point to directory tree.
CMP BYTE PTR [DI-1],196 ;Is it horizontal bar?
JNZ NO_MATCH ;If no, next record.
NEXT_MATCH: CMP BYTE PTR [DI],0 ;Is it end of tree name?
JNZ CK_DEFAULT ;If no, see if name matches.
CMP BYTE PTR [SI],'\' ;Is it end of default level?
JZ MATCH ;If yes, then we have a match.
CMP BYTE PTR [SI],0 ;Zero also marks name end.
JZ MATCH
CK_DEFAULT: CMPSB ;Character in name match?
JZ NEXT_MATCH ;If yes, check next byte.
NO_MATCH: ADD BP,DIR_RECORD ;Else, next record.
LOOP NEXT_DEFAULT ;Search up to 500 records.
JMP SHORT CURRENT_END ;Give up if not found.

MATCH: DEC CX ;Decrement search counter.
JZ CURRENT_END ;Exit, if not found.
INC SI ;Point to name.
MOV BX,SI ;Store current level.
ADD BP,DIR_RECORD+2 ;Point to next record and level.
CMP BYTE PTR [SI-1],0 ;End of default?
JNZ NEXT_DEFAULT ;If no, continue.

FIND_DEFAULT: MOV CX,DIR_COUNT ;Retrieve directory count.
SUB CX,DX ;Subtract default entry counter.
MOV SEARCH_COUNT,CX ;Save.
MOV CL,NORMAL ;Turn off bar for now.
MOV BAR_ATTRIBUTE,CL
CALL END_BAR ;First move to end.
JMP SHORT FIND_IT
NEXT_FIND: CALL UP_ARROW ;Move up to matching directory.
FIND_IT: DEC SEARCH_COUNT
JNZ NEXT_FIND
MOV CL,INVERSE ;Turn bar back on.
MOV BAR_ATTRIBUTE,CL
CURRENT_END: RET

;-----------------------------------------------------;
; This subroutine displays the menu, requested drive, ;
; and directory count. Also displays highlight bar. ;
;-----------------------------------------------------;

START_DISPLAY: MOV SI,OFFSET MENU ;Point to menu position.
MOV DI,(2*160)+98 ;And to screen position.
MOV BH,19 ;Display 19 lines of menu.
NEXT_FUNCTION: MOV CX,22 ;22 characters per line.
NEXT_MENU: LODSB
MOV BL,AL
CALL WRITE_SCREEN
LOOP NEXT_MENU
ADD DI,116 ;Next line.
DEC BH
JNZ NEXT_FUNCTION

MOV SI,OFFSET HEADING ;Display "Directories of drive".
MOV DX,1
CALL DISPLAY_TEXT
MOV AL,DRIVE
ADD AL,'A' ;Display drive.
CALL WRITE_TEXT
MOV AL,':' ;And colon.
CALL WRITE_TEXT
CALL COUNT_DIRS ;Display directory count.
MOV SI,LINE
CALL MOVE_BAR ;Highlight the current directory.
RET

;----------------------------------------------------;
; This subroutine capitalizes the default directory. ;
;----------------------------------------------------;

CAPITALIZE: CMP BYTE PTR [SI],'a'
JB NEXT_CAPS
CMP BYTE PTR [SI],'z'
JA NEXT_CAPS
AND BYTE PTR [SI],5FH
NEXT_CAPS: INC SI
CMP BYTE PTR [SI],0
JNZ CAPITALIZE
RET

;-------------------------------------;
; This subroutine scrolls the screen. ;
;-------------------------------------;

SCROLL: MOV SI,CUR_OFFSET ;Get current offset.
ADD SI,BP ;Add requested direction.
CK_LOWER: CMP SI,OFFSET DIRECTORIES ;If above start check upper limit.
JAE UPPER_LIMIT
LOWER_LIMIT: MOV CUR_OFFSET,OFFSET DIRECTORIES ;Else, make it start.
JMP SHORT SCROLL_RETURN ;And update screen.

UPPER_LIMIT: MOV BX,END_OFFSET ;See if beyond end of
CMP BX,OFFSET DIRECTORIES+21*DIR_RECORD ; directory listing.
JA CK_UPPER
MOV CUR_OFFSET,OFFSET DIRECTORIES
JMP SHORT SCROLL_RETURN

CK_UPPER: SUB BX,21*DIR_RECORD
CMP SI,BX
JBE END_SCROLL
MOV SI,BX

END_SCROLL: MOV CUR_OFFSET,SI ;Update current offset.
MOV SI,LINE
CALL MOVE_BAR
SCROLL_RETURN: RET

;--------------------------------------------------;
; This subroutine scrolls the bar if between start ;
; and end of page. Otherwise the page is scrolled. ;
;--------------------------------------------------;

SCROLL_BAR: MOV SI,LINE ;Get current line.
ADD SI,BP ;Add requested line.
MOV BP,-DIR_RECORD ;Assume below beginning.
CMP SI,323 ;Is it?
JB SCROLL_PAGE ;If yes, scroll page instead.
MOV BP,DIR_RECORD ;Do the same for end of page.
CMP SI,PAGE_END
JAE SCROLL_PAGE
CALL MOVE_BAR
RET

SCROLL_PAGE: CALL SCROLL
RET

;----------------------------------------------------;
; This subroutine does the actual moving of the bar. ;
;----------------------------------------------------;

MOVE_BAR: MOV DI,BAR_START ;Remove old bar.
MOV CX,BAR_LENGTH
MOV BL,NORMAL
NEXT_BAR: CALL WRITE_SCREEN
LOOP NEXT_BAR

MOV LINE,SI ;And move bar to new line.
MOV BL,BAR_ATTRIBUTE
CALL GET_NAME
MOV SI,CUR_RECORD ;Retrieve current record.
MOV DI,LINE ;Retrieve line.
MOV BAR_LENGTH,0 ;Zero out bar length counter.
START_BAR: LODSB
CMP AL,'\' ;Is it the root?
JZ SAVE_START ;If yes, done here.
INC DI ;Increment bar pointer
INC DI ; past character as well.
CMP AL,196 ;Is it horizontal bar character?
JNZ START_BAR ;If no, find it.
INC SI ;Bump point to start of name.

SAVE_START: MOV BAR_START,DI ;Save.
DISPLAY_BAR: INC BAR_LENGTH ;Increment bar length counter.
MOV CX,1
CALL WRITE_SCREEN ;Write the attribute.
LODSB
CMP AL,0 ;Are we at end of name?
JNZ DISPLAY_BAR ;Continue until done.
RET

;-------------------------------------------------;
; This subroutine displays the directory listing. ;
;-------------------------------------------------;

UPDATE_SCREEN: XOR BP,BP
MOV SI,CUR_OFFSET ;Retrieve starting offset.
MOV DI,2*160+2 ;Point to row two of screen.
MOV BH,21 ;21 lines to write.
NEXT_WRITE: MOV CX,40 ;40 characters per line.
NEXT_CHAR: LODSB ;Get a byte.
MOV BL,AL ;Save it in BL.
CALL WRITE_SCREEN ;Write them.
LOOP NEXT_CHAR
ADD DI,80 ;Bump pointer to next line.
DEC BH ;Do all 21 lines.
JNZ NEXT_WRITE
RET

;------------------------------------------------------------;
; This subroutine displays the directory by writing directly ;
; to the screen buffer. To avoid screen noise (snow) on the ;
; color card, the horizontal retrace has to be monitored. ;
;------------------------------------------------------------;

WRITE_SCREEN: MOV DX,STATUS_REG ;Retrieve status register.
MOV AX,VIDEO_SEG ;Point to screen segment.
MOV ES,AX

HORZ_RET: IN AL,DX ;Get status.
TEST AL,1 ;Is it low?
JNZ HORZ_RET ;If not, wait until it is.
CLI ;No more interrupts.

WAIT: IN AL,DX ;Get status.
TEST AL,1 ;Is it high?
JZ WAIT ;If no, wait until it is.
MOV AL,BL ;Retrieve character; now it's OK
STOSB ; to write to screen buffer.
STI ;Interrupts back on.
INC DI ;Bump pointer past attribute.
PUSH CS
POP ES
RET ;Return

;-----------------------------------------------------------------------;
; These two subroutines clear either the messages or the entire screen. ;
;-----------------------------------------------------------------------;

CLEAR_MSG: CALL CURSOR_OFF
MOV CX,1629H ;Row 22; column 41.
MOV DX,184FH ;Row 24; column 79.
JMP SHORT CLEAR_WINDOW

CLEAR_LINE: MOV CX,1729H ;Row 23; column 41.
MOV DX,174FH ;Row 23; column 79.
JMP SHORT CLEAR_WINDOW

CLS: XOR CX,CX
MOV DX,184FH ;Entire screen.

CLEAR_WINDOW: PUSH BP
PUSH BX
MOV BH,NORMAL ;Clear with original attribute.
MOV AX,600H
INT 10H
POP BX
POP BP
RET

;-----------------------------------------;
; These subroutines display the messages. ;
;-----------------------------------------;

DISPLAY_TEXT: CALL SET_CURSOR ;Move cursor.
GET_TEXT: LODSB
CMP AL,0 ;Zero marks end of string.
JZ END_TEXT
CALL WRITE_TEXT
JMP SHORT GET_TEXT
END_TEXT: RET

WRITE_TEXT: PUSH SI ;BIOS does not save SI.
MOV AH,0EH ;Write teletype.
INT 10H
POP SI
RET

;---------------------------------------------------------;
; These four subroutines move the cursor, get the current ;
; directory, beep the speaker or get a keystroke. ;
;---------------------------------------------------------;

SET_CURSOR: PUSH SI
XOR BH,BH ;Page zero.
MOV AH,2 ;Set cursor.
INT 10H
POP SI
RET

BEEP: MOV DL,7 ;Beep via DOS.
MOV AH,2
INT 21H
RET

READ_KEY: MOV AH,0 ;Retrieve keystroke via BIOS.
INT 16H
RET

;-----------------------------------------------;
; These subroutines turn the cursor off and on. ;
;-----------------------------------------------;

CURSOR_OFF: MOV CX,2000H
JMP SHORT SET_TYPE

CURSOR_ON: MOV CX,CURSOR_TYPE

SET_TYPE: MOV AH,1
INT 10H
RET

;----------------------------------------------;
; This subroutine displays the count of files. ;
;----------------------------------------------;

COUNT_DIRS: MOV DX,1802H ;Row 24; column 2.
CALL SET_CURSOR
MOV AX,DIR_COUNT
CALL GET_DIR_COUNT
MOV SI,OFFSET DIR_MSG ;Display directory count.
CALL GET_TEXT
RET

;------------------------------------------;
; This subroutine converts hex to decimal. ;
;------------------------------------------;

GET_COUNT: MOV AX,COUNT
GET_DIR_COUNT: MOV BX,10 ;Convert to decimal.
XOR CX,CX ;Zero in counter.
NEXT_COUNT: XOR DX,DX
DIV BX
ADD DL,'0' ;Convert to ASCII.
PUSH DX ;Save results.
INC CX ;Also increment count.
CMP AX,0 ;Are we done?
JNZ NEXT_COUNT

NEXT_NUMBER: POP AX ;Retrieve numbers.
CALL WRITE_TEXT ;And write them.
LOOP NEXT_NUMBER
RET

;---------------------------------------------;
; This subroutine restores the current drive. ;
;---------------------------------------------;

RESTORE_DRIVE: PUSH DX
MOV DL,DEFAULT_DRIVE
MOV AH,0EH
INT 21H
POP DX
RET

;------------------------------------;

DISK_BLOCK LABEL WORD
CURRENT_DIR EQU DISK_BLOCK+18
LEVEL_ADDRESS EQU CURRENT_DIR+65
DIRECTORIES EQU LEVEL_ADDRESS+100
DATA_BUFFER EQU LEVEL_ADDRESS+100
END_RESIDENT EQU DATA_BUFFER+(DATA_RECORD*500)

CODE ENDS
END START


  3 Responses to “Category : Files from Magazines
Archive   : VOL6N16.ZIP
Filename : RN.ASM

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/