;
;        MENU.ASM Version 2.0 as of April 26, 1981
;
;    Original Program by: James J. Frantz, May 31, 1979
;
;Menu Program Selection for '.BAS', '.INT' or '.COM' Files
;
;
;         Modified from original by: Kelly Smith
;
;    Version 2.0 contains the following modifications:
;
;(1)  Made  CP/M  2.x compatible;  previous  version  would 
;   "clobber"  the  '$' string delimiter  in  filename  and 
;   would   display   filenames  and  garbage  memory   'ad 
;   nauseum'.
;
;(2)  Added  equates for either MBASIC or  CBASIC  assembly 
;   function, and corresponding 'heading'.
;
;     Note:  You  must  have MBASIC.COM  on  the  currently 
;            logged  diskette if 'mbasic$program' equate is 
;            true, and it will expect files of type '.BAS'. 
;            If 'cbasic$program' equate is true,  you  must 
;            have   CRUN.COM   on  the   currently   logged 
;            diskette,  and  it  will expect files of  type 
;            '.INT'.  If neither is equated to true, '.COM' 
;            filetypes are assumed.
;
;(3)   Added  command  option  to  select  drive  for  user 
;   requested MENU.  (i.e.,  MENU B:<cr>). If corresponding 
;   filetype  is not found for assembled MENU  type  (.BAS, 
;   .INT  or .COM),  MENU will display  "+++ File Not Found 
;   +++" and return to the default drive.
;
;(4) Added "Disk Drive - X:" to heading display,  where 'X' 
;    is the selected drive.
;
;(5)  Added  conditional  assembly  switches  for   'upper' 
;    (uppercase only terminals),  'stdcpm' (standard CP/M), 
;    'altcpm' (alternate CP/M).
;
;(6)  Added conditional assembly switch for 'no$sys$files',     
;    to inhibit display of CP/M 2.X 'SYS' files.  Handy, if 
;    running a Remote CP/M System and you don't want to let 
;    the whole world know whats 'hidden'...
;
;(7)  Stripped  MSB "tag bit" for files set-up  for  Remote 
;    CP/M  Systems as "not for distribution".  Files  would 
;    not  be  sorted  in proper order for  display  without 
;    this.
;
;(8)  Fixed  bug  when MENU is set for BASIC  file  (either 
;    MBASIC or CBASIC) and is in B:  (or greater drive) and 
;    attempting  to load the MBASIC (or CRUN) and THEN  the 
;    '.BAS'  (or '.INT') file...now looks on same  diskette 
;    that MENU is on.
;
;(9)  Added 'widecrt' equate for two CRT  terminal  display 
;    formats. Set  the equate 'widecrt' to true if your CRT 
;    terminal  can  display  80   columns  by  24  rows. If 
;    'widecrt' is false, the default display is 64  columns 
;    by 16 rows.
;
; As supplied,  MENU.ASM is set-up for the following system 
;configuration and display format:
;
;     Lowercase terminal display:   upper = false
;
;     80 column by 24 row display:  widecrt = true
;
;     Standard CP/M:                stdcpm = true
;                                   altcpm = false
;
;     Display '.COM' Menu:          mbasic$program = false
;                                   cbasic$program = false
;
;     No display of 'SYS' files:    no$sys$files = true
;
;
;
;         A Generalized Suggestions for Using MENU
;
; If you have kids that love to play games on your  system, 
;this  is  perhaps the most 'protective' way to keep  there 
;"busy   little   hands"   out  of   the   CP/M   operating 
;system...Just set  them up  with  a  GAMES  diskette  that 
;AUTOLOAD's the MENU program, and never ever again will you 
;hear "Gee Dad, I tried ERA *.* and now nuthi'n works...".
;
;
;
true	equ	-1	;define true
false	equ	not true;define false
;
mbasic$program	equ	false	;true = .BAS
cbasic$program	equ	false	;true = .INT
				;
				;Note: Only one may be true, and
				;      both false = .COM
;
no$sys$files	equ	true	;true = no SYS display, false = SYS display
;
upper	equ	false	;true if uppercase only terminal
widecrt	equ	false	;true if 80 column/24 row terminal
;
stdcpm	equ	true	;true if standard CP/M (base address 0000h)
altcpm	equ	false	;true if alternate CP/M (base address 4200h)
;
	if	stdcpm	;if standard CP/M...
base	equ	0000h	;base for standard CP/M system
	endif		;end if...
;
	if	altcpm	;if alternate CP/M...
base	equ	4200h	;base for H8 or TRS-80 CP/M system
	endif		;end if...
;
bdos	equ	base+5	;CP/M BDOS entry address for function call
tfcb	equ	base+5ch;transient file control block base address
;
nbr$col		equ	4	;number of columns to display

	if	widecrt		;if 80 column/24 row crt terminal...
screen$size	equ	24	;terminal has 24 lines/screen display
screen$hgt	equ	80	;terminal has 80 rows/screen display
	endif			;end if...

	if	not widecrt	;if 64 column/16 row crt terminal...
screen$size	equ	16	;terminal has 16 lines/screen display
screen$hgt	equ	64	;terminal has 64 rows/screen display
	endif			;end if...
;
ccp$len	equ	3c06h-3400h	;calculate CCP length
;
bel	equ	007h		;(^g) bell - human attention required
lf	equ	00ah		;(^j) line feed
cr	equ	00dh		;(^m) carriage return

	org	base+100h

start:	lxi	sp,stack$area	;set up a stack
	lda	tfcb		;get drive specification
	ora	a		;was drive explicity requested?
	jnz	drive$request	;if yes, skip interrogate disk request
	mvi	c,25		;'interrogate disk' function
	call	bdos		;find out which disk we're on...
	inr	a		;make A: = 1
;
drive$request:
;
	adi	'A'-1		;make it ASCII
	sta	driveid		;save the ASCII drive identification
	sbi	'@'		;make it HEX
	sta	srch$fcb	;set selection for requested or default drive
	mvi	c,17		;'search first' command
;
sort$loop:
;
	lxi	d,srch$fcb	;point file control block
	call	bdos		;use CP/M entry point
	ora	a		;test for -1
	jm	assign$menu$nbr	;print empty
	jz	kludge		;do not set file found, if zero
	sta	file$found	;set file found flag
kludge:	rrc			;this is the same as
	rrc			;5 "add a's"
	rrc
	ani	60h 		;mask correct bits
	adi	80h		;add base address (0080h)
	mov	e,a		;put pointer in (de)
	mvi	d,0		;as 16 bit value
	lxi	h,dirtable	;point start of table of
				;sorted names
	inx	d		;point past erase field
;
compare$loop:
;
	push	d		;save pointer to next
				;entry from disk directory
	mvi	c,8		;length of compare
	push	h		;save pointer to table
;
compare1:
;
	ldax	d		;get trial name char
	ani	7fh		;mask-off high bit 'cause may be
				;"tagged" as 'not for distribution' for RCPM's
	cmp	m		;match?
	jnz	end$compare	;if not, try next entry
	inx	h		;advance pointers
	inx	d				
	dcr	c		;one less char to compare	
	jnz	compare1	;keep testing	
;
end$compare:	
;
	pop	b		;restore table pointer
	jc	insert$name	;directory name goes in	
				;front of current table
				;entry if lower (cy = 1)
	lxi	h,14		;length of table entry
	dad	b		;(hl) to next table entry
	pop	d		;recover trailer name point
	jmp	compare$loop	;loop again
;
insert$name:
;
	if	no$sys$files	;if no SYStem files to be displayed...
	push	b		;save pointer to table entry
	push	d		;save pointer to file name
	xchg
	lxi	d,9		;add bias for "SYS" flag character
	dad	d
	mov	a,m		;get character
	ani	80h		;mask for "SYS" flag
	pop	d		;adjust stack, in case we take the next jump
	pop	b		;...or we really need the pointers
	jnz	abort		;abort this filename, if "SYS"
	endif			;end if...

	ldax	d		;get first byte of filename
	ani	7fh		;mask-off high bit 'cause may be
				;"tagged" as 'not for distribution' for RCPM's
	stax	d		;and save back, so proper file sort display
	lxi	h,file$count	;count the number of files
	inr	m		;to be displayed
	lhld	end$of$table	;get pointer to table
	xchg
	lxi	h,14		;distance to move
	dad	d		;(hl) point destination
	shld	end$of$table	;save the new end of table
	inx	h
	inx	d
;
move$up:
;
	dcx	d
	dcx	h
	ldax	d		;get byte to move
	mov	m,a		;put in new spot
	mov	a,c		;test for done
	cmp	e		;(bc) = (de)?
	jnz	move$up
	mov	a,b
	cmp	d
	jnz	move$up
	pop	h		;recover pointer
	mvi	c,8
	call	block$move	;insert name in table
	lxi	h,menu$buff	;point menu number block
	mvi	c,6		;length of movek
	call	block$move	;insert text in table
abort:	mvi	c,18		;'search next' command
	jmp	sort$loop
;
assign$menu$nbr:
;
	lda	file$count
	mov	b,a		;save in (b)
	push	psw		;and on stack
	mvi	c,0		;initial file number
	lxi	h,dirtable+11	;point first file number
	lxi	d,13		;offset to other numbers
;
number$files:
;
	mov	a,c		;put file number in (a)
	adi	1		;increment
	daa			;decimal convert
	mov	c,a		;resave	in (c)
	rrc			;get tens digit into
	rrc			;proper place
	rrc			;
	rrc
	ani	0fh		;add mask
	jz	use$blank	;supress leading zero by
	adi	10h		;add either 20h (ASCII ' ')
;
use$blank:
;
	adi	' '		;or 20h + 10h for numeral
	mov	m,a		;put in text stream
	mov	a,c		;get units portion
	ani	0fh		;mask off tens portion
	adi	'0'		;convert to ASCII
	inx	h			
	mov	m,a
	dad	d		;repeat until all files
	dcr	b		;are sequentially numbered
	jnz	number$files
	pop	psw		;get file$count from stack
	push	psw		;and save again for later
	adi	nbr$col-1
	mvi	b,255		;(b) accumulates quotient
				;so set to -1 for at least
				;1 pass thru gives 0
;
divx:
;
	inr	b
	sui	nbr$col		;divide (file$count+3) by
				;fout to get offset1
	jp	divx
	adi	nbr$col		;substracted once too much
				;so add it back on
	lxi	h,offset1		
	mov	m,b		;insert offset1 into table
	inx	h		;point offset2 location
	jnz	setoffset2	;same as offset1 if non-
				;zero remainder
	dcr	b		;else offset2 = offset1-1
;
setoffset2:
;
	mov	m,b		;put offset2 in table
	inx	h		;point offset	for col 3
	dcr	a		;test for remainder of 1
	jnz	setoffset3	;if remainder <> 1, use
				;offset3 = offset2-1
	dcr	b		;else offset3 = offset2-1y
;
setoffset3:
;
	mov	m,b		;else offset to column 4
;	
reprint:
;
	pop	psw		;recover file count
;
reprint1:	
;
	push	psw		;save again for later use
	sta	file$count	;save for counting	
	mvi	a,screen$hgt	;set for video display size
	sta	line$count
	lda	file$found	;get file$found flag
	ora	a		;and set psw flags
	jz	exit		;if zero, no files of this type found
	lxi	d,heading
	mvi	c,9		;buffer printer command
	call	bdos		;CP/M prints heading
	lxi	h,dir$table-14	;point dummy 0th entry
;	
print$line:
;
	push	h		;save base address
	lxi	d,offset0	;point offset table
	mvi	a,nbr$col	;4 column per line
;
print$name:
;
	sta	column$cnt	;save count of columns
	push	h		;save current name pointer
	push	d		;save offset table pointer

	if	widecrt		;if 80 column/24 row crt terminal...
	lxi	d,doubl$space	;print 2 spaces
	mvi	c,9		;'print buffer' command
	call	bdos		;use CP/M 
	endif			;end if...

	lxi	d,doubl$space	;print 2 spaces
	mvi	c,9		;'print buffer' command
	call	bdos		;use CP/M 
	pop	d		;get offset table pointer
	pop	h		;get name pointer
	ldax	d		;get offset value
	lxi	b,14		;each name is 14 long
;
mult$14:
;
	dad	b		;add 14 for each offset
	dcr	a		;until offset = 0
	jnz	mult$14
	push	h		;save new name pointer
	push	d		;save offset pointer
	xchg			;pointer name to print w/(de)
	mvi	c,9		;print buffer
	call	bdos		;print file name
				;and it's menu number
;
test$finish:
;
	lxi	h,file$count	;see if done printing
	dcr	m		;by testing count of files
	pop	d		;get offset pointer
	pop	h		;get pointer to last name
	jz	finish		;no more to print
	inx	d		;advance offset pointer
	lda	column$cnt
	dcr	a		;see if column left = 0
	jnz	print$name	;print another save line 
	call	crlf			
	pop	h		;get base of previous line
	lxi	d,14		;add offset
	dad	d	
	jmp	print$line
;
finish:
;
	pop	h		;unjunk stack
;
lf$loop:
;
	call	crlf
	lxi	d,prompt	;point instruction message
	mvi	c,9			
	call	bdos
	lxi	d,input$buff
	mvi	a,10		;10 characters maximum
	stax	d
	mvi	c,10		;'read buffer' command
	call	bdos
	lxi	h,input$buff+1	;point to character counter
	mov	a,m		;get it and see if >2 
	cpi	3
	jnc	reprint		;reprint the menu
	mov	c,a		;count of digits to (c)
	mvi	b,0
;
get$menu$nbr:
;
	inx	h		;point ASCII digit
	mov	a,m		;get it
	call	ascii$convert	;convert to binary
	jc	reprint		;re-display on error
	dcr	c		
	jnz	get$menu$nbr
	pop	psw		;recover file counter
	cmp	b		;file count - request number
	jc	reprint1	;redisplay menu if illegal
	lxi	d,14		;increment between names
	lxi	h,dir$table-14	;point dummy 0th entry
;
find$name:
;
	dad	d		;add offset b times
	dcr	b			
	jnz	find$name
	push	d		;found filename, tidy-up screen...
	push	h
	lxi	d,crlfmsg
	mvi	c,9
	call	bdos
	pop	h
	pop	d
	xchg			;save pointer to file name
	lhld	base+6		;get bdos entry point
	lxi	b,-ccp$len	;offset to start of CP/M
	dad	b
	push	h		;save CP/M entry point
				;on stack for branch
	lxi	b,8		;offset to command buffer "autoload"
	dad	b		;(hl) points place to put name of 
				;.BAS, .INT, or .COM file to be
				;executed
	push	d		;save pointer to file name
	xchg			;(de) points to command buffer
	lxi	h,128		;offset to end of command buffer
				;where pointer is stored
	dad	d		;(hl) points to storage place
	mov	m,e		;update buffer pointer to
	inx	h		;the start of the command
	mov	m,d		;buffer so CP/M will read
	lda	driveid		;get selected drive identification
	stax	d		;store at start of command buffer
	inx	d		;bump for ':' delimeter position
	mvi	a,':'		;make delimeter
	stax	d		;and store it...
	inx	d		;bump for destination pointer to filename

	if	mbasic$program or cbasic$program	;if BASIC program...
	lxi	h,command$name	;point command name
	mvi	c,len$cmd$name	;length of command name
	call	block$move
	lda	driveid		;get selected drive identification
	stax	d		;store at start of command buffer
	inx	d		;bump for ':' delimeter position
	mvi	a,':'		;make delimeter
	stax	d		;and store it...
	inx	d		;bump for destination pointer to filename
	endif			;end if...

	pop	h		;point selected file name
	mvi	c,8		;length of file name
	call	block$move

	if	mbasic$program or cbasic$program	;if BASIC program...
	lxi	h, spec$type
	mvi	c,4
	call	block$move 
	endif			;end if...

	xra	a		;needs a 0 at end
	stax	d		;of command line
	ret
;
block$move:
;
	mov	a,m
	stax	d
	inx	d
	inx	h
	dcr	c
	jnz	block$move
	ret
;
ascii$convert:
;
	sui	'0'		;subtract ASCII bias
	cpi	9+1		;be sure it's numeric
	cmc
	rc
	mov	d,a
	mov	a,b
	rlc
	rlc
	rlc
	add	b
	rc
	add	b
	rc
	add	d
	mov	b,a
	ret
;
crlf:
;
	lxi	d,crlfmsg
	mvi	c,9
	call	bdos
	lxi	h,line$count
	dcr	m
	ret
;
exit:	lxi	d,nofile	;indicate no files present
	mvi	c,9
	call	bdos
	jmp	base		;and exit to CP/M via "warm boot"
;
	if	upper		;if uppercase only terminal...
nofile:		db	cr,lf,lf,'+++ FILE NOT FOUND! +++',cr,lf,'$'
	endif

	if	not upper	;if not uppercase only terminal...
nofile:		db	cr,lf,lf,'+++ File Not Found! +++',cr,lf,'$'
	endif

crlfmsg:	db	cr,lf,'$'
;
heading:	db	cr,lf,lf
	if	widecrt and mbasic$program
	db	'                   '
	endif

	if	not widecrt and mbasic$program
	db	'              '
	endif

	if	upper and mbasic$program
	db	'MICROSOFT COMPATIBLE BASIC FILE MENU'
	endif

	if	not upper and mbasic$program
	db	'Microsoft Compatible BASIC File Menu'
	endif

	if	widecrt and cbasic$program
	db	'                '
	endif

	if	not widecrt and cbasic$program
	db	'           '
	endif

	if	upper and cbasic$program
	db	'COMPILER SYSTEMS COMPATIBLE BASIC FILE MENU'
	endif

	if	not upper and cbasic$program
	db	'Compiler Systems Compatible BASIC File Menu'
	endif

	if	widecrt and not mbasic$program and not cbasic$program
	db	'                         '
	endif

	if	not widecrt and not mbasic$program and not cbasic$program
	db	'                    '
	endif

	if	upper and not mbasic$program and not cbasic$program
	db	'CP/M COMMAND FILE MENU'
	endif

	if	not upper and not mbasic$program and not cbasic$program
	db	'CP/M Command File Menu'
	endif

	db	cr,lf

	if	widecrt		;if 80 column/24 row crt terminal...
	db	'                             '
	endif			;end if...

	if	not widecrt	;if 64 column/16 row crt terminal
	db	'                       '
	endif			;end if...

	if	upper	;if uppercase only terminal...
	db	'DISK DRIVE - '
	endif		;end if...

	if	not upper	;if not uppercase only terminal
	db	'Disk Drive - '
	endif			;end if...

driveid:ds	1	;current logged ASCII drive identification

	db	':',cr,lf,lf,'$'

	if	upper		;if uppercase only terminal
prompt:	db	cr,lf,bel,' ENTER MENU NUMBER, AND PRESS RETURN: $'
	endif			;end if...

	if	not upper	;if not uppercase only terminal...
prompt:	db	cr,lf,bel,' Enter menu number, and press return: $'
	endif			;end if...

	if	mbasic$program	;if MBASIC program...
command$name:	db	'MBASIC '	;Microsoft BASIC
;
len$cmd$name	equ	$-command$name
;
spec$type:	db	'.BAS'
	endif			;end if...

	if	cbasic$program	;if CBASIC program...
command$name:	db	'CRUN '	;Compiler Systems BASIC
;
len$cmd$name	equ	$-command$name
;
spec$type:	db	'.INT'
	endif			;end if...

doubl$space:	db	'  $'
;
menu$buff:	db	' - 00$'
;
offset0:	db	1
;
offset1:	db	0,0,0
;
end$of$table:	dw	dirtable
;
file$count:	db	0
;
column$cnt:	db	4
;
line$count:	db	0
;
file$found:	db	0	;file found flag

	if	mbasic$program	;if MBASIC program...
srch$fcb:	db	0,'????????BAS',0,0,0,0
	endif			;end if...

	if	cbasic$program	;if CBASIC program...
srch$fcb:	db	0,'????????INT',0,0,0,0
	endif			;end if...

	if	not mbasic$program and not cbasic$program	;if not BASIC program...
srch$fcb:	db	0,'????????COM',0,0,0,0
	endif			;end if...

dir$table:	db	255
;
stack$area	equ	200*14 + 30
;
input$buff	equ	stack$area
;
	end	start
