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 Where 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: 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 ; 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 (no abort), return to caller and run ENDIF ; IF NOT CLEVEL3 ;if third command level is not permitted JRZ CNVBUF ;if (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 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 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 < JRC ERROR RZ ;=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 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 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 - 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 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 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 displays the dir files ; DIR S displays the sys files ; DIR 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 , 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 (= counter) MOV A,E ;add 1 to it INR E PUSH D ;save it ; TWOPOK EQU $+1 ;for apple patching ANI NUMCOL-1 ;output 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 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 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 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 print file ; TYPE 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,' ' ; 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 ; save specified number of pages (start at 100h) ; from tpa into specified file; is in dec ; SAVE 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 = ; 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 select specified user number; 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 select default user number -- 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 call at ; 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 ; 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: ; ; COM: LDA FCBFN ;any command? CPI ' ' ;' ' means command was 'd:' to switch JRNZ COM1 ;not , 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 load the specified file at the specified page; ; 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 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 ;