	title	'Z80 CMD Processor v1.0 as of 04/13/82'
;
;  CP/M Z80 Command Processor Replacement (CCP) Version 1.0
;
;     ZCMD is a replacement for the standard CCP (by Digital Research).
;ZCMD is based upon ZCPR (by the CCP group) and NZCPR (by the ZCPR group).
;This program has been renamed (again) in an effort to avoid confusion with
;ZCPR version 2.0 (which is by Richard Conn).
;
;This program is released to the Public Domain, it may not be sold or re-sold
;for profit.
;
;Grandparent's history:
;
;  ZCPR version 1.0 was created from CCPZ version 4.0 by RLC in a coordinated
; effort with the CCP-GROUP.  ZCPR was a group effort by CCP-GROUP, whose
; active membership involved in this project consisted of the following:
;    RLC - Richard Conn		KBP - Keith Peterson
;    RGF - Ron Fowler		FJW - Frank Wancho
; The following individuals also provided a contribution:
;    SBB - Steve Bogolub	PST - Paul Traina
;
;  Since RLC has now produced ZCPR version 2.0, in an effor to avoid confusion,
; NZCPR v2.1 has been updated and renamed to ZCMD version 1.0.  --pst
;
; The following individuals have contributed to this program:
;
; SBB - Steve Bogolub	The person that keeps ZCPR neat and not a chaotic mess
;			(fixes my (PST's) bugs and writes great hacks too)
;			(v1.1S, v1.5S (?), v1.6)
; PST - Paul Traina	The silly person behind SECURE mode. Numerous minor
;			re-hacks to make life easier. (a bunch o'bugs too..)
;			(v1.1, v1.3, v1.4, v1.5, v1.7, v2.1)
;
; HLB - Howard Bookser	CAF - Chuch Forsberg	RAF - Bob Fischer
; BB  - Ben Bronson	PRG - Paul Grupp	PJH - Paul Homchick
; HEW - Hal Walchli	DR  - Dave Roznar	HK  - Harry Kaemmerer
; SFK - Sigi Kluger	PP  - Peter Pinchis
;
;   In an attempt to maintain a link to the past, changes between the
; current version of ZCMD will be provided as both a difference file
; between ZCMD's (ZCMD10-11.DIF).  All updates and bug reports should
; be transmitted to either Technical CBBS (in the East) or preferably
; OxGate-001 (in the West).  Make comments or complaints there to PST.
;
;   The most obvious differences between ZCMD and ZCPR are the security
; features, controlled by additional conditional assembly flags. Such
; features restrict access to ZCMD intrinsic commands, add additional
; levels of .COM file searching, and prevent access to higher drives
; or user levels, with either internal or external password control of
; these features. Less obvious differences involve code optimization to
; gain space, and some minor bug fixes in the TYPE command.
;
;************************************************************************
;
FALSE	EQU	0
TRUE	EQU	NOT FALSE
;
;  CUSTOMIZATION EQUATES
;
;  The following equates may be used to customize this CPR for the user's
;    system and integration technique.  The following constants are provided:
;
; REL	  - TRUE if integration is to be done via MOVCPM
;	  - FALSE if integration is to be done via DDT and SYSGEN
;
; SECURE  -  TRUE to conditionally disable potentially-harmful
;	     commands (GO, ERA, SAVE, REN, DFU, GET, JUMP). Under
;	     SECURE, if WHEEL contains RESTRCT, do not accept those
;	     commands, and search for COM files under current user
;	     then user "DEFUSR" only. If WHEEL does not contain
;	     RESTRCT (presumably from passworded change), allow
;	     all commands, and search current user, then last user
;	     set by DFU (originally "RESUSR"), then user "DEFUSR"
;	     for COM files, giving access with password to an
;	     additional level of COM files.
;
;      If you have chosen a SECURE system,  all resident commands may be
; activated by entering:  PASS <password> <cr>  Where <password> is a sequence
; of characters placed at PASSID (if INPASS is true, otherwise, see
; documentation in PST's PASS.ASM).  If the password is incorrect. the system
; will come back with PASS? as if it was looking for a COM file.
; NORM is the reverse of PASS, it will disable the WHEEL mode.
;
; INPASS -  If in the SECURE mode, you wish to use a program similar
;	    to PST's PASS.ASM, set this false, otherwise, ZCMD will
;	    handle the PASSword coding with a built in command.
;
; DRUSER -  Set this EQU false if you wish to disable RAF's neat hack
;	    that allows you the type B: 7 to move to drive B: user area
;	    seven.  This also removes the USER command.  Basically, set
;	    this equate false if you want to use USERPW or some other pgm.
;
; RAS    -  Remote-Access System; setting this equate to TRUE disables
;	    certain CPR commands that are considered harmful in a Remote-
;	    Access environment; use under Remote-Access Systems (RBBS) for
;	    security purposes.  Note: SECURE is the direct enemy of RAS,
;	    DON'T define both equates or you will be VERY sorry.
;	    The advantage SECURE has over RAS is that by saying a magic
;	    word, all of the normal commands pop into existance.
;
; MAXDRIV - Maximum legal drive number stored in this location.
;	    (0 means only A:, etc.)  0000H disables this feature.
;	    The value MAXDR is stuffed into MAXDRIV at cold boot,
;	    and presumably will be changed later by a passworded
;	    program if desired.
;
; USRMAX -  Maximum legal user # + 1 stored in this location. 0000H
;	    disables this feature, and uses the value of MAXUSR+1 instead.
;
; BASE	  - Base Address of user's CP/M system (normally 0 for DR versttp
Dt Vr	HtsEm
	 rttttttREm
tttttttttttttttttttttttttttt,cy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	oy	ok bVtePtem, or by setting the
;	    MSIZE and BIOSEX equates to the system memory size in
;	    K-bytes and the "extra" memory required by your BIOS
;	    in K-bytes. BIOSEX is zero if your BIOS is normal size,
;	    and can be negative if your BIOS is in PROM or in
;	    non-contiguous memory.
;
; EPRMPT  - Set TRUE to be prompted "Ok?" after seeing what files will
;	    be erased. No, this is NOT for individual file prompting,
;	    it is just to confirm deletion of all selected files at once.
;
; CMDTYP  - Set to TRUE to generate code for intrinsic TYPE command.
;
; WSTYPE  - Set to TRUE to generate an extra three lines of code
;	    to correctly interpret the WordStar (tm) internal
;	    end of line hyphen for display, which is the ASCII
;	    NEWLINE code (1FH) and normally non-printing or
;	    troublemaking -- thanks to PJH for this one. CMDTYP
;	    must be TRUE, or this symbol will be ignored.
;
; CMDLST  - Set to TRUE to generate code for intrinsic LIST command.
;           Since almost all of the LIST code is common to the
;	    TYPE code, CMDTYP must be set TRUE as well, or this
;	    Symbol will be ignored.
;
; CMDDIR  - Set to TRUE to generate code for intrinsic DIR command.
;
;  Remember, you only get a total of 2048 (0800h) bytes of space for
; ALL of the generated code, or many other areas of your system
; generation will be affected. For example, to be fully SECURE, you
; would set SECURE to TRUE, and define MAXDRIV and USRMAX, and maybe
; use the internal password by setting INPASS to TRUE (external is
; MUCH recommended for easier modification). Those options absolutely
; generate too much code unless either CMDTYP or CMDDIR or both are
; set FALSE. A system with SECURE set to FALSE is right on the edge,
; and requires a give and take on options to fit, i.e. you can have
; MAXDRIV and USRMAX with DIR and TYPE if you leave out LIST and
; querying on ERASE, and so on.
;
***************************************************************************
** Be careful when playing with different combinations of these equates. **
** You might not have enough memory to some combinations.  Check this    **
** if you have problems, if they still persist, gripe to me (PST).       **
***************************************************************************
;
REL	EQU	FALSE	;Set to true for MOVCPM integration
;
BASE	EQU	0	;Base of CP/M system (set for standard CP/M)
;
	IF	REL
CMDLOC	EQU	0	;MOVCPM image
	ELSE
;
; If REL is FALSE, the value of CMDLOC may be set in one
; of two ways.  The first way is to set MSIZE and BIOSEX
; as described above using the following three lines:
;
;MSIZE	EQU	56			;size of memory in kilobytes
;BIOSEX	EQU	2			;extra # kilobytes in BIOS
;CMDLOC	EQU	3400H+(MSIZE-20-BIOSEX)*1024	;ZCMD origin
;
; The second way is to obtain the origin of your current
; CMD using BDSLOC or its equivalent, then merely set CMDLOC
; to that value as in the following line:
;
CMDLOC	EQU	0DA00H	;Fill in with BDLOC supplied value
;
; Note that you should only use one method or the other.
; Do NOT define CMDLOC twice!
;
; The following gives the required offset to load the CMD into the
; CP/M SYSGEN Image through DDT (the Roffset command); Note that this
; value conforms with the standard value presented in the CP/M reference
; manuals, but it may not necessarily conform with the location of the
; CCP in YOUR CP/M system; several systems (Morrow Designs, P&T, Heath
; Org-0 to name a few) have the CCP located at a non-standard address in
; the SYSGEN Image
;
;CMDR	EQU	0E00H-CMDLOC	;DDT load offset for Apple CP/M 2.20B
;CMDR	EQU	0980H-CMDLOC	;DDT load offset for D.R. standard sysgen
CMDR	EQU	1600H-CMDLOC	;DDT load offset for CompuPro Disk-1
;CMDR	EQU	1100H-CMDLOC	;DDT load offset for Morrow's MicroStuff
	ENDIF
;
;
RAS	EQU	FALSE		;set to true if cmd is for a remote-access
				;system and you don't want to run secure.
;
USRMAX	EQU	0000H		;location of byte in memory containing
				; number of highest allowable user code + 1
				; this value is set by cmd on cold boot,
				; and presumably controlled after that
				; by a password program. if usrmax=0, then
				; maxusr below is used for checking only.
				; 03fh is recommended if used  ***
MAXUSR	EQU	15		;max allowed user number, this + 1 is stuffed
				; into usrmax on cold boot, or used directly
				; if usrmax=0
;
MAXDRIV	EQU	0000H		;location that has max legal drive #
				;set it to zero to disable this crock
				;03dh is recommended if used ***
MAXDR	EQU	1		;max drive # to set into maxdriv on cold boot
;
SECURE	EQU	FALSE		;set true for secure environment...
;
DEFUSR	EQU	0		;default user for unrestricted com files
;
	IF	SECURE
WHEEL	EQU	3EH		;set to "restrct" for limited access
RESTRCT EQU	0		;when (wheel)==restrct, limit commands
RESUSR	EQU	15		;check here for restricted access com files
				;until changed by dfu or warm boot
	ENDIF	;SECURE
;
INPASS	EQU	FALSE		;set true if running secure and not pass.com
;
INUSER	EQU	TRUE		;true to allow USER command and drive/user hack
;
EPRMPT	EQU	FALSE		;true to prompt before erasing all files
;
INTYPE	EQU	TRUE		;true to generate internal TYPE command
WSTYPE	EQU	TRUE		;true to generate wordstar hyphen check (INTYPE
				;must be true too)
INLIST	EQU	TRUE		;true to generate internal LIST command
				;(INTYPE must be true too)
INDIR 	EQU	TRUE		;true to generate internal DIR code
;
;
;  ***  Note to Apple Softcard Users  ***
;
;  In their infinite (?) wisdom (???), Microsoft decided that the way to
; get a two-column directory display instead of four-column (narrow 40-col
; screen, remember) was to have their BIOS poke CCP every time it was
; loaded, if there was no terminal interface card in I/O slot 3.
; Naturally, that will turn into a random poke on any non-standard
; CCP, like this one.  The best way to get this ZCMD up on the Apple is to
; load it into CPM56.COM, at location 0E00H in the image.  The BIOS code
; that pokes the ZCMD can also be modified at that time.  The poke is done
; by "STA 0C8B2H", found at 24FEH in the CPM56 image.  To keep this
; feature, change the 0C8B2H address in that instruction by hand to
; the value generated for the symbol TWOPOK in the DIR routine.  If
; you have assembled out the DIR code by setting CMDDIR to FALSE, then
; disable this feature by changing the "STA 0C8B2H" to three NOP instructions.
; ie: change the contents of location 24FEH to 2500H to 00,00,00. If you wish
; to force a two-column display in all cases, set the NUMCOL equate below to a
; the proper value and disable that poke.
;
NUMCOL	EQU	5	;number of columns in directory display
;
; The following is presented as an option, but is not generally user-customiz-
; able.  A basic design choice had to be made in the design of ZCMD concerning
; the execution of SUBMIT files.  The original CCP had a problem in this sense
; in that it ALWAYS looked for the SUBMIT file from drive A: and the SUBMIT
; program itself (SUBMIT.COM) would place the $$$.SUB file on the currently
; logged-in drive, so when the user was logged into B: and he issued a SUBMIT
; command, the $$$.SUB was placed on B: and did not execute because the CCP
; looked for it on A: and never found it.
;
;	After much debate it was decided to have ZCMD perform the same type of
; function as CCP (look for the $$$.SUB file on A:), but the problem with
; SUBMIT.COM still exists.  Hence, RGF designed SuperSUB and RLC took his
; SuperSUB and designed SUB from it; both programs are set up to allow the
; selection at assembly time of creating the $$$.SUB on the logged-in drive
; or on drive A:.
;
;	A final definition of the Indirect Command File ($$$.SUB or SUBMIT
; File) is presented as follows:
;
;		"An Indirect Command File is one which contains
;		 a series of commands exactly as they would be
;		 entered from a CP/M Console.  The SUBMIT Command
;		 (or SUB Command) reads this files and transforms
;		 it for processing by the ZCMD (the $$$.SUB File).
;		 ZCMD will then execute the commands indicated
;		 EXACTLY as if they were typed at the Console."
;
;	Hence, to permit this to happen, the $$$.SUB file must always
; be present on a specific drive, and A: is the choice for said drive.
; With this facility engaged as such, Indirect Command Files like:
;
;		DIR
;		A:
;		DIR
;
; can be executed, even though the currently logged-in drive is changed
; during execution.  If the $$$.SUB file was present on the currently
; logged-in drive, the above series of commands would not work since the
; ZCMD would be looking for $$$.SUB on the logged-in drive, and switching
; logged-in drives without moving the $$$.SUB file as well would cause
; processing to abort.
;
SUBA	EQU	TRUE 	;Set to TRUE to have $$$.SUB always on A:
			;Set to FALSE to have $$$.SUB on the logged-in drive
;
;   The following flag enables extended processing for user-program supplied
; command lines.  This is for Command Level 3 of ZCMD.  Under the current
; ZCMD philosophy, three command levels exist:
;
;	(1) that command issued by the user from his console at the '>' prompt
;	(2) that command issued by a $$$.SUB file at the '$' prompt
;	(3) that command issued by a user program by placing the command into
;	    CIBUFF and setting the character count in CBUFF
;
;   Setting CLEVEL3 to TRUE enables extended processing of the third level of
; ZCMD command.  All the user program need do is to store the command line and
; set the character count; ZCMD will initialize the pointers properly, store
; the ending zero properly, and capitalize the command line for processing.
; Once the command line is properly stored, the user executes the command line
; by reentering the ZCMD through CMDLOC [NOTE:  The C register MUST contain
; a valid User/Disk Flag (see location 4) at this time.]
;
CLEVEL3	EQU	TRUE		;enable command level 3 processing
;
;	Terminal and 'TYPE' customization equates
;
NLINES	EQU	24		;number of lines on crt screen
WIDE	EQU	TRUE		;true if wide dir display
FENCE	EQU	'|'		;sep char between dir files
;
PGDFLT	EQU	TRUE  		;set to false to disable paging by default
PGDFLG	EQU	'P'		;for type command: page or not (dep on pgdflt)
				;  this flag reverses the default effect
;
SYSFLG	EQU	'A' 		;for dir command: list $sys and $dir
;
SOFLG	EQU	'S'		;for dir command: list $sys files only
;
SUPRES	EQU	TRUE 		;supresses user # report for user 0
;
SPRMPT	EQU	'$'		;cmd prompt indicating submit command
CMDMPT	EQU	'>'		;cmd prompt indicating user command
;
NUMBASE	EQU	'H'		;character used to switch from default
				; number base
;
SECTFLG	EQU	'S'		;option char for save command to save sectors
;
; end of customization section
;
CR	EQU	0DH
LF	EQU	0AH
TAB	EQU	09H
FFEED	EQU	0CH
BEL	EQU	07H
;
WBOOT	EQU	BASE+0000H		;cp/m warm boot address
UDFLAG	EQU	BASE+0004H		;user num in high nybble, disk in low
BDOS	EQU	BASE+0005H		;bdos function call entry pt
TFCB	EQU	BASE+005CH		;default fcb buffer
TBUFF	EQU	BASE+0080H		;default disk i/o buffer
TPA	EQU	BASE+0100H		;base of tpa
;
;
; macros to provide z80 extensions
;   macros include:
;
$-MACRO 		;first turn off the expansions
;
;	JR	- jump relative
;	JRC	- jump relative if carry
;	JRNC	- jump relative if no carry
;	JRZ	- jump relative if zero
;	JRNZ	- jump relative if no zero
;	DJNZ	- decrement b and jump relative if no zero
;	LDIR	- mov (hl) to (de) for count in bc
;	LxxD	- load double reg direct
;	SxxD	- store double reg direct
;
;	@GENDD macro used for checking and generating
;	8-bit jump relative displacements
;
@GENDD	MACRO	?DD	;;used for checking range of 8-bit displacements
	IF (?DD GT 7FH) AND (?DD LT 0FF80H)
	DB	100H	;displacement range error on jump relative
	ELSE
	DB	?DD
	ENDIF
	ENDM
;
; Z80 macro extensions
;
JR	MACRO	?N	;;jump relative
	DB	18H
	@GENDD	?N-$-1
	ENDM
;
JRC	MACRO	?N	;;jump relative on carry
	DB	38H
	@GENDD	?N-$-1
	ENDM
;
JRNC	MACRO	?N	;;jump relative on no carry
	DB	30H
	@GENDD	?N-$-1
	ENDM
;
JRZ	MACRO	?N	;;jump relative on zero
	DB	28H
	@GENDD	?N-$-1
	ENDM
;
JRNZ	MACRO	?N	;;jump relative on no zero
	DB	20H
	@GENDD	?N-$-1
	ENDM
;
DJNZ	MACRO	?N	;;decrement b and jump relative on no zero
	DB	10H
	@GENDD	?N-$-1
	ENDM
;
LDIR	MACRO		;;ldir
	DB	0EDH,0B0H
	ENDM
;
LDED	MACRO	?N	;;load de direct
	DB	0EDH,05BH
	DW	?N
	ENDM
;
LBCD	MACRO	?N	;;load bc direct
	DB	0EDH,4BH
	DW	?N
	ENDM
;
SDED	MACRO	?N	;;store de direct
	DB	0EDH,53H
	DW	?N
	ENDM
;
SBCD	MACRO	?N	;;store bc direct
	DB	0EDH,43H
	DW	?N
	ENDM
;
; end of z80 macro extensions
;
;
;**** section 0 ****
;
	ORG	CMDLOC
;
;  Entry points into ZCMD
;
;    if ZCMD is entered at location CMDLOC (at the jump to CMD), then the
; default command in CIBUFF will be processed.  if ZCMD is entered at
; location CMDLOC+3 (at the jump to CMD1), then the default command in
; CIBUFF will not be processed.
;
;    Note:  entry into zcmd in this way is permitted under this version,
; but in order for this to work, CIBUFF and CBUFF must be initialized properly
; and the C register must contain a valid user/disk flag (see location 4: the
; most significant nybble contains the user number and the least significant
; nybble contains the disk number).
;
;    Some user programs (such as SYNONYM3) attempt to use the default
; command facility.  Under the original ccp, it was necessary to initialize
; the pointer after the reserved space for the command buffer to point to
; the first byte of the command buffer.  under current versions, this is
; no longer the case.  The CIBPTR (command input buffer pointer) is located
; to be compatible with such programs (provided they determine the buffer
; length from the byte at mbuff [CMDLOC+6]), but under ZCMD this is
; no longer necessary, since this buffer pointer is automatically
; initialized in all cases.
;
ENTRY:	JMP	CMD	; process potential default command, and set
			; usrmax to maxusr default
	JMP	CMD1	; do not process potential default command
;	
; Buffers et al
;
; Input command line and default command
;
;   The command line to be executed is stored here.  This command line
; is generated in one of three ways:
;
;	(1) by the user entering it through the BDOS READLN function at
;	    the du> prompt [user input from keyboard]
;	(2) by the submit file facility placing it there from a $$$.sub
;	    file
;	(3) by an external program or user placing the required command
;	    into this buffer
;
;   In all cases, the command line is placed into the buffer starting at
; CIBUFF.  This command line is terminated by the last character (not carriage
; return), and a character count of all characters in the command line
; up to and including the last character is placed into location CBUFF
; (immediately before the command line at CIBUFF).  The placed command line
; is then parsed, interpreted, and the indicated command is executed.
; if CLEVEL3 is permitted, a terminating zero is placed after the command
; (otherwise the user program has to place this zero) and the CIBPTR is
; properly initialized (otherwise the user program has to init this ptr).
; If the command is placed by a user program, entering at CMDLOC is enough
; to have the command processed.  Again, under the current ZCMD, it is not
; necessary to store the pointer to CIBUFF in CIBPTR; ZCMD will do this for
; the calling program if CLEVEL3 is made true.
;
;   Warning:  The command line must not exceed buflen characters in length.
; for user programs which load this command, the value of BUFLEN can be
; obtained by examining the byte at MBUFF (CMDLOC+6).
;
BUFLEN	EQU	80			;maximum buffer length
MBUFF:	DB	BUFLEN			;maximum buffer length
CBUFF:	DB	0			;number of valid chars in command line
CIBUFF:	DB	'              '	;default (cold boot) command
;
;  The Copyright notice from Digital Research is genned into the
; stock CCP at this location. In order to avoid the slightest chance
; of legal hassles, we'll leave it in.
;
	DB	'  Copyright (C) 1979, Digital Research  '
CIBUF:	DB	0			;command string terminator
	DS	BUFLEN-($-CIBUFF)+1	;total is 'buflen' bytes
;
CIBPTR:	DW	CIBUFF		;pointer to command input buffer
CIPTR:	DW	CIBUF		;pointer to curr command for
				; error reporting
;
	DS	26		;stack area
STACK	EQU	$		;top of stack
;
; file type for command
;
COMMSG:	DB	'COM'
;
; submit file control block
;
SUBFCB:
	IF	SUBA		;if $$$.sub on a:
	DB	1		;disk name set to default to drive a:
	ENDIF
;
	IF	NOT SUBA	;if $$$.sub on current drive
	DB	0		;disk name set to default to current drive
	ENDIF
;
	DB	'$$$'		;file name
	DB	'     '
	DB	'SUB'		;file type
	DB	0		;extent number
	DB	0		;s1
SUBFS2:	DS	1		;s2
SUBFRC:	DS	1		;record count
	DS	16		;disk group map
SUBFCR:	DS	1		;current record number
;
; command file control block
;
FCBDN:	DS	1		;disk name
FCBFN:	DS	8		;file name
FCBFT:	DS	3		;file type
	DS	1		;extent number
	DS	2		;s1 and s2
	DS	1		;record count
FCBDM:	DS	16		;disk group map
FCBCR:	DS	1		;current record number
;
; other buffers
;
PAGCNT:	DB	NLINES-2	;lines left on page
CHRCNT:	DB	0		;char count for type
QMCNT:	DB	0		;question mark count for fcb token scanner
;
;
; ZCMD starting points.  Note that some CP/M implementations
; require the cold start address to be in the starting page
; of the ccp, for dynamic CCP loading.  CMDTBL was moved for
; this reason.
;
; Set USRMAX and/or MAXDRIV to default values on cold boot
; if required. note that some BIOS implementations will end
; up here instead of at the warm boot, defeating passwording
; of these options. I reccomend that such a bios be fixed.
;
	IF	USRMAX OR MAXDRIV
CMD:
	IF	USRMAX
	MVI	A,MAXUSR+1	;set usrmax on cold boot
	STA	USRMAX
	ENDIF	;usrmax
;
	IF	MAXDRIV
	MVI	A,MAXDR		;set maxdriv on cold boot
	STA	MAXDRIV
	ENDIF	;maxdriv
;
	JR	CMD2		; then proceed
	ENDIF	;usrmax or maxdriv
;
; start command and don't process default command stored
;
CMD1:	XRA	A		;set no default command
	STA	CBUFF
;
; Start command and possibly process default command
;
; Note on modification by RGF: BDOS returns 0FFh in
; accumulator whenever it logs in a directory, if any
; file name contains a '$' in it.  this is now used as
; a clue to determine whether or not to do a search
; for submit file, in order to eliminate wasteful searches.
;
	IF	USRMAX OR MAXDRIV
CMD2:
	ELSE
CMD:
	ENDIF	;usrmax or maxdriv
;
	LXI	SP,STACK	;reset stack
	PUSH	B
	MOV	A,C		;c=user/disk number (see loc 4)
	RAR			;extract user number
	RAR
	RAR
	RAR
	ANI	0FH
	MOV	E,A		;set user number
	CALL	SETUSR
	CALL	RESET		;reset disk system
	STA	RNGSUB		;save submit clue from drive a:
	POP	B
	MOV	A,C		;c=user/disk number (see loc 4)
	ANI	0FH		;extract default disk drive
	STA	TDRIVE		;set it
	JRZ	NOLOG		;skip if 0...already logged
	CALL	LOGIN		;log in default disk
;
	IF	NOT SUBA	;if $$$.sub is on current drive
	STA	RNGSUB		;bdos '$' clue
	ENDIF
;
NOLOG:	LXI	D,SUBFCB	;check for $$$.sub on current disk
RNGSUB	EQU	$+1		;pointer for in-the-code modification
	MVI	A,0		;2nd byte (immediate arg) is the rngsub flag
	ORA	A		;set flags on clue
	CMA			;prepare for coming 'cma'
	CNZ	SEAR1
	CMA			;0ffh is returned if no $$$.sub, so complement
	STA	RNGSUB		;set flag (0=no $$$.sub)
	LDA	CBUFF		;execute default command?
	ORA	A		;0=no
	JRNZ	RS1
;
; prompt user and input command line from him
;
RESTRT:	LXI	SP,STACK	;reset stack
;
; print prompt (du>)
;
	CALL	CRLF		;print prompt
	CALL	GETDRV		;current drive is part of prompt
	ADI	'A'		;convert to ascii a-p
	CALL	CONOUT
	CALL	GETUSR		;get user number
;
	IF	SUPRES		;if suppressing usr # report for usr 0
	ORA	A
	JRZ	RS000
	ENDIF
;
	CPI	10		;user < 10?
	JRC	RS00
	SUI	10		;subtract 10 from it
	PUSH	PSW		;save it
	MVI	A,'1'		;output 10's digit
	CALL	CONOUT
	POP	PSW
RS00:	ADI	'0'		;output 1's digit (convert to ascii)
	CALL	CONOUT
;
; read input line from user or $$$.sub
;
RS000:	CALL	REDBUF		;input command line from user (or $$$.sub)
;
; process input line
;
RS1:
;
	IF	CLEVEL3		;if third command level is permitted
	CALL	CNVBUF		;capitalize command line, place ending 0,
				; and set cibptr value
	ENDIF
;
	CALL	DEFDMA		;set tbuff to dma address
	CALL	GETDRV		;get default drive number
	STA	TDRIVE		;set it
	CALL	SCANER		;parse command name from command line
	CNZ	ERROR		;error if command name contains a '?'
	LXI	D,RSTCMD	;put return address of command
	PUSH	D		;on the stack
	LDA	TEMPDR		;is command of form 'd:command'?
	ORA	A		;nz=yes
	JNZ	COM		; immediately
	CALL	CMDSER		;scan for cmd-resident command
	JNZ	COM		;not cmd-resident
	MOV	A,M		;found it:  get low-order part
	INX	H		;get high-order part
	MOV	H,M		;store high
	MOV	L,A		;store low
	PCHL			;execute cmd routine
;
; entry point for restarting cmd and logging in default drive
;
RSTCMD:	CALL	DLOGIN		;log in default drive
;
; entry point for restarting cmd without logging in default drive
;
RCMDNL:	CALL	SCANER		;extract next token from command line
	LDA	FCBFN		;get first char of token
	SUI	' '		;any char?
	LXI	H,TEMPDR
	ORA	M
	JNZ	ERROR
	JR	RESTRT
;
; no file error message
;
PRNNF:	CALL	PRINTC		;no file message
	DB	'No Fil','e'+80H
	RET
;
; cmd built-in command table
;
NCHARS	EQU	4		;number of chars/command
;
; cmd command name table
;   each table entry is composed of the 4-byte command and 2-byte address
;
CMDTBL:
;
	IF	INPASS AND SECURE
	DB	'PASS'			;enable wheel (sysop) mode
	DW	PASS
	ENDIF	;inpass and secure
;
	IF	INUSER
	DB	'USER'			;change user areas
	DW	USER
	ENDIF	;inuser
;
	IF	INTYPE
	DB	'TYPE'			;type a file to con:
	DW	TYPE
	ENDIF	;intype
;
	IF	INDIR
	DB	'DIR '			;pull a directory of disk files
	DW	DIR
	ENDIF	;indir

NRCMDS	EQU	($-CMDTBL)/(NCHARS+2)	;put any commands that are ok to
					;run when not under wheel mode
					;in front of this label
	IF	INLIST AND INTYPE
	DB	'LIST'			;list file to printer
	DW	LIST
	ENDIF	;inlist and intype
;
	IF	INPASS AND SECURE
	DB	'NORM'			;disable wheel mode
	DW	NORM
	ENDIF	;inpass and secure
;
	IF	NOT RAS			;for non-ras
	DB	'GO  '			;jump to 100h
	DW	GO
	DB	'ERA '			;erase file
	DW	ERA
	DB	'SAVE'			;save memory image to disk
	DW	SAVE
	DB	'REN '			;rename file
	DW	REN
	DB	'DFU '			;set default user
	DW	DFU
	DB	'GET '			;load file into memory
	DW	GET
	DB	'JUMP'			;jump to location in memory
	DW	JUMP
	ENDIF	;not ras
;
NCMNDS	EQU	($-CMDTBL)/(NCHARS+2)
;
;------------------------------------------------------------------------------
;
; I/O Utilities
;
; Output char in reg A to console and don't change BC
;
;
; Output <crlf>
;
CRLF:	MVI	A,CR
	CALL	CONOUT
	MVI	A,LF	;fall thru to conout
;
CONOUT:	PUSH	B
	MVI	C,02H
OUTPUT:	ANI	7FH	;prevent inadvertant graphic output to printers
	MOV	E,A
	PUSH	H
	CALL	BDOS
	POP	H
	POP	B
	RET
;
CONIN:	MVI	C,01H	;get char from con: with echo
	CALL	BDOSB
;
; Convert char in A to upper case
;
UCASE:	CPI	'a'		;lower-case a
	RC
	CPI	7BH		;greater than lower-case z?
	RNC
	ANI	5FH		;capitalize
	RET
;
	IF	INTYPE
LCOUT:
	ENDIF	;INTYPE
;
	IF	INTYPE AND INLIST
	PUSH	PSW		;output char to con: or lst: dep on prflg
PRFLG	EQU	$+1		;pointer for in-the-code modification
	MVI	A,0		;2nd byte (immediate arg) is the print flag
	ORA	A		;0=type
	JRZ	LC1
	POP	PSW		;get char
;
; Output char in reg A to list device
;
LSTOUT:	PUSH	B
	MVI	C,05H
	JR	OUTPUT
LC1:	POP	PSW	;get char
	ENDIF	;INTYPE AND INLIST
;
	IF	INTYPE
	PUSH	PSW
	CALL	CONOUT	;output to con:
	POP	PSW
	CPI	LF	;check for paging
	RNZ		;done if not eol yet
;
;  Count down lines and pause for input (direct) if count expires
;
	PUSH	H
	LXI	H,PAGCNT	;count down
	DCR	M
	JRNZ	PGBAK		;jump if not end of page
	MVI	M,NLINES-2	;refill counter
;
PGFLG	EQU	$+1		;pointer to in-the-code buffer pgflg
	MVI	A,0		;0 may be changed by pgflg equate
	CPI	PGDFLG		;page default override option wanted?
;
	IF	PGDFLT		;if paging is default
	JRZ	PGBAK		;  pgdflg means no paging, please
	ELSE			;if paging not default
	JRNZ	PGBAK		;  pgdflg means please paginate
	ENDIF
;
WTLOOP:	CALL	BREAK		;get char but don't echo to screen
	JRZ	WTLOOP		;nothing there yet.... so loop
	CPI	'C'-'@' 	;^C
	JZ	RSTCMD		;restart cmd

PGBAK:	POP	H		;restore hl
	RET
	ENDIF	;INTYPE
;
READF:	LXI	D,FCBDN ;fall thru to read
READ:	MVI	C,14H	;fall thru to bdosb
;
; Call BDOS and save BC
;
BDOSB:	PUSH	B
	CALL	BDOS
	POP	B
	ORA	A
	RET
;
; Print string ending with zero byte or character high bit set
; pointed to by ret address,  start with <cr><lf>
;
PRINTC:	PUSH	PSW		;save flags
	CALL	CRLF		;new line
	POP	PSW
;
PRINT:	XTHL			;get ptr to string
	PUSH	PSW		;save flags
	CALL	PRIN1		;print string
	POP	PSW		;get flags
	XTHL			;restore hl and ret adr
	RET
;
; Print string ending with zero byte or character high bit set
; pointed to by hl
;
PRIN1:	MOV	A,M		;get next byte
	CALL	CONOUT		;print char
	MOV	A,M		;get next byte again for test
	INX	H		;pt to next byte
	ORA	A		;set flags
	RZ			;done if zero
	RM			;done if msb set
	JR	PRIN1
;
;------------------------------------------------------------------------------
;
; BDOS function routines
;
; Return number of current disk in A
;
GETDRV:	MVI	C,19H
	JR	BDOSJP
;
; Set 80h as DMA address
;
DEFDMA:	LXI	D,TBUFF 	;80h=tbuff
DMASET:	MVI	C,1AH
	JR	BDOSJP
;
RESET:	MVI	C,0DH
BDOSJP:	JMP	BDOS
;
LOGIN:	MOV	E,A		;move desired # to bdos reg
;
	IF	MAXDRIV
	LDA	MAXDRIV		;check for legal drive #
	CMP	E
	JC	ERROR		;don't do it if too high
	ENDIF	;maxdriv
;
	MVI	C,0EH
	JR	BDOSJP		;save some code space
;
OPENF:	XRA	A
	STA	FCBCR
	LXI	D,FCBDN		;fall thru to open
;
OPEN:	MVI	C,0FH		;fall thru to grbdos
;
GRBDOS:	CALL	BDOS
	INR	A		;set zero flag for error return
	RET
;
CLOSE:	MVI	C,10H
	JR	GRBDOS
;
SEARF:	LXI	D,FCBDN		;specify fcb
SEAR1:	MVI	C,11H
	JR	GRBDOS
;
SEARN:	MVI	C,12H
	JR	GRBDOS
;
; Check for submit file in execution and abort it if so
;
SUBKIL:	LXI	H,RNGSUB	;check for submit file in execution
	MOV	A,M
	ORA	A		;0=no
	RZ
	MVI	M,0		;abort submit file
	LXI	D,SUBFCB	;delete $$$.sub
;
DELETE:	MVI	C,13H
	JR	BDOSJP		;save more space
;
; Reset user number if changed
;
RESETUSR:
TMPUSR	EQU	$+1		;pointer for in-the-code modification
	MVI	A,0		;2nd byte (immediate arg) is tmpusr
	MOV	E,A		;place in e
	JR	SETUSR		;then go set user

GETUSR:	MVI	E,0FFH		;get current user number
SETUSR:	MVI	C,20H		;set user number to value in e (get if e=ffh)
	JR	BDOSJP		;more space saving
;
; End of BDOS functions
;
;------------------------------------------------------------------------------
;
;	ZCMD Utilities
;
; Set user/disk flag to current user and default disk
;
SETUD:	CALL	GETUSR		;get number of current user
	ADD	A		;place it in high nybble
	ADD	A
	ADD	A
	ADD	A
	LXI	H,TDRIVE	;mask in default drive number (low nybble)
	ORA	M		;mask in
	STA	UDFLAG		;set user/disk number
	RET
;
; Set user/disk flag to user 0 and default disk
;
SETU0D:
TDRIVE	EQU	$+1		;pointer for in-the-code modification
	MVI	A,0		;2nd byte (immediate arg) is tdrive
	STA	UDFLAG		;set user/disk number
	RET
;
; Input next command to ZCMD
; (This routine determines if a submit file is being processed
; and extracts the command line from it if so or from the user's console)
;
REDBUF:	LDA	RNGSUB		;submit file currently in execution?
	ORA	A		;0=no
	JRZ	RB1		;get line from console if not
	LXI	D,SUBFCB	;open $$$.sub
	PUSH	D		;save de
	CALL	OPEN
	POP	D		;restore de
	JRZ	RB1		;erase $$$.sub if end of file and get cmnd
	LDA	SUBFRC		;get value of last record in file
	DCR	A		;pt to next to last record
	STA	SUBFCR		;save new value of last record in $$$.sub
	CALL	READ		;de=subfcb
	JRNZ	RB1		;abort $$$.sub if error in reading last rec
	LXI	D,CBUFF 	;copy last record (next submit cmnd) to cbuff
	LXI	H,TBUFF 	;  from tbuff
	LXI	B,BUFLEN	;number of bytes
	LDIR
	LXI	H,SUBFS2	;pt to s2 of $$$.sub fcb
	MVI	M,0		;set s2 to zero
	INX	H		;pt to record count
	DCR	M		;decrement record count of $$$.sub
	LXI	D,SUBFCB	;close $$$.sub
	CALL	CLOSE
	JRZ	RB1		;abort $$$.sub if error
	MVI	A,SPRMPT	;print submit prompt
	CALL	CONOUT
	LXI	H,CIBUFF	;print command line from $$$.sub
	CALL	PRIN1
	CALL	BREAK		;check for abort (any char)
;
	IF	CLEVEL3		;if third command level is permitted
	RZ			;if <null> (no abort), return to caller and run
	ENDIF
;
	IF	NOT CLEVEL3	;if third command level is not permitted
	JRZ	CNVBUF		;if <null> (no abort), capitalize command
	ENDIF
;
	CALL	SUBKIL		;kill $$$.sub if abort
	JMP	RESTRT		;restart cmd
;
; Input command line from user console
;
RB1:	CALL	SUBKIL		;erase $$$.sub if present
	CALL	SETUD		;set user and disk
	MVI	A,CMDMPT	;print prompt
	CALL	CONOUT
	MVI	C,0AH		;read command line from user
	LXI	D,MBUFF
	CALL	BDOS
;
	IF	CLEVEL3		;if third command level is permitted
	JMP	SETU0D		;set current disk number in lower params
	ENDIF
;
	IF	NOT CLEVEL3	;if third command level is not permitted
	CALL	SETU0D		;set current disk number if lower params
				; and fall thru to cnvbuf
	ENDIF
;
; Capitalize string (ending in 0) in cbuff and set ptr for parsing
;
CNVBUF:	LXI	H,CBUFF 	;pt to user's command
	MOV	B,M		;char count in b
	INR	B		;add 1 in case of zero
CB1:	INX	H		;pt to 1st valid char
	MOV	A,M		;capitalize command char
	CALL	UCASE
	MOV	M,A
	DJNZ	CB1		;continue to end of command line
CB2:	MVI	M,0		;store ending <null>
	LXI	H,CIBUFF	;set command line ptr to 1st char
	SHLD	CIBPTR
	RET
;
; Check for any char from user console  (ret w/zero set if none)
;
BREAK:	PUSH	D		;save de
	MVI	C,6		;direct console i/o
	MVI	E,0FFH		;input mode
	CALL	BDOSB		;get character (if any)
	POP	D		;restore de
	JNZ	UCASE		;we have something, caseify and return
	RET
;
; Get the requested user number from the command line and validate it.
;
USRNUM:	CALL	NUMBER
;
	IF	USRMAX
	LXI	H,USRMAX	;pt to maxusr + 1
	CMP	M		;new value allowed?
	ELSE
	CPI	MAXUSR+1	;new value allowed?
	ENDIF	;usrmax
;
	RC			;return to caller if so,
				; else flag as error
;
; Invalid command -- print it
;
ERROR:	CALL	CRLF		;new line
	LHLD	CIPTR		;pt to beginning of command line
ERR2:	MOV	A,M		;get char
	CPI	' '+1		;simple '?' if <sp> or less
	JRC	ERR1
	PUSH	H		;save ptr to error command char
	CALL	CONOUT		;print command char
	POP	H		;get ptr
	INX	H		;pt to next
	JR	ERR2		;continue
ERR1:	CALL	PRINT		;print '?'
	DB	'?'+80H
	CALL	SUBKIL		;terminate active $$$.sub if any
	JMP	RESTRT		;restart cmd
;
; Check to see if DE points to delimiter; if so, ret w/zero flag set
;
SDELM:	LDAX	D
	ORA	A		;0=delimiter
	RZ
	CPI	' '		;error if < <sp>
	JRC	ERROR
	RZ			;<sp>=delimiter
	CPI	'='		;'='=delimiter
	RZ
	CPI	'_'		;'_'=delimiter
	RZ
	CPI	'.'		;'.'=delimiter
	RZ
	CPI	':'		;':'=delimiter
	RZ
	CPI	';'		;';'=delimiter
	RZ
	CPI	'<'		;'<'=delimiter
	RZ
	CPI	'>'		;'>'=delimiter
	RET
;
; Advance input ptr to first non-blank and fall through to sblank
;
ADVAN:	LDED	CIBPTR
;
; Skip string pointed to by DE (string ends in 0) until end of string
;   or non-blank encountered (beginning of token)
;
SBLANK:	LDAX	D
	ORA	A
	RZ
	CPI	' '
	RNZ
	INX	D
	JR	SBLANK
;
; Add a to hl (hl=hl+a)
;
ADDAH:	ADD	L
	MOV	L,A
	RNC
	INR	H
	RET
;
; Extract decimal number from command line
;   return with value in reg a (all registers may be affected)
;
NUMBER:	CALL	SCANER		;parse number and place in fcbfn
	LXI	H,FCBFN+10 	;pt to end of token for conversion
	MVI	B,11		;11 chars max
;
; Check for suffix for hexadecimal number
;
NUMS:	MOV	A,M		;get chars from end, searching for suffix
	DCX	H		;back up
	CPI	' '		;space?
	JRNZ	NUMS1		;check for suffix
	DJNZ	NUMS		;count down
	JR	NUM0		;by default, process
NUMS1:	CPI	NUMBASE		;check against base switch flag
	JRZ	HNUM0
;
; Process decimal number
;
NUM0:	LXI	H,FCBFN		;pt to beginning of token
	LXI	B,1100H		;c=accumulated value, b=char count
				; (c=0, b=11)
NUM1:	MOV	A,M		;get char
	CPI	' '		;done if <sp>
	JRZ	NUM2
	INX	H		;pt to next char
	SUI	'0'		;convert to binary (ascii 0-9 to binary)
	CPI	10		;error if >= 10
	JRNC	NUMERR
	MOV	D,A		;digit in d
	MOV	A,C		;new value = old value * 10
	RLC
	RLC
	RLC
	ADD	C		;check for range error
	JRC	NUMERR
	ADD	C		;check for range error
	JRC	NUMERR
	ADD	D		;new value = old value * 10 + digit
	JRC	NUMERR		;check for range error
	MOV	C,A		;set new value
	DJNZ	NUM1		;count down
;
; Return from number
;
NUM2:	MOV	A,C		;get accumulated value
	RET
;
; Number error routine for space conservation
;
NUMERR:	JMP	ERROR		;use error routine - this is relative pt
;
; Extract hexadecimal number from command line
;   return with value in reg a; all registers may be affected
;
HEXNUM:	CALL	SCANER		;parse number and place in fcbfn
HNUM0:	LXI	H,FCBFN		;pt to token for conversion
	LXI	D,0		;de=accumulated value
	MVI	B,11		;b=char count
HNUM1:	MOV	A,M		;get char
	CPI	' '		;done?
	JRZ	HNUM3		;return if so
	CPI	NUMBASE		;done if numbase suffix
	JRZ	HNUM3
	SUI	'0'		;convert to binary
	JRC	NUMERR		;return and done if error
	CPI	10		;0-9?
	JRC	HNUM2
	SUI	7		;a-f?
	CPI	10H		;error?
	JRNC	NUMERR
HNUM2:	INX	H		;pt to next char
	MOV	C,A		;digit in c
	MOV	A,D		;get accumulated value
	RLC			;exchange nybbles
	RLC
	RLC
	RLC
	ANI	0F0H		;mask out low nybble
	MOV	D,A
	MOV	A,E		;switch low-order nybbles
	RLC
	RLC
	RLC
	RLC
	MOV	E,A		;high nybble of e=new high of e,
				;  low nybble of e=new low of d
	ANI	0FH		;get new low of d
	ORA	D		;mask in high of d
	MOV	D,A		;new high byte in d
	MOV	A,E
	ANI	0F0H		;mask out low of e
	ORA	C		;mask in new low
	MOV	E,A		;new low byte in e
	DJNZ	HNUM1		;count down
;
; Return from hexnum
;
HNUM3:	XCHG			;returned value in hl
	MOV	A,L		;low-order byte in a
	RET
;
; Point to directory entry in tbuff whose offset is specified by a and c
;
DIRPTR:	LXI	H,TBUFF 	;pt to temp buffer
	ADD	C		;pt to 1st byte of dir entry
	CALL	ADDAH		;pt to desired byte in dir entry
	MOV	A,M		;get desired byte
	RET
;
; Check for specified drive and log it in if not default
;
SLOGIN:	XRA	A		;set fcbdn for default drive
	STA	FCBDN
	CALL	COMLOG		;check drive
	RZ
	JR	DLOG5		;do login otherwise
;
; Check for specified drive and log in default drive if specified<>default
;
DLOGIN:	CALL	COMLOG		;check drive
	RZ			;abort if same
	LDA	TDRIVE		;log in default drive
;
DLOG5:	JMP	LOGIN
;
; Routine common to both login routines; on exit, z set means abort
;
COMLOG:
TEMPDR	EQU	$+1		;pointer for in-the-code modification
	MVI	A,0		;2nd byte (immediate arg) is tempdr
	ORA	A		;0=no
	RZ
	DCR	A		;compare it against default
	LXI	H,TDRIVE
	CMP	M
	RET			;abort if same
;
; Extract token from command line and place it into fcbdn;
;   format FCBDN FCB if token resembles file name and type (filename.typ);
;   on input, CIBPTR points to char at which to start scan;
;   on output, CIBPTR points to char at which to continue and zero flag is
;     reset if '?' is in token
;
; Entry points:
;	scaner - load token into first fcb
;	scanx  - load token into fcb pointed to by hl
;
SCANER:	LXI	H,FCBDN 	;point to fcbdn
SCANX:	XRA	A		;set temporary drive number to default
	STA	TEMPDR
	CALL	ADVAN		;skip to non-blank or end of line
	SDED	CIPTR		;set ptr to non-blank or end of line
	LDAX	D		;end of line?
	ORA	A		;0=yes
	JRZ	SCAN2
	SBI	'A'-1		;convert possible drive spec to number
	MOV	B,A		;store number (a:=0, b:=1, etc) in b
	INX	D		;pt to next char
	LDAX	D		;see if it is a colon (:)
	CPI	':'
	JRZ	SCAN3		;yes, we have a drive spec
	DCX	D		;no, back up ptr to first non-blank char
SCAN2:	LDA	TDRIVE		;set 1st byte of fcbdn as default drive
	MOV	M,A
	JR	SCAN4
SCAN3:	MOV	A,B		;we have a drive spec
	STA	TEMPDR		;set temporary drive
	MOV	M,B		;set 1st byte of fcbdn as specified drive
	INX	D		;pt to byte after ':'
;
; Extract filename from possible filename.typ
;
SCAN4:	XRA	A		;a=0
	STA	QMCNT		;init count of number of question marks in fcb
	MVI	B,8		;max of 8 chars in file name
	CALL	SCANF		;fill fcb file name
;
; Extract file type from possible filename.typ
;
	MVI	B,3		;prepare to extract type
	CPI	'.'		;if (de) delimiter is a '.', we have a type
	JRNZ	SCAN15		;fill file type bytes with <sp>
	INX	D		;pt to char in command line after '.'
	CALL	SCANF		;fill fcb file type
	JR	SCAN16		;skip to next processing
SCAN15:	CALL	SCANF4		;space fill
;
; Fill in ex, s1, s2, and rc with zeroes
;
SCAN16:	MVI	B,4		;4 bytes
SCAN17:	INX	H		;pt to next byte in fcbdn
	MVI	M,0
	DJNZ	SCAN17
;
; Scan complete -- de pts to delimiter byte after token
;
	SDED	CIBPTR
;
; Set zero flag to indicate presence of '?' in filename.typ
;
	LDA	QMCNT		;get number of question marks
	ORA	A		;set zero flag to indicate any '?'
	RET
;
;  scanf -- scan token pted to by de for a max of b bytes; place it into
;    file name field pted to by hl; expand and interpret wild cards of
;    '*' and '?'; on exit, de pts to terminating delimiter
;
SCANF:	CALL	SDELM		;done if delimiter encountered - <sp> fill
	JRZ	SCANF4
	INX	H		;pt to next byte in fcbdn
	CPI	'*'		;is (de) a wild card?
	JRNZ	SCANF1		;continue if not
	MVI	M,'?'		;place '?' in fcbdn and don't advance de if so
	CALL	SCQ		;scanner count question marks
	JR	SCANF2
SCANF1:	MOV	M,A		;store filename char in fcbdn
	INX	D		;pt to next char in command line
	CPI	'?'		;check for question mark (wild)
	CZ	SCQ		;scanner count question marks
SCANF2:	DJNZ	SCANF		;decrement char count until 8 elapsed
SCANF3:	CALL	SDELM		;8 chars or more - skip until delimiter
	RZ			;zero flag set if delimiter found
	INX	D		;pt to next char in command line
	JR	SCANF3
;
;  Fill memory pointed to by hl with spaces for b bytes
;
SCANF4:	INX	H		;pt to next byte in fcbdn
	MVI	M,' '		;fill filename part with <sp>
	DJNZ	SCANF4
	RET
;
;  Increment question mark count for scanner
;    this routine increments the count of the number of question marks in
;    the current fcb entry
;
SCQ:	LDA	QMCNT		;get count
	INR	A		;increment
	STA	QMCNT		;put count
	RET
;
; CMDTBL (command table) scanner
;   on return, hl pts to address of command if cmd-resident
;   on return, zero flag set means cmd-resident command
;
CMDSER:	LXI	H,CMDTBL	;pt to command table
;
	IF	SECURE
	MVI	C,NRCMDS
	LDA	WHEEL		;see if non-restrcted
	CPI	RESTRCT
	JRZ	CMS1		;pass if restrcted
	ENDIF	;secure
;
	MVI	C,NCMNDS	;set command counter
CMS1:	LXI	D,FCBFN 	;pt to stored command name
	MVI	B,NCHARS	;number of chars/command (8 max)
CMS2:	LDAX	D		;compare against table entry
	CMP	M
	JRNZ	CMS3		;no match
	INX	D		;pt to next char
	INX	H
	DJNZ	CMS2		;count down
	LDAX	D		;next char in input command must be <sp>
	CPI	' '
	JRNZ	CMS4
	RET			;command is cmd-resident (zero flag set)
CMS3:	INX	H		;skip to next command table entry
	DJNZ	CMS3
CMS4:	INX	H		;skip address
	INX	H
	DCR	C		;decrement table entry number
	JRNZ	CMS1
	INR	C		;clear zero flag
	RET			;command is disk-resident (zero flag clear)
;
;------------------------------------------------------------------------------
;
; ZCMD resident commands
;
;Command:	DIR
;Function:	To display a directory of the files on disk
;Forms:
;	DIR <afn>	displays the dir files
;	DIR <afn> S	displays the sys files
;	DIR <afn> A	display both dir and sys files
;
	IF	INDIR
;
DIR:	MVI	A,80H		;set system bit examination
	PUSH	PSW
	CALL	SCANER		;extract possible d:filename.typ token
	CALL	SLOGIN		;log in drive if necessary
	LXI	H,FCBFN 	;make fcb wild (all '?') if no filename.typ
	MOV	A,M		;get first char of filename.typ
	CPI	' '		;if <sp>, all wild
	CZ	FILLQ
	CALL	ADVAN		;look at next input char
	MVI	B,0		;sys token default
	JRZ	DIR2		;jump; there isn't one
	CPI	SYSFLG		;system flag specifier?
	JRZ	GOTSYS		;got system specifier
	CPI	SOFLG		;sys only?
	JRNZ	DIR2
	MVI	B,80H		;flag sys only
GOTSYS:	INX	D
	SDED	CIBPTR
	CPI	SOFLG		;sys only spec?
	JRZ	DIR2		;then leave bit spec unchagned
	POP	PSW		;get flag
	XRA	A		;set no system bit examination
	PUSH	PSW 
DIR2:	POP	PSW		;get flag

DIR2A:				;drop into dirpr to print directory
				; then restart cmd
	ENDIF	;INDIR
;
; directory print routine; on entry, msb of a is 1 (80h) if system files
; excluded. this routine is also used by era.
;
DIRPR:	MOV	D,A		;store system flag in d
	MVI	E,0		;set column counter to zero
	PUSH	D		;save column counter (e) and system flag (d)
	MOV	A,B		;sys only specifier
	STA	SYSTST
	CALL	SEARF		;search for specified file (first occurrance)
	CZ	PRNNF		;print no file msg;reg a not changed
;
; entry selection loop; on entry, a=offset from searf or searn
;
DIR3:	JRZ	DIR11		;done if zero flag set
	DCR	A		;adjust to returned value
	RRC			;convert number to offset into tbuff
	RRC
	RRC
	ANI	60H
	MOV	C,A		;offset into tbuff in c (c=offset to entry)
	MVI	A,10		;add 10 to pt to system file attribute bit
	CALL	DIRPTR
	POP	D		;get system bit mask from d
	PUSH	D
	ANA	D		;mask for system bit
SYSTST	EQU	$+1		;pointer to in-the-code buffer systst
	CPI	0
	JRNZ	DIR10
	POP	D		;get entry count (=<cr> counter)
	MOV	A,E		;add 1 to it
	INR	E
	PUSH	D		;save it
;
TWOPOK	EQU	$+1		;for apple patching
	ANI	NUMCOL-1	;output <crlf> if 4 entries printed in line
;
	PUSH	PSW
	JRNZ	DIR4
	CALL	CRLF		;new line
	JR	DIR5
DIR4:	CALL	PRINT
;
	IF	WIDE
	DB	'  '		;2 spaces
	DB	FENCE		;then fence char
	DB	' ',' '+80H	;then 2 more spaces
	ENDIF
;
	IF	NOT WIDE
	DB	' '		;space
	DB	FENCE		;then fence char
	DB	' '+80H		;then space
	ENDIF
;
DIR5:	MVI	B,01H		;pt to 1st byte of file name
DIR6:	MOV	A,B		;a=offset
	CALL	DIRPTR		;hl now pts to 1st byte of file name
	ANI	7FH		;mask out msb
	CPI	' '		;no file name?
	JRNZ	DIR8		;print file name if present
	POP	PSW
	PUSH	PSW
	CPI	03H
	JRNZ	DIR7
	MVI	A,09H		;pt to 1st byte of file type
	CALL	DIRPTR		;hl now pts to 1st byte of file type
	ANI	7FH		;mask out msb
	CPI	' '		;no file type?
	JRZ	DIR9		;continue if so
DIR7:	MVI	A,' '		;output <sp>
DIR8:	CALL	CONOUT		;print char
	INR	B		;incr char count
	MOV	A,B
	CPI	12		;end of filename.typ?
	JRNC	DIR9		;continue if so
	CPI	09H		;end if filename only?
	JRNZ	DIR6		;print typ if so
	MVI	A,'.'		;print dot between file name and type
	CALL	CONOUT
	JR	DIR6
DIR9:	POP	PSW
DIR10:	CALL	BREAK		;check for abort
	JRNZ	DIR11
	CALL	SEARN		;search for next file
	JR	DIR3		;continue
DIR11:	POP	D		;restore stack
	RET
;
; Fill fcb @hl with '?'
;
FILLQ:	MVI	B,11		;number of chars in fn & ft
FQLP:	MVI	M,'?'		;store '?'
	INX	H
	DJNZ	FQLP
	RET
;
;Command:	ERA
;Function:	Erase files
;Forms:
;	ERA <afn>	erase specified files and print their names
;
	IF	NOT RAS		;not for remote-access system
;
ERA:	CALL	SCANER		;parse file specification
	CPI	11		;all wild (all files = 11 '?')?
	JRNZ	ERA1		;if not, then do erases
	CALL	PRINTC
	DB	'All','?'+80H
	CALL	CONIN		;get reply
	CPI	'Y'		;yes?
ERARJ:	JNZ	RESTRT		;restart cmd if not
	CALL	CRLF		;new line
ERA1:	CALL	SLOGIN		;log in selected disk if any
	XRA	A		;print all files (examine system bit)
	MOV	B,A		;no sys-only opt to dirpr
	CALL	DIRPR		;print directory of erased files
;
	IF	EPRMPT
;
;  query user after files are seen, and give one last chance to back out
;
	MOV	A,E		;how many files displayed?
	ORA	A
	JZ	RESTRT		;if none, don't ask or delete
	CALL	PRINTC		;prompt
	DB	'Ok','?'+80H
	CALL	CONIN		;get reply folded
	CPI	'Y'		;yes?
	JRNZ	ERARJ		;get out if not
	ENDIF	;eprmpt
;
	LXI	D,FCBDN 	;delete file(s) specified
	JMP	DELETE		;restart cmd after delete
;
	ENDIF			;ras
;
;Command:	LIST
;Function:	Print out specified file on the lst: device
;Forms:
;	LIST <ufn>	print file (no paging)
;
	IF	INLIST AND INTYPE
LIST:	MVI	A,0FFh		;turn on printer flag
	JR	TYPE0
	ENDIF	;inlist and intype
;
;Command:	TYPE
;Function:	Print out specified file on the con: device
;Forms:
;	TYPE <ufn>	print file
;	TYPE <ufn> P	print file with paging flag	
;
	IF	INTYPE
TYPE:	
	ENDIF	;intype
;
	IF	INTYPE AND INLIST
	XRA	A		;turn off printer flag
;
; Entry point for ZCMD list function (list)
;
TYPE0:	STA	PRFLG		;set flag
	ENDIF	;intype and inlist
;
	IF	INTYPE
	CALL	SCANER		;extract filename.typ token
	JNZ	ERROR		;error if any question marks
	CALL	ADVAN		;get pgdflg if it's there
	STA	PGFLG		;save it as a flag
	JRZ	NOSLAS		;jump if input ended
	INX	D		;put new buf pointer
	XCHG
	SHLD	CIBPTR

NOSLAS:	CALL	SLOGIN		;log in selected disk if any
	CALL	OPENF		;open selected file
	JZ	TYPE4		;abort if error
	CALL	CRLF		;new line
	MVI	A,NLINES-1	;set line count
	STA	PAGCNT
	LXI	H,CHRCNT	;set char position/count
	MVI	M,0FFH		;empty line
	MVI	B,0		;set tab char counter
TYPE1:	LXI	H,CHRCNT	;pt to char position/count
	MOV	A,M		;end of buffer?
	CPI	80H
	JRC	TYPE2
	PUSH	H		;read next block
	CALL	READF
	POP	H
	JRNZ	TYPE3		;error?
	XRA	A		;reset count
	MOV	M,A
TYPE2:	INR	M		;increment char count
	LXI	H,TBUFF 	;pt to buffer
	CALL	ADDAH		;compute address of next char from offset
	MOV	A,M		;get next char
	ANI	7FH		;mask out msb
	CPI	1AH		;end of file (^Z)?
	RZ			;restart cmd if so
;
; Output char to con: or lst: device with tabulation
;
	IF	WSTYPE		;wordstar hyphen check
	CPI	1FH		;is char wordstar eol hyphen?
	JRNZ	NOHYPH		;pass if not
	MVI	A,'-'		;yes, make it a real hyphen
NOHYPH:	
	ENDIF	;wstype
;
	CPI	' '		;is char control code?
	JRNC	PRT		;go bop char count and print if not
	CPI	CR		;is char a cr?
	JRZ	YESCR		;if so, go zero b then print
	CPI	FFEED		;form feed?
	JRZ	YESCR		;many printers return carriage on this
	CPI	LF		;line feed?
	JRZ	NOBOP		;print, but don't bop b
	CPI	BEL		;bell?
	JRZ	NOBOP		;go ring but don't bop b
	CPI	TAB		;tab?
	JRNZ	TYPE2L		;if not, no other choices, toss control

LTAB:	MVI	A,' '		;<sp>
	CALL	LCOUT
	INR	B		;incr pos count
	MOV	A,B
	ANI	7
	JRNZ	LTAB
	JR	TYPE2L
;
YESCR:	MVI	B,0FFH		;combine with inc below to get zero
;
PRT:	INR	B		;increment char count
NOBOP:	CALL	LCOUT		;print it
;
; Continue processing
;
TYPE2L:	CALL	BREAK		;check for abort
	JRZ	TYPE1		;continue if no char
	CPI	'C'-'@' 	;^c?
	RZ			;restart if so
	JR	TYPE1

TYPE3:	DCR	A		;no error?
	RZ			;restart cmd

TYPE4:	JMP	ERRLOG
	ENDIF	;cmdtyp
;
;Command:	SAVE
;Function:	To save the contents of the TPA onto disk as a file
;Forms:
;	SAVE <number of pages> <ufn>
;				save specified number of pages (start at 100h)
;				from tpa into specified file; <number of
;				pages> is in dec
;	SAVE <number of sectors> <ufn> S
;				like save above, but numeric argument specifies
;				number of sectors rather than pages
;
	IF	NOT RAS		;not for remote-access system
;
SAVE:	CALL	NUMBER		;extract number from command line
	MOV	L,A		;hl=page count
	MVI	H,0
	PUSH	H		;save page count
	CALL	EXTEST		;test for existence of file and abort if so
	MVI	C,16H		;bdos make file
	CALL	GRBDOS
	POP	H		;get page count
	JRZ	SAVE3		;error?
	XRA	A		;set record count field of new file's fcb
	STA	FCBCR
	CALL	ADVAN		;look for 's' for sector option
	INX	D		;pt to after 's' token
	CPI	SECTFLG
	JRZ	SAVE0
	DCX	D		;no 's' token, so back up
	DAD	H		;double it for hl=sector (128 bytes) count
SAVE0:	SDED	CIBPTR		;set ptr to bad token or after good token
	LXI	D,TPA		;pt to start of save area (tpa)
SAVE1:	MOV	A,H		;done with save?
	ORA	L		;hl=0 if so
	JRZ	SAVE2
	DCX	H		;count down on sectors
	PUSH	H		;save ptr to block to save
	LXI	H,128		;128 bytes per sector
	DAD	D		;pt to next sector
	PUSH	H		;save on stack
	CALL	DMASET		;set dma address for write (address in de)
	LXI	D,FCBDN 	;write sector
	MVI	C,15H		;bdos write sector
	CALL	BDOSB		;save bc
	POP	D		;get ptr to next sector in de
	POP	H		;get sector count
	JRZ	SAVE1		;continue if no write error
	JR	PRNLE		;go print error and reset dma
SAVE2:	LXI	D,FCBDN 	;close saved file
	CALL	CLOSE
	INR	A		;error?
	JRNZ	SAVE3		;pass if ok
;
	ENDIF	;not ras

;
;  prnle is also used by memload for tpa full error
;
PRNLE:	CALL	PRINTC		;disk or mem full
	DB	'Ful','l'+80H
;
SAVE3:	JMP	DEFDMA		;set dma to 0080 and restart cmd
				; or return to mlerr
;
	IF	NOT RAS
;
; Test file in FCB for existence, ask user to delete if so, and abort if he
;  choses not to.
;
EXTEST:	CALL	SCANER		;extract file name
	JNZ	ERROR		;'?' is not permitted
	CALL	SLOGIN		;log in selected disk
	CALL	SEARF		;look for specified file
	LXI	D,FCBDN		;pt to file fcb
	RZ			;ok if not found
	PUSH	D		;save ptr to fcb
	CALL	PRINTC
	DB	'Erase','?'+80H
	CALL	CONIN		;get response
	POP	D		;get ptr to fcb
	CPI	'Y'		;key on yes
	JNZ	RSTCMD		;restart if no, sp reset eventually
	PUSH	D		;save ptr to fcb
	CALL	DELETE		;delete file
	POP	D		;get ptr to fcb
	RET
;
	ENDIF			;ras
;
;Command:	REN
;Function:	To change the name of an existing file
;Forms:
;	REN <new ufn>=<old ufn>
;
	IF	NOT RAS		;not for remote-access system
;
REN:	CALL	EXTEST		;test for file existence and return
				; if file doesn't exist; abort if it does
	LDA	TEMPDR		;save current default disk
	PUSH	PSW		;save on stack
REN0:	LXI	H,FCBDN 	;save new file name
	LXI	D,FCBDM
	LXI	B,16		;16 bytes
	LDIR
	CALL	ADVAN		;advance cibptr
	CPI	'='		;'=' ok
	JRNZ	REN4
REN1:	XCHG			;pt to char after '=' in hl
	INX	H
	SHLD	CIBPTR		;save ptr to old file name
	CALL	SCANER		;extract filename.typ token
	JRNZ	REN4		;error if any '?'
	POP	PSW		;get old default drive
	MOV	B,A		;save it
	LXI	H,TEMPDR	;compare it against current default drive
	MOV	A,M		;match?
	ORA	A
	JRZ	REN2
	CMP	B		;check for drive error
	MOV	M,B
	JRNZ	REN4
REN2:	MOV	M,B
	XRA	A
	STA	FCBDN		;set default drive
	LXI	D,FCBDN 	;rename file
	MVI	C,17H		;bdos rename fct
	CALL	GRBDOS
	RNZ
REN3:	CALL	PRNNF		;print no file msg
REN4:	JMP	ERRLOG
;
	ENDIF			;ras
;
;Command:	USER
;Function:	Change current user number
;Forms:
;	USER <unum>	select specified user number;<unum> is in dec
;
	IF	INUSER		;if drive/user code ok...
USER:	CALL	USRNUM		;extract user number from command line
	MOV	E,A		;place user number in e
SUSER:	CALL	SETUSR		;set specified user
	ENDIF	;inuser

RSTJMP:	JMP	RCMDNL		;restart cmd
;
;Command:	DFU
;Function:	Set the default user number for the command/file scanner
;		 Note:  When under secure mode, this will select the second
;			user area to check for programs (normally user 15).
;
;Forms:
;	DFU <unum>	select default user number -- <unum> is in decimal
;
	IF	NOT RAS		;not for remote-access system
DFU:	CALL	USRNUM		;get user number
	STA	DFUSR		;put it away
	JR	RSTJMP		;restart cmd (no default login)
	ENDIF	;not ras
;
;Command:	JUMP
;Function:	To call the program (subroutine) at the specified address
;		without loading from disk.
;Forms:
;	JUMP <adr>		call at <adr>;<adr> is in hex
;
	IF	NOT RAS		;not for remote-access system
;
JUMP:	CALL	HEXNUM		;get load address in hl
	JR	CALLPROG	;perform call
;
	ENDIF			;ras
;
;Command:	GO
;Function:	To call the program in the tpa without loading
;		from disk. same as jump 100h, but much
;		more convenient, especially when used with
;		parameters for programs like stat. also can be
;		allowed on remote-access systems with no problems.
;
;Form:
;	GO <parameters like for command>
;
	IF	NOT RAS		;only if ras
;
GO:	LXI	H,TPA		;always to tpa
	JR	CALLPROG	;perform call
;
	ENDIF			;end of go for ras
;
;Command:	COM file processing
;Function:	To load the specified com file from disk and execute it
;Forms:
;	<command>
;
COM:	LDA	FCBFN		;any command?
	CPI	' '		;' ' means command was 'd:' to switch
	JRNZ	COM1		;not <sp>, so must be transient or error
	LDA	TEMPDR		;look for drive spec
	ORA	A		;if zero, just blank
	JZ	RCMDNL
	DCR	A		;adjust for log in
	STA	TDRIVE		;set default drive
	CALL	LOGIN		;log in drive
	CALL	SETUD		;set drive with current user area
;
	IF	INUSER		;drive/user hackery ok?
	CALL	USRNUM		;get user #, if any
	MOV	E,A		;get it ready for bdos
	LDA	FCBFN		;see if # specified
	CPI	' '
	JRNZ	SUSER		;select if wanted
	ENDIF	;inuser
;
	JMP	RCMDNL		;restart cmd

COM1:	LDA	FCBFT		;file type must be blank
	CPI	' '
	JNZ	ERROR
	LXI	H,COMMSG	;place default file type (com) into fcb
	LXI	D,FCBFT		;copy into file type
	LXI	B,3		;3 bytes
	LDIR
	LXI	H,TPA		;set execution/load address
	PUSH	H		;save for execution
	CALL	MEMLOAD		;load memory with file specified in cmd line
				; (no return if error or too big)
	POP	H		;get execution address
;
; CALLPROG is the entry point for the execution of the loaded
;   program. on entry to this routine, hl must contain the execution
;   address of the program (subroutine) to execute
;
CALLPROG:
	SHLD	EXECADR		;perform in-line code modification
	CALL	DLOGIN		;log in default drive
	CALL	SCANER		;search command line for next token
	LXI	H,TEMPDR	;save ptr to drive spec
	PUSH	H
	MOV	A,M		;set drive spec
	STA	FCBDN
	LXI	H,FCBDN+10H	;pt to 2nd file name
	CALL	SCANX		;scan for it and load it into fcbdn+16
	POP	H		;set up drive specs
	MOV	A,M
	STA	FCBDM
	XRA	A
	STA	FCBCR
	LXI	D,TFCB		;copy to default fcb
	LXI	H,FCBDN 	;from fcbdn
	LXI	B,33		;set up default fcb
	LDIR
	LXI	H,CIBUFF-1
COM4:	INX	H
	MOV	A,M		;skip to end of 2nd file name
	ORA	A		;end of line?
	JRZ	COM5
	CPI	' '		;end of token?
	JRNZ	COM4
;
; Load command line into TBUFF
;
COM5:	MVI	B,-1		;set char count
	LXI	D,TBUFF		;pt to char pos
	DCX	H
COM6:	INR	B		;incr char count
	INX	H		;pt to next
	INX	D
	MOV	A,M		;copy command line to tbuff
	STAX	D
	ORA	A		;done if zero
	JRNZ	COM6
;
; Run loaded transient program
;
COM7:	MOV	A,B		;save char count
	STA	TBUFF
	CALL	CRLF		;new line
	CALL	DEFDMA		;set dma to 0080
	CALL	SETUD		;set user/disk
;
; Execution (call) of program (subroutine) occurs here
;
EXECADR	EQU	$+1		;change address for in-line code modification
	CALL	TPA		;call transient
	CALL	DEFDMA		;set dma to 0080, in case
				;prog changed it on us
	CALL	SETU0D		;set user 0/disk
	CALL	LOGIN		;login disk
	JMP	RESTRT		;restart cmd
;
;Command:	GET
;Function:	To load the specified file from disk to the specified address
;Forms:
;	GET <adr> <ufn>	load the specified file at the specified page;
;			<adr> is in hex
;
	IF	NOT RAS		;not for remote-access system
;
GET:	CALL	HEXNUM		;get load address in hl
	PUSH	H		;save address
	CALL	SCANER		;get file name
	POP	H		;restore address
	JNZ	ERROR		;must be unambiguous
;
; fall thru to memload
;
	ENDIF			;ras
;
; Load memory with the file whose name is specified in the command line
; on input, hl contains starting address to load.
;
; Exit bact to caller if no error. If the COM file is too big, or another
; error, then exit directly to MLERR.
;
MEMLOAD:
	SHLD	LOADADR		;set load address
	CALL	GETUSR		;get current user number
	STA	TMPUSR		;save it for later
	STA	TSELUSR 	;temp user to select
;
; MLA is a reentry point for a non-standard cp/m modification
; this is the return point for when the .COM (or GET) file is not found the
; first time, drive A: is selected for a second attempt
;
MLA:	CALL	SLOGIN		;log in specified drive if any
	CALL	OPENF		;open command.com file
	JRNZ	MLA1		;file found - load it
;
	IF	SECURE
;
; If SECURE is enabled, search the current drive, current user, then
; if in wheel mode, search under last user set by dfu (set to "RESUSR"
; on warm boot) on current drive. If not found, or not in wheel mode,
; then search on current drive under user area "DEFUSR." If file still
; hasn't been found, then do the same thing again except on drive A:
;
DFLAG	EQU	$+1		;mark in-the-code variable
	MVI	A,0		;have we checked this drive already?
	ORA	A
	JRNZ	MLA0		;pass if so to go to drive a:
	LDA	WHEEL		;restricted progs allowed?
	CPI	RESTRCT
	JRZ	MLA00		;pass if not
	PUSH	B		;push bc
	LDA	DFUSR		;load default user
	MOV	B,A		;put it in b
	LDA	TSELUSR		;check curr user
DFUSR	EQU	$+1		;default user location
	CPI	RESUSR		;restricted user?
	MOV	A,B		;assume not
	POP	B		;restore bc
	JRNZ	SETTSE		;go try if not
MLA00:				;ss if not
TSELUSR	EQU	$+1		;mark in-the-code variable
	MVI	A,0		;get curr user
	SUI	DEFUSR		;is it unrestricted com area?
	JRZ	MLA0		;no more choices if so
	STA	DFLAG		;make dflag non-zero if not
	MVI	A,DEFUSR	; and try unrestricted com area
SETTSE:
	ENDIF	;secure
;
	IF	NOT SECURE
DFUSR	EQU	$+1		;mark in-the-code variable
	MVI	A,DEFUSR	;get default user
TSELUSR	EQU	$+1		;mark in-the-code variable
	CPI	DEFUSR		;check for the user area..
	JRZ	MLA0		;..equal default, and jump if so
	ENDIF	;not secure
;
	STA	TSELUSR		;put down new one
	MOV	E,A
	CALL	SETUSR		;go set new user number
	JR	MLA		;and try again
;
; Error routine to select drive a: if default was originally selected
;
MLA0:
	LXI	H,TEMPDR	;get drive from current command
	XRA	A		;a=0
;
	IF	SECURE
	STA	DFLAG		;allow a: search
	ENDIF	;secure
;
	ORA	M
	JNZ	MLERR		;error if already disk a:
	MVI	M,1		;select drive a:
;
	IF	NOT SECURE
	JR	MLA
	ENDIF	;not secure
;
	IF	SECURE
	LDA	TMPUSR		;go to 'current' user code
	JR	SETTSE
	ENDIF	;secure
;
; File found -- proceed with load
;
MLA1:
LOADADR	EQU	$+1
	LXI	H,TPA
ML2:	MVI	A,ENTRY/256-1	;get high-order adr of just below cmd
	CMP	H		;are we going to overwrite the cmd?
	JRC	ML4		;error if so
	PUSH	H		;save address of next sector
	XCHG			;... in de
	CALL	DMASET		;set dma address for load
	LXI	D,FCBDN 	;read next sector
	CALL	READ
	POP	H		;get address of next sector
	JRNZ	ML3		;read error or eof?
	LXI	D,128		;move 128 bytes per sector
	DAD	D		;pt to next sector in hl
	JR	ML2
;
ML3:	DCR	A		;load complete
	JZ	RESETUSR	;if zero, ok, go reset correct user #
				; on way out, else fall thru to prnle
;
;  TPA full
;
ML4:	CALL	PRNLE		;print msg and reset def dma
;
; Transient load error
;
MLERR:				;note that there is an extra return address
				;on the stack.  it will be tossed when error
				;exits to restrt, which reloads sp.
	CALL	RESETUSR	;reset current user number
				;  reset must be done before login
ERRLOG:	CALL	DLOGIN		;log in default disk
	JMP	ERROR		;flag error
;
;PASS:  Enable wheel mode.
;NORM:	Disable wheel mode.
;
;  Type PASS <password> <cr> to CP/M prompt to enter wheel mode.
; This code can be replaced with PST's PASS.ASM which gives many
; nice little options like no keyboard echo, etc.
;
	IF	INPASS			;we want to use this code, not pass.com
PASS:	LXI	H,PASSWD		;set up pointers
	LXI	D,CIBUFF+NCHARS+1
	MVI	B,PASEND-PASSWD		;b= length
CKPASS:	LDAX	D			;trial pw to a
	CMP	M			;check for match
	JNZ	COM			;nope.. look for pass.com
	INX	H			;increment counter
	INX	D
	DJNZ	CKPASS			;continue if more
	MVI	A,NOT RESTRCT		;wheel = not restrct

PWOUT:	STA	WHEEL
	JMP	RESTRT
;
NORM:	MVI	A,RESTRCT
	JR	PWOUT
;
PASSWD:	DB	'YOURPW'		;your password
PASEND:	EQU	$			;end of password
;
	ENDIF	;inpass
;
	END
ry point for a non-standard cp/m modification
; this is the return point for when the .COM (or GET) file is not found the
;