title MENU
z80
aseg
;
; Originally published in Creative Computing, December 1979
; by James J. Frantz who wrote this on May 31, 1979.
; Converted to Z80 code by Frank J. Wancho, August, 1980, and
; removed the BASIC dependencies - i.e. .COM files are the only
; file type examined and displayed.
;
; This program is designed to be automatically executed
; by CP/M immediately after a cold (or warm) boot.  This
; program then displays the contents of the disk in a
; menu fashion.  All files of specified type are sorted and
; displayed alphabetically in four columns.  The user then
; selects the desired program by its menu number.  The
; selected program is then run.
;
PAGE
       org     0100h
;
; First, the CP/M "Search" command is used to find the
; file of the specified type.  The pointer to the File
; Control Block is put in <DE>, and the command number is
; put in <C>.  The File Control Block is pre-constructed to
; the form '????????XXX0'.  The XXX is the specified file
; 'type', and the '?' force a match to any file name of that
; file type.
;
start   equ     $
       ld      sp,stack        ; Set up a stack
again   equ     $
       ld      c,17            ; 'Search First' Comand
;
; This next routine sorts the file names as they are found
; on the disk directory.  A name is read from the disk and
; its location is found in the directory table by comparing
; alphabetically.
;
sortlp  equ     $
       ld      de,srcfcb       ; Point File Control Block
       call    bdos            ; Use CP/M entry point
;
; CP/M returns the disk address of the next match in <A>.
; This is a value between 0 and 64, or -1 if no match was
; found.  Test for -1 and quit when no more files of the
; specified file 'type' are found on the disk.  The disk
; address is converted to a pointer to the file name
; within the sector by multiplying by 32 and adding the
; base address of the sector.
;
       cp      255             ; Test for -1
       jp      z,assign        ; Print empty menu
       rrca                    ; This is the same as
       rrca                    ; 5 ADD A's
       rrca
       and     60h             ; Mask correct bits
       add     a,80h           ; Add base address (0080h)
       ld      e,a             ; Put pointer in <DE>
       ld      d,0             ; as a 16 bit value
       ld      hl,dirtab       ; Point to start of table of
                               ; sorted names
       inc     de              ; Point past erase field
cmplop  equ     $
       push    de              ; Save pointer to next
                               ; entry from disk directory
       ld      c,8             ; Length of compare
       push    hl              ; Save pointer to table
cmp1    equ     $
       ld      a,(de)          ; Get trial name char
       cp      (hl)            ; Match?
       jr      nz,endcmp       ; If not, try next entry
       inc     hl              ; Advance pointers
       inc     de
       dec     c
       jr      nz,cmp1         ; Keep testing
endcmp  equ     $
       pop     bc              ; Restore table pointer
       jr      c,insnam        ; Directory name goes in
                               ; front of the current table
                               ; if lower (CY=1)
       ld      hl,14           ; Length of table entry
       add     hl,bc           ; <HL> to next table entry
       pop     de              ; Recover trial name point
       jr      cmplop          ; Loop again
;
; This next portion makes room in the directory table for
; the new entry by moving all alphabetically higher names
; upward in memory.
;
insnam  equ     $
       ld      hl,filcnt       ; Count the number of files
       inc     (hl)            ; to be displayed
       ld      hl,(eot)        ; Get pointer to table end
       ex      de,hl
       ld      hl,14           ; Distance to move
       add     hl,de           ; <HL> point destination
       ld      (eot),hl        ; Save the new End of Table
       inc     hl
       inc     de
;
moveup  equ     $
       dec     de
       dec     hl
       ld      a,(de)          ; Get byte to move
       ld      (hl),a          ; Put in new spot
       ld      a,c             ; Test for done
       cp      e               ; <BC>=<DE>?
       jr      nz,moveup
       ld      a,b
       cp      d
       jr      nz,moveup
       pop     hl              ; Recover pointer
       ld      c,8
       call    blkmov          ; Insert name in table
;
; The menu number field is inserted in the directory table
; at this point but the actual menu number will be assigned
; after all the files are sorted.
;
       ld      hl,menbuf       ; Point menu number block
       ld      c,6             ; Length of move
       call    blkmov          ; Insert text in table
;
; The command number for subsequent searches of the disk
; directory must be altered to cause CP/M to search from
; where it left off.
;
       ld      c,18            ; 'Search Next' command
       jp      sortlp
PAGE
;
;
; This is the second major portion of the program.  At this
; point, all files have been inserted in the directory table
; in alphabetical order.  Now the menu numbers are assigned
; and inserted in the proper place in preparation for
; display on the terminal.
;
assign  equ     $
       ld      a,(filcnt)
       ld      b,a             ; Save in <B>
       push    af              ; and on stack
       ld      c,0             ; Initial file number
       ld      hl,dirtab+11    ; Point first file number
       ld      de,13           ; Offset to other numbers
numfil  equ     $
       ld      a,c             ; Put file number in <A>
       add     a,1             ; Increment
       daa                     ; Decimal convert
       ld      c,a             ; Resave in <C>
       rrca                    ; Get tens digit into
       rrca                    ; proper place
       rrca
       rrca
       and     0fh             ; and mask
       jr      z,useblk        ; Suppress leading zero by
       add     a,10h           ; add either 20H (ASCII ' ')
useblk  equ     $
       add     a,' '           ; or 20H + 10H for numeric
       ld      (hl),a          ; Put in text stream
       ld      a,c             ; Get units portion
       and     0fh             ; Mask off tens portion
       add     a,'0'           ; Convert to ASCII
       inc     hl
       ld      (hl),a
       add     hl,de           ; Repeat until all files
       djnz    numfil          ; are sequentially numbered
;
       pop     af              ; Get FILCNT from stack
       push    af              ; and save again for later
;
; This algorithym ensures the columns are as even in length
; as possible.  Don't worry, it works.
;
       add     a,nbrcol-1
       ld      b,-1            ; <B> accumulates quotient
                               ; So set to -1 for at least
                               ; one pass thru gives 0
divx    equ     $
       inc     b
       sub     nbrcol          ; Divide (FILCNT+3) by
                               ; four to get OFSET1
       jp      p,divx
       add     a,nbrcol        ; Subtracted once too much
                               ; so add it back in
       ld      hl,ofset1
       ld      (hl),b          ; Insert OFSET1 into table
       inc     hl              ; Point OFSET2 location
       jr      nz,setof2       ; Same as OFSET2 if non-
                               ; zero remainder
       dec     b               ; Else OFSET2=OFSET1-1
setof2  equ     $
       ld      (hl),b          ; Put OFSET2 in table
       inc     hl              ; Point OFFSET for Col. 3
       dec     a               ; Test for remainder of 1
       jr      nz,setof3       ; If remainder <> 1, use
                               ; OFSET3=OFSET2
       dec     b               ; Else OFSET3=OFSET2-1
setof3  equ     $
       ld      (hl),b          ; Offset to Col. 4
;
; Now that the offsets for the columns have been determined
; the actual print out can be made
;
reprt   equ     $
       pop     af              ; Recover FILCNT
reprt1  equ     $
       push    af              ; Save again for later use
       ld      (filcnt),a      ; Save for counting
       ld      a,scrhgt        ; Set for video display size
       ld      (lincnt),a
       call    clear           ; Clear screen
       ld      de,hdg
       call    print
;
       ld      hl,dirtab-14    ; Point dummy 0th entry
;
prtlin  equ     $
       push    hl              ; Save base address
       ld      de,ofset0       ; Point offset table
       ld      a,nbrcol        ; 4 columns per line
prtnam  equ     $
       ld      (colcnt),a      ; Save count of columns
       push    hl              ; Save current name pointer
       push    de              ; Save offset table pointer
;
       ld      de,trplsp       ; Print 3 blanks
       call    print           ; Use CP/M facility
       pop     de              ; Get offset table pointer
       pop     hl              ; Get name pointer
       ld      a,(de)          ; Get offset value
;
; The offset value is the number of file names from the
; current name pointer to move the print buffer.
;
       ld      bc,14           ; Each name is 14 long
mult14  equ     $
       add     hl,bc           ; Add 14 for each offset
       dec     a               ; Until offset = 0
       jr      nz,mult14
       push    hl              ; Save new name pointer
       push    de              ; Save offset pointer
       ex      de,hl           ; Point name to print w/<DE>
       call    print           ; Print the file name and
                               ; its menu number
;
tesfin  equ     $
       ld      hl,filcnt       ; See if done printing
       dec     (hl)            ; by testing count of files
       pop     de              ; Get offset pointer
       pop     hl              ; Get pointer to last name
       jr      z,finish        ; No more to print
       inc     de              ; Advance offset pointer
       ld      a,(colcnt)
       dec     a               ; See if columns left = 0
       jr      nz,prtnam       ; Print another same line
       call    crlf            ; Move to next line
       pop     hl              ; Get base of previous line
       ld      de,14           ; Add offset
       add     hl,de
       jr      prtlin
;
; The file names and their menu numbers have been printed.
; This next loop outputs sufficient linefeeds to put the
; heading at the top of a 16 line video display (thereby
; clearing the screen), and puts the request for user
; selection at the bottom of the screen.
;
;
finish  equ     $
       pop     hl              ; Unjunk stack
lfloop  equ     $
       call    crlf
       jp      p,lfloop        ; Omit this line if desired
       ld      de,prompt       ; Point instruction msg
       call    print           ; Again CP/M prints message
       ld      de,inbuf
       ld      a,10            ; Ten characters max
       ld      (de),a
       ld      c,10            ; 'Read Buffer' Command
       call    bdos
;
; On return from BDOS line input function, the digits
; typed by the user are in the buffer at INBUF+2.
; Convert to binary
;
       ld      hl,inbuf+1      ; Point character count
       ld      a,(hl)          ; Get it and see if > 2
       cp      3
       jr      nc,reprt        ; Reprint the menu
       ld      c,a             ; Count of digits to <C>
       ld      b,0             ; Zero <B>
;
getnum  equ     $
       inc     hl              ; Point ASCII digit
       ld      a,(hl)          ; Get it
       call    ascbin          ; Convert to binary
       jr      c,reprt         ; Redisplay on error
       dec     c
       jr      nz,getnum
;
; <B> has the menu number.  To be sure this is still a legal menu
; request, compare with FILCNT (still in stack).
;
       pop     af              ; Recover file count
       cp      b               ; FILCNT-request number
       jp      c,reprt1        ; Redisplay menu if illegal
;
;
       ld      de,14           ; Increment between names
       ld      hl,dirtab-14    ; Point dummy 0th entry
finame  equ     $
       add     hl,de           ; Add offset <B> times
       djnz    finame
;
; At this point <HL> points to the selected file name.  Now find
; the address of CP/M so the proper command name and the selected
; filename can be put into the command buffer.
;
       ex      de,hl           ; Save pointer to file name
       ld      hl,(6)          ; Get BDOS entry point
       ld      bc,-ccplen      ; Offset to start of CP/M
       add     hl,bc
       push    hl              ; Save CP/M entry point
                               ; on stack for branch.
       ld      bc,7            ; Offset to command buffer
       add     hl,bc           ; <HL> points place to put
                               ; name of .COM file to be executed.
       ld      a,8             ; Get default length of file name
       ld      (hl),a          ; Store in CCP
       inc     hl
       push    de              ; Save pointer to file name
       ex      de,hl           ; <DE> points command buffer
;
; Since the scan pointer is not reset by reentry into CP/M,
; the scan pointer must be reset by this program.  The scan
; pointer is stored by CP/M at the end of the command buffer.
;
       ld      hl,128          ; Offset to end of cmd buff
                               ; where pointer is stored
       add     hl,de           ; <HL> points storage place
       ld      (hl),e          ; Update buffer pointer to
       inc     hl              ; to start of the command
       ld      (hl),d          ; buff so CP/M will read.
;
; (The insertion of the BASIC command name goes here.  Removed
; from original version.)
;
       pop     hl              ; Point selected file name
       ld      c,8             ; Length of file name
       call    blkmov
;
; (The insertion of file type goes here if your version of BASIC
; requires it.)
;
       xor     a               ; Need a zero at end
       ld      (de),a          ; of command line
;
; The address of CP/M is on the stack, so a simple RETurn
; will execute CP/M and in turn execute 'filename'.
;
       ret
;
; Subroutines
;
blkmov  equ     $
       ld      a,(hl)
       ld      (de),a
       inc     de
       inc     hl
       dec     c
       jr      nz,blkmov
       ret
;
ascbin  equ     $
       sub     '0'             ; Subtract ASCII bias
       cp      9+1             ; Be sure it's numeric
       ccf
       ret     c               ; Set CY=1 if illegal
       ld      d,a             ; Save in <D>
       ld      a,b             ; Get previous result
       rlca                    ; Multipy by 2
       rlca                    ; then 4
       rlca                    ; then 8
       add     a,b             ; then 9
       ret     c               ; out of bounds
       add     a,b             ; Then finally by 10
       ret     c               ; CY=1 always means error
       add     a,d             ; Add in new result
       ld      b,a             ; Save in <B>
       ret
;
crlf    equ     $
       ld      de,crlfmsg
       call    print
       ld      hl,lincnt
       dec     (hl)
       ret
;
print   equ     $
       ld      c,9             ; Buffer print command
       call    bdos            ; CP/M prints heading
       ret
;
clear   equ     $
       ld      de,clrs
       call    print
       ret
;
crlfmsg equ     $
       defm    0dh,0ah,'$'
clrs    equ     $
       defm    30,30,'$'       ; Clear Screen
hdg     equ     $
       defm    9,9,9,'     MENU',0dh,0ah,0ah,'$'
;
prompt  equ     $
       defm    'Enter MENU Number and press RETURN: $'
;
trplsp  equ     $
       defm    '   $'
;
menbuf  equ     $
       defm    ' -  0$'
;
;
ofset0  equ     $
       defb    1
ofset1  equ     $
       defm    0,0,0
;
eot     equ     $
       defw    dirtab
;
filcnt  equ     $
       defb    0
colcnt  equ     $
       defb    4
lincnt  equ     $
       defb    0
;
srcfcb  equ     $
       defm    0,'????????COM',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;
dirtab  equ     $
       defb    -1              ; Force first file to be put here
;
stack   equ     dirtab+64*14+30
;
inbuf   equ     stack
;
; Equates
;
bdos    equ     5
nbrcol  equ     4
ccplen  equ     3106h-2900h
scrsiz  equ     24
scrhgt  equ     scrsiz-4
       end     start