; 
;         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...this   will   also   work   for   MP/M   system 
;     configuration where "tag bit" must be 'Bit 6' (NOT Bit 
;     7)...Bit  7 used as "tag" in MP/M environment fouls up 
;     the 'Control-S' start/stop video scroll  function,  so 
;     some users are switching to Bit 6 as "tag"...
; 
; (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 the little BAST..errr,  pardon  me...set 
; your  "prodigy  child"  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	true	; 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
