	      NAME	FLIST
	      PAGE	60,132
	      TITLE	'FLIST - Alphabetically Sorted List of Diskette Files'
;
; References: 1 	DOS 2.0 Manual
;	      2 	IBM Macro Assembler Manual
;
DATA	      SEGMENT	PARA	  PUBLIC 'DATA'
;
; Extended File Control Block Area (see 1, pages E-10 through E-14 for a
; description of Standard and Extended File Control Blocks):
  FCB_FLAG    DB		  (0)		 ;FCB extension flag
  RES_AREA1   DB	5    DUP  (0)		 ;Reserved space ???
  FCB_ATTR    DB		  (0)		 ;1=read only, 2=hidden file,
; Standard FCB: 				  4=system file (see 1, C.4)
  FCB_DRIVE   DB		  (0)		 ;1=A, 2=B, etc.
  FILE_NAME   DB		  '????????'     ;Left-justfd, trailing blanks
  FILE_EXT    DB		  '???'          ;Left-justfd, trailing blanks
  CUR_BLOCK   DW		  (0)		 ;Current block, starting at 0
  REC_SIZE    DW		  (0)		 ;Initialized to 128 bytes
  FILE_SIZE   DW	2    DUP  (0)		 ;Least-significant word is 1st
  LAST_UPDT   DW		  (0)		 ;Last update (see 1, E-12)
  RES_AREA2   DW	5    DUP  (0)		 ;Reserved space ???
  REL_RECNO   DB		  (0)		 ;Cur. rel. rec. no. (0-127)
  ACT_RECNO   DW	2    DUP  (0)		 ;Actual rec. no. (see 1, E-12)
; End of Extended FCB Area
;
; Extended DOS Disk Transfer Area (see 1, D-22 for a description of the data
; transferred by INT 21H, AH=11, and pages C-3 through C-6 for a description of
; the DOS Disk Directory):
  DTA_FLAG    DB		  (0)		 ;Extension flag from FCB_FLAG
  RES_AREA3   DB	5    DUP  (0)		 ;Reserved space ???
  SRCH_ATTR   DB		  (0)		 ;Search attr. from FCB_ATTR
; Standard DTA:
  DTA_DRIVE   DB		  (0)		 ;1=A, 2=B, etc.
  DTA_FNAME   DB	8    DUP  (0)		 ;First byte indicates status
  DTA_FEXT    DB	3    DUP  (0)		 ;Left-justfd, trailing blanks
  DTA_ATTR    DB		  (0)		 ;File attribute (see FCB_ATTR)
  RES_AREA4   DW	5    DUP  (0)		 ;Reserved space ???
  DTA_TIME    DW		  (0)		 ;Time of last update
  DTA_DATE    DW		  (0)		 ;Date of last update
  STRT_CLSTR  DW		  (0)		 ;First relative cluster number
  DTA_FSIZE   DW	2    DUP  (0)		 ;Least-significant word is 1st
; End of Extended DTA Area
;
; Disk Format Table, used to interpret first byte of the File Allocation Table:
  DISK_FORMAT DB		  (0)		 ;Disk format. See 1, C-7
  FAT_FLAG    DB		  0FFH		 ;2 sided, 8 sectors-per-track
	      DW		  FORMAT_28
	      DB		  0FEH		 ;1 sided, 8 sectors-per-track
	      DW		  FORMAT_18
	      DB		  0FDH		 ;2 sided, 9 sectors-per-track
	      DW		  FORMAT_29
	      DB		  0FCH		 ;1 sided, 9 sectors-per-track
	      DW		  FORMAT_19
	      DB		  0F8H		 ;Fixed disk
	      DW		  FORMAT_FD
	      DB		  0		 ;unknown end of table
	      DW		  FORMAT_??
; End of Disk Format Table
;
; Top Line Information Area:
  TLINE_1     DB	'Sorted Directory for '
  REQ_DRIVE   DB	'@'                      ;The drive for which the dir.
;						  list was requested. A=A, etc.
  TLINE_2     DB	' Drive Diskette   |   UNUSED SPACE = $'
  TLINE_3     DB	'  bytes',0DH,0AH
  TLINE_4     DB	'Current Date: '
  CURMONTH    DW	'00'
  TLINE_5     DB	'/'
  CURDAY      DW	'00'
  TLINE_6     DB	'/'
  CURYEAR     DW	'00'
  TLINE_7     DB	'    Time: '
  CURHOUR     DW	'00'
  TLINE_8     DB	':'
  CURMIN      DW	'00'
  TLINE_9     DB	'   |   DISK FORMAT  =  $'
  FORMAT_18   DB	'1 sided 8 sectored$'
  FORMAT_28   DB	'2 sided 8 sectored$'
  FORMAT_19   DB	'1 sided 9 sectored$'
  FORMAT_29   DB	'2 sided 9 sectored$'
  FORMAT_FD   DB	'fixed disk$'
  FORMAT_??   DB	'unknown$'
  COL_TITLES  DB	'Filename.Ext        Size      Date$'
;
; End of Top Line Information Area
;
  SKIP_1      DW	0D0AH			 ;Carriage return / line feed
  CR_LF       DW	0D0AH			 ;Carriage return / line feed
  PRINT_END   DB	'$'                      ;Print string terminator
  TAB_4       DB	'    $'
  TAB_6       DB	'      $'
  TAB_8       DB	'        $'
;
  DEF_DRIVE   DB		  (0)		 ;Default drive: 0=A, 1=B, etc.
  FILE_COUNT  DW		  (0)		 ;Number of files in the disk
;						  directory which conform to
;						  the user-specified parameter
  FIRST_ENTRY DW		  (0)		 ;Points to the first entry in
;						 ;alpha-sorted DIR_LIST
  MID_ENTRY   DW		  (0)		 ;Points to the entry half-way
;						  down the sorted DIR_LIST
  LAST_ENTRY  DW		  (0)		 ;Points to the byte after the
;						  last entry in DIR_LIST
  FREE_SPACE  DW	2    DUP  (0)		 ;The unused or available space
;						  on the disk
  DIR_LIST    DB		  (0)		 ;Area used to save file
;						  directory data which conform
;						  to the user-specified param.
;	      The structure of each entry in
;	      DIR_LIST is as follows:
;
;	      link pointer	     2 bytes (1 word)
;	      filename		     8 bytes
;	      . separator	     1 byte
;	      file extension	     3 bytes
;	      date of last update    2 bytes (1 word)
;	      file size in bytes     4 bytes (2 words)
;				    --------
;	      size of each entry:   20 bytes
;
DATA	      ENDS
;
	      USER_PARMS     EQU  05CH
	      INT24_SEG      EQU  090H
	      INT24_OFF      EQU  092H
;
CODE	      SEGMENT	PARA	  PUBLIC 'CODE'
MAIN	      PROC	FAR
	      ASSUME	CS:CODE, SS:STACK
;
; Standard Program Prologue:
;
	      PUSH	DS			 ;Save PSP Segment Address
	      XOR	AX,AX			 ;Zero out AX and push it to
	      PUSH	AX			 ;save PSP Offset Address
;
; Establish Extra Segment Addressability:
;
	      MOV	AX,DATA 		 ;Point ES to Data Segment and
	      MOV	ES,AX			 ;establish Extra Segment
	      ASSUME	ES:DATA 		 ;Addressability
;
	      CALL	GET_PARMS
;
; Establish Data Segment Addressability:
;
	      PUSH	ES			 ;Point DS to ES, i.e. our Data
	      POP	DS			 ;Segment, and establish Data
	      ASSUME	DS:DATA 		 ;Segment Addressability
;
;
	      CALL	SETUP_DRIVE
;
	      CALL	SETUP_FCB
;
	      CALL	GET_ENTRIES
;
	      CALL	SORT_ENTRIES
;
	      CALL	FIND_MIDDLE
;
	      CALL	GET_FAT_INFO
;
	      CALL	DISPLAY_TOP
;
	      CALL	LIST_ENTRIES
;
	      CALL	RESET_DRIVE
;
	      RET				 ;Return control to DOS
;
; Move user-specified parameters (if any) from the first formatted FCB area in
; the PSP into the Extended FCB Area in our Data Segment:
;
GET_PARMS     PROC	NEAR
	      MOV	SI,USER_PARMS		 ;1st parm offset in PSP
	      MOV	DI,OFFSET FCB_DRIVE	 ;Offset within our FCB area
	      MOV	CX,1			 ;Initialize count to 1
	      CMP	BYTE PTR[SI+01],' '      ;Second byte is blank if the
;						  user specified no parameters
	      JE	SHORT XFER_PARM 	 ;Jump if no parms specified
	      ADD	CX,11			 ;Otherwise, let CX = 12
XFER_PARM:
	      CLD				 ;Set 'forward' MOVSB operation
	      REP	MOVSB			 ;Transfer parameters
	      RET
GET_PARMS     ENDP
;
SETUP_DRIVE   PROC	NEAR
;
; Find the default drive and save the default drive code in DEF_DRIVE:
;
	      MOV	AH,19H			 ;See 1, D-26
	      INT	21H			 ;Code is returned in AL:
	      MOV	DEF_DRIVE,AL		 ;0=A, 1=B, etc.
;
; If the user did not specify a drive in his parameters, FCB_DRIVE is 0. This
; must be changed to reflect the default drive code stored in DEF_DRIVE:
;
	      MOV	DL,FCB_DRIVE		 ;0=default, 1=A, 2=B, etc.
	      DEC	DL			 ;-1=default, 0=A, 1=B, etc.
	      JNS	SHORT CHECK_DRIVE	 ;If user specified a drive,
;						  value of DL is "not signed"
;						  (DL => 0) so jump, i.e. DON'T
;						  adjust the value of DEF_DRIVE
	      INC	AL			 ;Otherwise, set FCB_DRIVE to
	      MOV	FCB_DRIVE,AL		 ;DEF_DRIVE: 1=A, 2=B, etc.
	      JMP	END_OF_CHECK
CHECK_DRIVE:
;
; Change the default drive to that specified in the user parameters.
; The drive specified by the user is not necessarily valid.
;
	      MOV	AH,0EH			 ;Use DL value to set default
	      INT	21H			 ;drive (0=A, 1=B, etc.)
	      INC	DL			 ;Number of drives is returned
	      CMP	DL,AL			 ;in AL (1=1 drive, etc.)
	      JNA	END_OF_CHECK
	      MOV	DL,DEF_DRIVE		 ;The user specified an invalid
	      INC	DL			 ;drive so set the FCB_DRIVE to
	      MOV	FCB_DRIVE,DL		 ;the default drive
END_OF_CHECK:
	      MOV	AL,FCB_DRIVE		 ;Record the value of FCB_DRIVE
	      ADD	REQ_DRIVE,AL		 ;as a letter (A=1, B=2, etc.)
	      RET
SETUP_DRIVE   ENDP
;
; Initialize the Extended FCB Area in our Data Segment to indicate that it is
; an Extended FCB and that we want to retrieve ALL files (including read-only,
; hidden, and system files) from the disk directory:
;
SETUP_FCB     PROC	NEAR
	      MOV	AL,0FFH
	      MOV	FCB_FLAG,AL		 ;See 1, E-14
	      MOV	AL,00000111B		 ;See 1, C-4,C-5, and D-22,D-23
	      MOV	FCB_ATTR,AL		 ;See 1, E-14
	      RET
SETUP_FCB     ENDP
;
; Move each entry in the disk directory which conforms to the user parameters
; into the DIR_LIST in our Data Area:
;
GET_ENTRIES   PROC	NEAR
	      MOV	BX,OFFSET DIR_LIST	 ;Initialize LAST_ENTRY to
	      MOV	LAST_ENTRY,BX		 ;beginning of DIR_LIST area
;
	      MOV	DX,OFFSET DTA_FLAG	 ;Point DS:DX, the Disk
;						  Transfer Address, to our DTA
;						  Area so that data transferred
;						  from the disk will appear in
;						  Data Seg rather than the PSP
	      MOV	AH,1AH			 ;Set Disk Transfer Address
	      INT	21H			 ;(DTA) to DS:DX (see 1, D-26)
;
	      MOV	DX,OFFSET FCB_FLAG	 ;Point DS:DX to our unopened
	      MOV	AH,11H			 ;FCB Area and go looking for
	      INT	21H			 ;the first match-up. If no
;						  files match-up whatsoever,
;						  AL returns  with a value of
	      OR	AL,AL			 ;FF. Otherwise, AL is zero.
	      JNZ	END_XFER		 ;If no entry whatsoever, jump
;						  Otherwise:
DIR_XFER:
	      MOV	SI,OFFSET DTA_FNAME	 ;Point to transferred filename
	      MOV	DI,LAST_ENTRY		 ;Point DI to next entry space
	      LEA	DI,[DI+02]		 ;in our DIR_LIST. Leave 2
;						  bytes for the link pointer
	      MOV	CX,8			 ;8 byte filename to move
	      CLD				 ;Set 'forward' MOVSB operation
	      REP	MOVSB			 ;Transfer the filename from
;						  the DTA to the DIR_LIST
	      MOV	BYTE PTR [DI],'.'        ;Set filename.ext period
	      INC	DI
	      MOV	CX,3			 ;3 byte file extension to move
	      REP	MOVSB			 ;Transfer the file extension
	      MOV	SI,OFFSET DTA_DATE	 ;Transfer the date the file
	      MOVSW				 ;was created or last updated
	      MOV	SI,OFFSET DTA_FSIZE	 ;Transfer size of file (bytes)
	      MOVSW				 ;Least-significant part of the
	      MOVSW				 ;size is stored in 1st word
;
	      ADD	BX,20			 ;Each entry in DIR_LIST uses
	      MOV	LAST_ENTRY,BX		 ;20 bytes. Update LAST_ENTRY
	      INC	WORD PTR FILE_COUNT	 ;1 more file in the list
;
	      MOV	DX,OFFSET FCB_FLAG	 ;Reset DS:DX to the start of
	      MOV	AH,12H			 ;our FCB Area and go looking
	      INT	21H			 ;for the next match-up in the
;						  disk directory
	      OR	AL,AL			 ;Any more match-ups?
	      JZ	DIR_XFER		 ;If so, jump back baby
END_XFER:
	      RET
GET_ENTRIES   ENDP
;
; The following code uses a bubble sort to establish a linked list of entries
; in DIR_LIST. The link pointers are stored in the first word of each entry in
; DIR_LIST and the variable FIRST_ENTRY points to the first entry in the sorted
; list. The link pointer in this first entry points to the second entry, etc.
; The last entry in the sorted list has a link pointer value of zero.
;
; At the start of each pass in the bubble sort, DI is set to the first entry
; in the sub-list sorted so far and SI points to the DIR_LIST entry being
; added to the sorted sub-list.  During the pass, DI moves up the sorted sub-
; list and the filename beginning at [DI+02] is compared to the filename at
; [SI+02].  BX points to the previous value of DI.  When the correct location
; for the SI entry is found in the sorted sub-list (entry in BX < entry in SI
; < entry in DI), the link pointer in BX is set to the offset of the SI entry
; and the link pointer in SI is set to the offset of the DI entry.
; i.e., BX points to SI and SI points to DI.
; A value of DI = 0 indicates that we are at the top of the sorted sub-list.
;
SORT_ENTRIES  PROC	NEAR
	      MOV	SI,OFFSET DIR_LIST	 ;Initially, select the first
;						  entry in DIR_LIST for sort
	      CMP	SI,LAST_ENTRY		 ;If no entries in DIR_LIST,
	      JE	SHORT	  END_SORT	 ;bypass the sort step
NEW_PASS:
	      MOV	DI,OFFSET FIRST_ENTRY	 ;Point DI to the first entry
;						  in the sorted sub-list of
;						  DIR_LIST entries
NEXT_COMPARE:
	      MOV	BX,DI			 ;Update DI, retaining previous
	      MOV	DI,[BX] 		 ;value of DI in BX
	      OR	DI,DI			 ;Are we at the top of the
	      JZ	FIX_PTRS		 ;sub-list? If so, jump
	      PUSH	SI
	      PUSH	DI
	      LEA	SI,[SI+02]		 ;If not, compare the filenames
	      LEA	DI,[DI+02]		 ;pointed to by SI and DI
	      MOV	CX,12			 ;12 chars in "filename.ext"
	      CLD				 ;Set 'forward' CMPSB operation
	      REPE	CMPSB			 ;Compare until different:
;						  (value in SI - value in DI)
;						  i.e. result is positive if
;						  SI entry > DI entry
	      POP	DI
	      POP	SI
	      JA	NEXT_COMPARE		 ;If the entry in SI is alpha-
;						  betically greater than the
;						  entry in DI, try the next
;						  entry in the sorted sub-list
FIX_PTRS:
	      MOV	[BX],SI 		 ;Point BX entry to SI entry
	      MOV	[SI],DI 		 ;Point SI entry to DI entry
	      ADD	SI,20			 ;Point SI to next entry in
;						  DIR_LIST to be sorted
	      CMP	SI,LAST_ENTRY		 ;Proceed with a new pass only
	      JB	NEW_PASS		 ;if we are not at the end of
;						  DIR_LIST
END_SORT:
	      RET
SORT_ENTRIES  ENDP
;
; The screen display is going to list the files in two columns.  To do this we
; must know the file entry in DIR_LIST which is half-way down the list, as
; this will be displayed on the right-hand side of the screen against the first
; entry in the list which will be displayed on the left-hand side, and so on
; down the list.  So here goes:
;
FIND_MIDDLE   PROC	NEAR
	      MOV	CX,FILE_COUNT
	      SAR	CX,1			 ;CX = FILE_COUNT / 2
	      JZ	NO_RHS
	      ADC	CL,00			 ;Add 0 to CL ???
	      MOV	BX,OFFSET FIRST_ENTRY
NEXT_ENTRY:
	      MOV	BX,[BX] 		 ;Find the entry which is half-
	      LOOP	NEXT_ENTRY		 ;way down the sorted DIR_LIST
	      MOV	AX,[BX]
	      MOV	MID_ENTRY,AX		 ;Save the list mid-point
	      XOR	AX,AX
	      MOV	[BX],AX 		 ;Set the link pointer in the
;						  mid-point entry to zero
NO_RHS:
	      RET
FIND_MIDDLE   ENDP
;
; Read the File Allocation Table information. See 1, C-1, C-2, C-6 through C-9,
; and D-26.
;
GET_FAT_INFO  PROC	NEAR
	      PUSH	DS			 ;DS is destroyed by next
;						  DOS function call
	      MOV	AH,1BH			 ;Get DOS File Allocation Table
	      INT	21H			 ;information. See 1, A-12
;				     On return, DS:BX points to FAT i.d. byte
;				     AL = number of sectors per cluster
;				     CX = number of bytes per sector
;				     DX = number of clusters (allocation units)
	      POP	DS			 ;Restore DS to Data Segment
	      XCHG	CX,DX			 ;Swap values in CX and DX
	      XOR	AH,AH			 ;Zero out AH
	      MUL	DX			 ;Multiply the no. of sectors
;						  per cluster (AX) by the
;						  physical sector size (DX)
	      PUSH	AX			 ;Save AX = number of bytes per
;						  cluster
	      PUSH	CX			 ;Save number of clusters (for
;						  future use as loop counter)
	      MOV	AH,2			 ;Read sectors into memory
	      MOV	AL,2			 ;Read two sectors
	      MOV	BX,OFFSET DIR_LIST+800H  ;Point ES:BX to buffer address
;						  at 2K bytes above DIR_LIST
	      XOR	CH,CH			 ;Read track zero
	      MOV	CL,2			 ;Start reading at sector 2
	      XOR	DH,DH			 ;Read side zero (head 0)
	      MOV	DL,FCB_DRIVE		 ;1=A, 2=B, etc.
	      DEC	DL			 ;0=A, 1=B, etc.
	      INT	13H			 ;Diskette I/O BIOS interrupt
	      MOV	AL,DIR_LIST+800H	 ;Store first byte of FAT in
	      MOV	DISK_FORMAT,AL		 ;DISK_FORMAT.	See 1, C-7
	      POP	CX			 ;Number of clusters on disk
	      XOR	AX,AX			 ;AX is used to accumulate the
;						  number of clusters unused or
;						  available on the disk
	      MOV	SI,02			 ;Point to the cluster which
;						  begins mapping of the data
;						  area on the disk (see 1, C-7)
NEXT_CLUSTER:
	      MOV	DI,SI			 ;DI = SI
	      SHR	DI,1			 ;DI = SI/2
	      ADD	DI,SI			 ;DI = 1.5 * SI, i.e. the byte
;						  offset from the beginning of
;						  the FAT corresponding to the
;						  cluster numbered SI
	      MOV	DI,[BX+DI]		 ;The address of the FAT entry
	      TEST	SI,01			 ;If SI is even (right-most
	      JZ	EVEN_CLUSTER		 ;bit is 0), jump
	      SHR	DI,1			 ;Keep the 12 high-order bits
	      SHR	DI,1			 ;of DI.  This is the fastest
	      SHR	DI,1			 ;way to shift right 4 times,
	      SHR	DI,1			 ;dividing DI by 16 (8 clocks)
EVEN_CLUSTER:					 ;See 1, C-8, C-9
	      AND	DI,0FFFH		 ;Check 12 low-order bits for
	      JNZ	IN_USE			 ;000 (unused space)
	      INC	AX			 ;AX = number of clusters not
;						  in use on the disk
IN_USE:
	      INC	SI			 ;Point to the next cluster in
;						  the File Allocation Table
	      LOOP	NEXT_CLUSTER
	      POP	DX			 ;DX = number of bytes per
;						  cluster
	      MUL	DX			 ;AX = number of bytes not in
;						  use on the disk. DX is set to
;						  most-significant word of
;						  AX*DX result. See 2, 6-114
	      MOV	FREE_SPACE,AX		 ;Save least-significant and
	      MOV	FREE_SPACE+02,DX	 ;most-sig. words of FREE_SPACE
	      PUSH	DS			 ;Save pointer to Data Seg
	      XOR	AX,AX
	      MOV	DS,AX			 ;Point DS to interrupt vectors
	      MOV	SI,[DS:INT24_SEG]	 ;Segment address for INT 24H
	      MOV	DI,[DS:INT24_OFF]	 ;Offset address for INT 24H
	      PUSH	DS			 ;Save interrupt vector DS
	      PUSH	CS
	      POP	DS			 ;Point DS to Code Segment
	      MOV	DX,OFFSET INT_24H	 ;Set interrupt vector for
	      MOV	AL,24H			 ;INT 24H to point to service
	      MOV	AH,25H			 ;routine in our Code Segment
	      INT	21H			 ;See 1, D-28
	      POP	DS			 ;Reset DS to interrupt vectors
	      MOV	AH,0DH			 ;Disk reset - flushes all file
	      INT	21H			 ;buffers. See 1, D-20
	      MOV	[DS:INT24_SEG],SI	 ;Reset interrupt vector for
	      MOV	[DS:INT24_OFF],DI	 ;INT 24H to original address
	      POP	DS			 ;Restore DS to Data Segment
	      RET
GET_FAT_INFO  ENDP
;
; Replacement for DOS interrupt 24H, used in PROC GET_FAT_INFO:
;
INT_24H       PROC	NEAR
	      STI				 ;Set Interrupt Flag
	      XOR	AX,AX			 ;Zero out value of AX
	      IRET				 ;Interrupt Return
INT_24H       ENDP
;
; Display the title line, including the requested drive and the available space
; on the disk. The value in FREE_SPACE must be converted from a 4-byte binary
; number to a multi-byte ASCII unpacked number before it can be displayed !!
;
DISPLAY_TOP   PROC	NEAR
	      MOV	DX,OFFSET CR_LF
	      MOV	AH,9			 ;Print string at DX,
	      INT	21H			 ;terminated by $ sign
;
; Print the title line, including the requested drive and the unused
; (available) space on the diskette.
;
	      MOV	DX,OFFSET TLINE_1	 ;Display the first part of the
	      MOV	AH,9			 ;display line (up to the no.
	      INT	21H			 ;of bytes of free space)
	      MOV	SI,FREE_SPACE		 ;Create an ASCII string of
	      MOV	DI,FREE_SPACE+02	 ;decimal numbers equivalent to
	      CALL	BIN_TO_ASCII		 ;the binary number stored in
;						  SI:DI, and display the string
	      MOV	AH,2AH			 ;Get cur. date. Date returned
	      INT	21H			 ;in CX:DX. See 1, D-31
	      MOV	AL,DH			 ;Move month (1-12) into AL
	      AAM				 ;ASCII adjust for multiply
	      XCHG	AL,AH			 ;Put least-sig. byte first
	      OR	CURMONTH,AX		 ;Store month in display line
	      MOV	AL,DL			 ;Move day (1-31) into AL
	      AAM
	      XCHG	AL,AH
	      OR	CURDAY,AX		 ;Store day in display line
	      MOV	AX,CX			 ;Move year (1980-2099) into AL
	      SUB	AX,1900 		 ;Convert to (80-199)
	      AAM
	      XCHG	AL,AH
	      OR	CURYEAR,AX		 ;Store year in display line
	      MOV	AH,2CH			 ;Get cur. time. Time returned
	      INT	21H			 ;in CX:DX. See 1, D-31
	      MOV	AL,CH			 ;Move hours (0-23) into AL
	      AAM
	      XCHG	AL,AH
	      OR	CURHOUR,AX		 ;Store hours in display line
	      MOV	AL,CL			 ;Move minutes (0-59) into AL
	      AAM
	      XCHG	AL,AH
	      OR	CURMIN,AX		 ;Store minutes in display line
;
	      MOV	DX,OFFSET TLINE_3	 ;Display some more of the
	      MOV	AH,9			 ;title line
	      INT	21H
;
; Determine the disk format and display the appropriate message:
;
	      MOV	BL,DISK_FORMAT
	      MOV	SI,OFFSET FAT_FLAG
	      CLD				 ;Set 'forward' LODSB operation
NEXT_FORMAT:
	      LODSB				 ;Load byte addressed by SI
;						  into AL and increase SI
	      OR	AL,AL			 ;If zero, we didn't match up
;						  any valid format type
	      JZ	SHORT DISP_FORMAT
	      CMP	AL,BL			 ;Check byte from table against
;						  first byte from FAT
	      JZ	SHORT DISP_FORMAT
	      ADD	SI,02			 ;Skip past the improper msg.
	      JMP	NEXT_FORMAT		 ;Check for next format type
DISP_FORMAT:
	      LODSW				 ;Load word addressed by SI
;						  into AX and increase SI, i.e.
	      MOV	DX,AX			 ;Pick up message address,
	      MOV	AH,9			 ;store address in DX, and
	      INT	21H			 ;display the message
	      MOV	DX,OFFSET CR_LF 	 ;Carriage return, line feed
	      MOV	AH,9
	      INT	21H
; Display the column titles on the left-half of the screen and, if
; appropriate, also display them on the right-half of the screen
;
	      MOV	DX,OFFSET COL_TITLES
	      MOV	AH,9
	      INT	21H
	      CMP	WORD PTR  MID_ENTRY,0	 ;Are there any entries for the
	      JZ	LINE_FEED		 ;right-half of the screen?
	      MOV	DX,OFFSET TAB_8
	      MOV	AH,9
	      INT	21H
	      MOV	DX,OFFSET COL_TITLES
	      MOV	AH,9
	      INT	21H
LINE_FEED:
	      MOV	DX,OFFSET SKIP_1
	      MOV	AH,9
	      INT	21H
	      RET
DISPLAY_TOP   ENDP
;
; Display the lines containing the filenames, their sizes, and dates of last
; update. File sizes are stored as 4-byte binary numbers. The least-significant
; part of the number is in the first 2 bytes.
;
LIST_ENTRIES  PROC	NEAR
NEXT_LINE:
	      MOV	BX,FIRST_ENTRY		 ;Point BX to the LHS entry to
	      OR	BX,BX			 ;be displayed
	      JZ	END_OF_LIST
	      MOV	AX,[BX] 		 ;Adjust FIRST_ENTRY so that it
	      MOV	FIRST_ENTRY,AX		 ;points to the following entry
;						  to be displayed on the LHS
	      CALL	DISPLAY_ENTRY
	      MOV	BX,MID_ENTRY		 ;Point BX to the RHS entry to
	      OR	BX,BX			 ;be displayed
	      JZ	END_OF_LINE
	      MOV	DX,OFFSET TAB_6 	 ;Leave 6 spaces down the
	      MOV	AH,9			 ;middle of the screen,
	      INT	21H			 ;separating LHS and RHS.
	      MOV	AX,[BX] 		 ;Adjust MID_ENTRY so that it
	      MOV	MID_ENTRY,AX		 ;points to the following entry
;						 ;to be displayed on the RHS
	      CALL	DISPLAY_ENTRY
END_OF_LINE:
	      MOV	DX,OFFSET CR_LF
	      MOV	AH,9
	      INT	21H
	      JMP	NEXT_LINE
END_OF_LIST:
	      RET
LIST_ENTRIES  ENDP
;
; Reset the default drive to its original status, as saved in DEF_DRIVE:
;
RESET_DRIVE   PROC	NEAR
	      MOV	DL,DEF_DRIVE		 ;Reset the default drive to
	      MOV	AH,0EH			 ;the value stored in DEF_DRIVE
	      INT	21H
	      RET
RESET_DRIVE   ENDP
;
; Display the filename, extension, size, and date of last update for the file
; in DIR_LIST pointed to by BX.
;
DISPLAY_ENTRY PROC	NEAR
	      MOV	CX,12
	      XOR	DI,DI
NEXT_CHAR:
	      MOV	DL,[BX+DI+02]		 ;Display the filename, period
	      MOV	AH,02			 ;separator, and file extension
	      INT	21H			 ;(12 bytes), 1 byte at a time
	      INC	DI			 ;Point to next character
	      LOOP	NEXT_CHAR		 ;Loop controlled by CX
	      PUSH	BX
	      MOV	SI,[BX+16]		 ;SI = least-significant word &
	      MOV	DI,[BX+18]		 ;DI = most-significant word of
;						  binary number to be unpacked
	      CALL	BIN_TO_ASCII
	      POP	BX
	      MOV	DX,OFFSET TAB_4 	 ;Leave 4 spaces between the
	      MOV	AH,9			 ;file size and the date of
	      INT	21H			 ;last update
	      MOV	AX,[BX+14]		 ;AX = the date of last update
	      CALL	DISPLAY_DATE
	      RET
DISPLAY_ENTRY ENDP
;
; Interpret AX as a date and display it on the screen as an 8 character string:
;				(MM/DD/YY)
;
DISPLAY_DATE  PROC	NEAR
	      OR	AX,AX
	      JNZ	REAL_DATE
	      MOV	DX,OFFSET TAB_8 	 ;Word at AX is all-zero so
	      MOV	AH,9			 ;display 8 blanks instead of
	      INT	21H			 ;a real date value
	      RET
REAL_DATE:
	      PUSH	AX			 ;Save date value stored in AX
	      AND	AX,0000000111100000B	 ;Extract the month value from
	      MOV	CL,5			 ;the date. See 1, C-5
	      SHR	AX,CL			 ;Get rid of 5 least-sig. bits
	      CALL	DISPLAY_CHARS
	      MOV	DL,'/'                   ;Display the slash separator
	      MOV	AH,2			 ;between the month and day
	      INT	21H			 ;of last update
	      POP	AX			 ;Restore date value to AX
	      PUSH	AX			 ;and save it again
	      AND	AX,0000000000011111B	 ;Extract the day value from
	      CALL	DISPLAY_CHARS		 ;the date. No shift necessary
	      MOV	DL,'/'
	      MOV	AH,2			 ;Slash is between day and year
	      INT	21H			 ; of last update
	      POP	AX
	      AND	AX,1111111000000000B	 ;Extract the year value from
	      MOV	CL,9			 ;the date.  Get rid of the 9
	      SHR	AX,CL			 ;least-significant bits in AX
	      ADD	AX,80			 ;Zero value corresponds to
	      CALL	DISPLAY_CHARS		 ;1980.  See 1, C-5
	      RET
DISPLAY_DATE  ENDP
;
; Convert the binary number stored in AX to a decimal number and display it as
; 2 digits.
;
DISPLAY_CHARS PROC NEAR
	      AAM
	      OR	AX,3030H		 ;30H = 48 = ASCII value of 0
	      PUSH	AX
	      MOV	DL,AH			 ;Display first digit of date
	      MOV	AH,2			 ;field
	      INT	21H
	      POP	AX
	      MOV	DL,AL			 ;Display second digit of date
	      MOV	AH,2			 ;field
	      INT	21H
	      RET
DISPLAY_CHARS ENDP
;
; Convert the 2-word binary number stored in SI:DI (where SI contains the
; least-significant part of the binary number and DI contains the
; most-significant part) into a multi-byte string containing the ASCII
; representation of the corresponding decimal number, and display the ASCII
; string on the screen at the current cursor location.
;
BIN_TO_ASCII  PROC	NEAR
	      XOR	AX,AX
	      MOV	BX,AX
	      MOV	BP,AX
	      MOV	CX,32			 ;32 bits to convert
NEXT_BIT:
	      SHL	SI,1
	      RCL	DI,1
	      XCHG	AX,BP
	      CALL	BTOA_SUBR4
	      XCHG	AX,BP
	      XCHG	AX,BX
	      CALL	BTOA_SUBR4
	      XCHG	AX,BX
	      ADC	AL,0
	      LOOP	NEXT_BIT
	      MOV	CX,1B10H
	      CALL	BTOA_SUBR1
	      MOV	AX,BX
	      CALL	BTOA_SUBR1
	      MOV	AX,BP
	      CALL	BTOA_SUBR1
	      RET
BIN_TO_ASCII  ENDP
;
;
;
BTOA_SUBR1    PROC	NEAR
	      PUSH	AX
	      MOV	DL,AH
	      CALL	BTOA_SUBR2
	      POP	DX
	      CALL	BTOA_SUBR2
	      RET
BTOA_SUBR1    ENDP
;
;
;
BTOA_SUBR2    PROC	NEAR
	      MOV	DH,DL
	      SHR	DL,1
	      SHR	DL,1
	      SHR	DL,1
	      SHR	DL,1
	      CALL	BTOA_SUBR3
	      MOV	DL,DH
	      CALL	BTOA_SUBR3
	      RET
BTOA_SUBR2    ENDP
;
;
;
BTOA_SUBR3    PROC	NEAR
	      AND	DL,0FH
	      JZ	SHORT	  TARGET
	      MOV	CL,0
TARGET:
	      DEC	CH
	      AND	CL,CH
	      OR	DL,48			 ;48 = ASCII value of zero
	      SUB	DL,CL			 ;Display the character in DX
	      MOV	AH,2			 ; on the screen
	      INT	21H
	      RET
BTOA_SUBR3    ENDP
;
;
;
BTOA_SUBR4    PROC	NEAR
	      ADC	AL,AL
	      DAA
	      XCHG	AL,AH
	      ADC	AL,AL
	      DAA
	      XCHG	AL,AH
	      RET
BTOA_SUBR4    ENDP
MAIN	      ENDP
CODE	      ENDS
;
STACK	      SEGMENT	PARA	  STACK 'CODE'
	      DB	128  DUP  (0)		 ;128 Byte Stack Space
STACK	      ENDS
	      END	MAIN
        WB      WB                