;------------------------------ Z40-4.Z80 ------------------------------------ ; Revisions to ZCPR Version 3.4 to make Z40 (C) Copyright Harold F. Bower ; 1992, all rights reserved. ;============================================================================= ; G E N E R A L U T I L I T Y R O U T I N E S ;============================================================================= ; This subroutine checks to see if a program loaded at an address given by HL ; has a Z3ENV header. If the header is not present, the zero flag is reset. ; If it is present, the zero flag is set, and on return HL points to the ; environment-type byte and A contains that byte. Z3CHK: LD DE,Z3ENV+3 ; Point to 'Z3ENV' string in ENV INC HL ; Advance three bytes to possible program INC HL ; ..header INC HL LD B,5 ; Check 5 chars in Z3 ID header Z3CHK1: LD A,(DE) ; Get character from ENV descriptor CP (HL) ; Compare it to loaded file RET NZ ; Quit now if mismatch INC HL ; If same, advance to next characters INC DE ; ..and continue comparing DJNZ Z3CHK1 ; (flags not affected by DJNZ) LD A,(HL) ; Return the environment type in A RET ; Return Z if all 5 characters match ;---------------------------------------- ; Subroutine to skip over spaces in the buffer pointed to by HL. On return, ; the zero flag is set if we encountered the end of the line or a command ; separator character. SKSP1: INC HL ; Point to the next character SKSP: LD A,(HL) ; Get next character CP ' ' ; Space? JR Z,SKSP1 ; If so, keep skipping ; ..else fall through ;-------------------- ; Subroutine to check if character is the command separator or marks the end ; of the line. TSTEOL: LD A,(HL) ; Get next character from command line OR A ; End of command line? RET Z ; Return with zero flag set CP CMDSEP ; Command separator? RET ; Return with flag set appropriately ;---------------------------------------- ; Initialize complete FCB pointed to by DE INITFCB: XOR A LD (DE),A ; Set default disk (dn byte is 0) INC DE ; Point to file name field CALL IFCB ; Fill 1st part of FCB ;..fall through to IFCB to run again ;-------------------- ; Initialize part of FCB whose file name field is pointed to by DE on entry. ; The file name and type are set to space characters; the EX, S2, RC, and the ; following CR (current record ) or DN (disk number) fields are set to zero. ; The S1 byte is set to the current user number. On exit, DE points to the ; byte at offset 17 in the FCB (two bytes past the record count byte). IFCB: LD B,11 ; Store 11 spaces for file name and type LD A,' ' CALL FILL XOR A LD (DE),A ; Set extent byte to zero INC DE LD A,(CURUSR) LD (DE),A ; Set S1 byte to current user INC DE LD B,3 ; Store 3 zeroes XOR A ; Fall thru to fill ;-------------------- ; Fill memory pointed to by DE with character in A for B bytes FILL: LD (DE),A ; Fill with byte in A INC DE ; Point to next DJNZ FILL RET ;---------------------------------------- ; Calculate address of command table in package from Z3ENV. On entry, E ; contains the offset to the address of the package in the environment. On ; exit, DE points to the beginning of the package and HL points to the fifth ; byte (where the command table starts in the RCP and FCP modules). The zero ; flag is set on return if the package is not supported. IF fcpenv OR rcpenv OR ndrenv PKGOFF: EX DE,HL ; Package address to DE LD HL,5 ; Offset to start of table ADD HL,DE ; Pointer to 5th byte of package in HL LD A,D OR E ; Set Z flag if not supported RET ; Return with zero flag set appropriately ENDIF ;fcpenv or rcpenv or ndrenv ;---------------------------------------- ; This subroutine checks to see if we are in a false IF state. If that is ; the case, the routine returns with the zero flag set. If there is no active ; IF state or if it is true, then the zero flag is reset. IF fcps NE 0 ; Omit code if FCP not implemented IFTEST: LD DE,(IFPTRFL) ; Current IF pointer into C, IF status into B LD A,E ; See if any IF in effect SUB 1 ; ..and set C flag if none RET C ; ..then return zero flag reset LD A,E AND D ; Mask the current IF status RET ENDIF ;fcps ne 0 ;---------------------------------------- ; Print the command prompt with DU and/or DIR, with specified trailing ; character. This routine prints trailing character using PRINT. The ; conditional assemblies are somewhat involved because of the possibilities ; of either or both of the DU or DIR forms being omitted from the prompt. IF NOT [incldu OR incldir OR incltim] PROMPT EQU PRINTC ; ONLY print appropriate trailer ELSE ;incldu or incldir or incltim PROMPT: IF BANKED ;4.0E CALL GO_SYS ;4.0E Switch to System Bank CALL BPROMP ;4.0E .call the Banked Routine CALL GO_TPA ;4.0E ..and switch back to TPA Bank COMMON /BANK2/ ;4.0E BPROMP: ;4.0E ENDIF ;4.0E CALL CRLF ; Read the system Clock and Print "HH:MM - " at beginning of Prompt IF incltim CALL PRTIME ;4.0E Get Clock Time and Print ENDIF ; incltim ; Get current user/drive into BC or HL for inclusion in prompt IF incldir ; If DIR in prompt LD BC,(CURUSR) ; Get current drive/user into BC ELSE ; not incldir ... and incldu LD HL,(CURUSR) ; Get current drive/user into HL ENDIF ; incldir ; If INCLENV is enabled, the drive and user (DU) will be included in the ; prompt based on the state of the DUOK flag in the environment. If INCLENV ; is disabled, the DU form will always be included if INCLDU is on. IF incldu IF inclenv LD A,(DUOKFL) ; If ENV disallows DU, OR A ; ..then don't show it JR Z,PROMPT2 ; ..in the prompt, either ENDIF ;inclenv IF incldir LD A,B ; Get current drive ADD A,'A' ; Convert to ascii A-P CALL CONOUT LD A,C ; Get current user ELSE ; not incldir LD A,H ; Get current drive ADD A,'A' ; Convert to ascii A-P CALL CONOUT LD A,L ; Get current user ENDIF ; incldir IF supres ; If suppressing user # report for user 0 OR A JR Z,PROMPT2 ENDIF IF highuser ; If allowing users 16..31 LD HL,10 SHL 8 + '0'-1 ; H=10, L='0'-1 CP H ; User < 10 ? JR C,PROMPT1 PROMPT0: INC L ; Advance character for user number tens digit SUB H JR NC,PROMPT0 ADD A,H LD H,A ; Keep low digit of user number in H LD A,L ; Display tens digit CALL CONOUT LD A,H ; Ready to process units digit PROMPT1: ELSE ;using only standard user numbers 0..15 LD H,A ; Save user number SUB 10 JR C,PROMPT1 ; If User < 10 LD H,A ; Save low digit CALL PRINT ; Display a '1' for tens digit DEFC '1' PROMPT1: LD A,H ENDIF ;highuser ADD A,'0' ; Output 1's digit (convert to ascii) CALL CONOUT PROMPT2: ENDIF ; incldu ; Display named directory IF incldir INC B ; Switch drive to range 1..16 CALL DU2DIR ; See if there is a corresponding DIR form JR Z,PROMPT4 ; If not, end now IF incldu ; Separate DU and DIR with colon IF inclenv LD A,(DUOKFL) ; If not displaying DU, then OR A ; ..don't send separator, either LD A,':' ; Make the separator CALL NZ,CONOUT ; ..and send if permitted ELSE CALL PRINT ; Put in colon separator DEFB ':' ENDIF ;inclenv ENDIF ; incldu LD B,8 ; Max of 8 chars in DIR name PROMPT3: INC HL ; Point to next character in DIR name LD A,(HL) ; ..and get it CP ' ' ; Done if space CALL NZ,CONOUT ; Print character if Not Space DJNZ PROMPT3 ; Count down ENDIF ; incldir PROMPT4: LD A,'-' ; Give a dash to separate IF BANKED ;4.0E JP CONOUT ;4.0E If banked, print and return to TPA CSEG ;4.0E ELSE ;4.0E CALL CONOUT ; If Not banked, Call print and fall thru ENDIF ;4.0E JP PRINT ; Now print appropriate prompt trailer & ret ENDIF ; incldu OR incldir OR incltim ;----------------------------------------------------------------------------- ; Subroutine to convert DU value in BC into pointer to a matching entry in ; the NDR. If there is no match, the routine returns with the zero flag set. ; If a match is found, the zero flag is reset, and the code returns with HL ; pointing to the byte before the directory name. IF BANKED ;4.0E COMMON /BANK2/ ;4.0E ENDIF ;4.0E IF z3ndirs NE 0 DU2DIR: IF ndrenv ; If getting NDR address from environment LD HL,(Z3ENV+15H) ; Offset to NDR in Z3ENV LD A,H OR L ; Is NDR implemented? RET Z ; If no NDR, return with zero flag set JR DU2DIR2 ELSE LD HL,Z3NDIR-17 ; Scan directory for match ENDIF ;ndrenv DU2DIR1: LD DE,16+1 ; Skip user (1 byte) and name/pw (16 bytes) ADD HL,DE ; ..advacing to next entry in NDR DU2DIR2: LD A,(HL) ; End of NDR? OR A RET Z ; If so, return with zero flag set INC HL ; Point to user number in NDR entry CP B ; Compare drive values JR NZ,DU2DIR1 ; If mismatch, back for another try LD A,(HL) ; Get user number SUB C ; ..and compare JR NZ,DU2DIR1 ; If mismatch, back for another try DEC A ; Force NZ to show successful match RET ENDIF ;z3ndirs ne 0 IF BANKED ;4.0E CSEG ;4.0E ENDIF ;4.0E ;----------------------------------------------------------------------------- ; This routine gets the next line of input for the command buffer. The ; following order of priority is followed: ; If ZEX is active, the next line is obtained from ZEX ; If a submit file is running, its last record provides the input ; If there is a command line on the shell stack, use it ; Finally, if none of the above, the input is obtained from the user READBUF: LD A,(ZEXRUNFL) ; Get ZEX-running flag OR A IF NOT (longsub AND subnoise NE 0) JR NZ,USERINPUT ; If ZEX running, go directly to user input ELSE JP NZ,USERINPUT ; Long jump over longsub routine ENDIF IF subon ; If submit facility is enabled, check for it LD A,(SUBFLAG) ; Test for submit file running OR A JR Z,SHELLINPUT ; If not, go on to possible shell input IF subzero ; If all submits through user 0 XOR A ; Log into user 0 CALL SETUSER ENDIF ;subzero CALL DEFLTDMA ; Initialize DMA pointer LD DE,SUBFCB ; Point to submit file FCB CALL OPEN ; Try to open file JR Z,READBUF1 ; Branch if open failed IF longsub LD C,B_FSIZ ; "Compute file size" bdos call no. CALL BDOSSAVE ; DE still => subfcb; get file size in subfr LD HL,(SUBFR) ; Get file size DEC HL ; Decrement to get last record no. LD (SUBFR),HL ; Replace in random record field LD C,B_FRDR ; "Read random" bdos call no. CALL BDOSSAVE ; DE still => subfcb; randm read last record LD HL,(TBUFF) ; Get current "last record" from last record DEC HL ; Point to next earlier record to read LD (TBUFF),HL ; Save pointer in actual last record INC C ; ... and write back to file (c=22h) CALL BDOSSAVE ; DE still => subfcb; randm write last recrd LD (SUBFR),HL ; Now prepare to read new "last record" DEC C ; (c=21h) CALL BDOSSAVE ; Random read record; DE still => subfcb LD (SUBFRC-1),A ; Zero FCB S2 byte (A=0 from previous read) ELSE ;not longsub LD HL,SUBFRC ; Point to record count in submit FCB LD A,(HL) ; Get the number of records in file DEC A ; Reduce to number of last record LD (SUBFCR),A ; ..and put into current record field CALL READ ; Attempt to read submit file JR NZ,READBUF1 ; Branch if read failed DEC (HL) ; Reduce file record count DEC HL ; Point to S2 byte of FCB (yes, this is req'd!) LD (HL),A ; Stuff a zero in there (A=0 from call to READ) ENDIF ;longsub CALL CLOSE ; Close the submit file one record smaller JR Z,READBUF1 ; Branch if close failed ; Now we copy the line read from the file into the multiple command line buffer LD DE,CHRCNT ; Point to command length byte in command buffer LD HL,TBUFF ; Point to sector read in from submit file LD BC,80H ; Assume full buffer (128 bytes) LD A,(Z3ENV+1AH) ; Otherwise BUFLEN (z3cls) CP C ; Carry if small MCL JR NC,RDBUF ; Full size LD C,A ; Use lower value RDBUF: LDIR ; Transfer line from submit file to buffer ; We now deal with various options that control the display of commands fed ; to the command processor from a submit file. IF subnoise GT 0 ; If subnoise = 0 we omit all this display code IF subnoise EQ 1 ; If subnoise = 1 we follow the quiet flag LD A,(QUIETFL) OR A JR NZ,READBUF0 ; If quiet, skip echoing the command ENDIF ;subnoise eq 1 CALL PROMPT ; Print prompt DEFC SPRMPT ; ..and SUBMIT prompt trailer LD HL,CMDLIN ; Print command line CALL PRINTHL ENDIF ;subnoise gt 0 READBUF0: CALL BREAK ; Check for abort (any char) RET NZ ; If no ^C, return to caller and run READBUF1: CALL SUBKIL ; Kill submit file and abort JP RESTART ; Restart CPR ENDIF ; subon SHELLINPUT: IF shstks NE 0 LD HL,(Z3ENV+1EH) ; Get address of shell stack LD A,H ; Make sure stack is active OR L JR Z,USERINPUT ; If not, skip shell checking LD A,(HL) ; Check first byte CP ' '+1 ; See if any entry JR C,USERINPUT ; Get user input if none LD DE,CMDLIN ; Point to first character of command line LD BC,(Z3ENV+21H) ; SHSIZE to C XOR A LD B,A ; Now BC has size LDIR ; Do copy EX DE,HL ; HL points to end of line INC A ; Set command status flag to 1 to show LD (CMDSTATFL),A ; ..that a shell has been invoked JR READBUF3 ; Store ending zero and exit ENDIF ;shstks ne 0 USERINPUT: IF zexnoise EQ 0 ; Never display prompt with ZEX LD A,(ZEXRUNFL) ; See if ZEX is running OR A JR NZ,USERIN1 ; If so, skip prompt ENDIF ;zexnoise eq 0 IF zexnoise EQ 1 ; Follow the quiet flag LD A,(ZEXRUNFL) ; See if ZEX is running OR A JR Z,USERIN0 ; If not, proceed with display of prompt LD A,(QUIETFL) ; Else, test quiet flag OR A JR NZ,USERIN1 ; If quiet, skip the prompt ENDIF ;zexnoise eq 1 USERIN0: CALL PROMPT ; Print prompt DEFC CPRMPT ; ..and prompt trailer USERIN1: LD C,B_RBUF ; Read command line from user LD DE,BUFSIZ ; Point to buffer size byte of command line CALL BDOS ; Store null at end of line LD HL,CHRCNT ; Point to character count LD A,(HL) ; ..and get its value INC HL ; Point to first character of command line CALL ADDAH ; Make pointer to byte past end of command line READBUF3: LD (HL),0 ; Store ending zero RET ;----------------------------------------------------------------------------- ; The routine NUMBER evaluates a string in the first FCB as either a decimal ; or, if terminated with the NUMBASE hexadecimal marker, a HEX number. If the ; conversion is successful, the value is returned as a 16-bit quantity in BC. ; If an invalid character is encountered in the string, the routine returns ; with the carry flag set and HL pointing to the offending character. IF BANKED ;4.0E BHEXNM: LD HL,HEXNUM ;4.0E Provide Entry for JUMP command JP DOITMV ;4.0E ..by copying Page 0 stuff then executing COMMON /BANK2/ ;4.0E ENDIF ;4.0E IF saveon NUMBER: LD HL,TFCB+8 ; Set pointer to end of number string LD BC,8 ; Number of characters to scan LD A,NUMBASE ; Scan for HEX identifier CPDR ; Do the search JR NZ,DECIMAL ; Branch if HEX identifier not found INC HL ; Point to HEX marker LD (HL),' ' ; Replace HEX marker with valid terminator ; ..and fall through to HEXNUM ENDIF ;saveon ;---------------------------------------- ; At this entry point the character string in the first default FCB is ; converted as a hexadecimal number (there must NOT be a HEX marker). HEXNUM: LD HL,TFCB+1 ; Point to string in first FCB ; At this entry point the character string pointed to by HL is converted ; as a hexadecimal number (there must be NO HEX marker at the end). HEXNUM1: LD C,16 ; HEX radix base IF saveon OR accptdu ; DECIMAL only used by number and duscan JR RADBIN ; Invoke the generalized conversion routine ;---------------------------------------- ; This entry point performs decimal conversion of the string in the first ; default FCB. DECIMAL: LD HL,TFCB+1 ; Set pointer to number string ; This entry point performs decimal conversion of the string addressed by HL. DECIMAL1: LD C,10 ; Decimal radix base ; Fall thru to generalized radix conversion routine ENDIF ;saveon or accptdu ; This routine converts the string pointed to by HL using the radix passed in ; C. If the conversion is successful, the value is returned in DE. HL points ; to the character that terminated the number, and A contains that character. ; If an invalid character is encountered, the routine returns with the carry ; flag set, and HL points to the offending character. RADBIN: LD DE,0 ; Initialize result RADBIN1: OR A ; Make sure carry is reset CALL SDELM ; Test for delimiter (returns Z if delimiter) RET Z ; Return if delimiter encountered SUB '0' ; See if less than '0' RET C ; Return with carry set if so CP 10 ; See if in range '0'..'9' JR C,RADBIN2 ; Branch if it is valid CP 'A'-'0' ; Bad character if < 'A' RET C ; ..so we return with carry set SUB 7 ; Convert to range 10..15 RADBIN2: CP C ; Compare to radix in C CCF ; Carry should be set; this will clear it RET C ; If carry now set, we have an error INC HL ; Point to next character PUSH HL ; Save character pointer LD B,C ; Multiplier (radix) to B LD HL,0 ; Clear accumulator MPY: ADD HL,DE ; Add it to HL.. DJNZ MPY ; ..radix times LD E,A ; New digit LD D,B ; A zero from djnz above ADD HL,DE ; New result EX DE,HL ; ..to DE POP HL ; Get char pointer JR RADBIN1 ; Loop until delimiter ;----------------------------------------------------------------------------- ; This routine checks for a delimiter character pointed to by HL. It returns ; with the character in A and the zero flag set if it is a delimiter. All ; registers are preserved except A. SDELM: LD A,(HL) ; Get the character SDELM0: EXX ; Use alternate register set (shorter code) LD HL,DELDAT ; Point to delimiter list LD BC,DELEND-DELDAT ; Length of delimiter list CPIR ; Scan for match EXX ; Restore registers RET ; Returns Z if delimiter DELDAT: DEFB ' =.:;,',0 ; List of permanent delimiter characters IF cmdsep NE ';' DEFB CMDSEP ENDIF ;cmdsep ne ';' DELIMLIST ; Macro for optional additional characters DELEND: IF BANKED ;4.0E CSEG ;4.0E ENDIF ;4.0E ;----------------------------------------------------------------------------- ; Log into DU contained in FCB pointed to by DE. Registers DE are preserved; ; all others are changed. Explicit values for the temporary drive and user ; are extracted from the FCB. If the record-count byte has an FF in it, that ; is a signal that the directory specification was invalid. We then invoke ; the error handler. IF diron OR eraon OR lton OR renon OR saveon IF BANKED ;4.0E COMMON /BANK2/ ;4.0E ENDIF ;4.0E FCBLOG: PUSH DE ; Save pointer to FCB LD A,(DE) ; Get drive SUB 1 ; See if drive value was 0 and change to 0..15 JR NC,FCBLOG1 ; If not, branch ahead LD A,(CURDR) ; Otherwise substitute current drive FCBLOG1: LD (DVVAL),A ; ..store for possible later use LD HL,13 ; Offset to S1 field ADD HL,DE LD D,A ; Get drive into D LD E,(HL) ; Get user into E CALL LOGDE ; ..and log into it POP DE ; Restore pointer to FCB ; Now check to make sure that the directory specification was valid. INC HL ; Advance pointer to record-count byte INC HL LD A,(HL) ; See if it is nonzero OR A JP NZ,BADDIR ; If so, invoke error handler RET ; Otherwise return IF BANKED ;4.0E CSEG ;4.0E ENDIF ;4.0E ENDIF ;diron OR eraon OR lton OR renon OR saveon ;----------------------------------------------------------------------------- ; This routine scans the command table pointed to by HL for the command name ; stored in the command FCB. If the command is not found, the routine returns ; with the zero flag reset. If the command is found, the address vector is ; stored in EXEADR and the zero flag is set. CMDSCAN: IF BANKED ;4.0E CALL GO_SYS ;4.0E CALL BCMDSC ;4.0E JP GO_TPA ;4.0E COMMON /BANK2/ ;4.0E BCMDSC: ;4.0E ENDIF ;4.0E LD B,(HL) ; Get command size JR CMDSCAN1 SCANSKIP: INC HL ; Skip to next command table entry DJNZ SCANSKIP SCANBAD: POP BC ; Command size in B INC HL ; Skip over address vector CMDSCAN1: INC HL LD A,(HL) ; Check for end of table SUB 1 ; Dec; C & NZ set if (HL)=0 RET C ; Return NZ (command not found) if end PUSH BC ; Save command size LD DE,CMDFCB+1 ; Point to name of requested command IF wheel ; Ignore commands with high bit set in first ; ..char of command name if wheel is false CALL WHLCHK ; Check the wheel status LD C,0FFH ; Make a mask that passes all characters JR Z,SCANCMP ; Use this mask if wheel not set ENDIF ; wheel LD C,7FH ; Use mask to block high bit if wheel set ; ..or not in use SCANCMP: LD A,(DE) ; Compare against table entry XOR (HL) AND C ; Mask high bit of comparison JR NZ,SCANSKIP ; No match, so skip rest of command name INC DE ; Advance to next characters to compare INC HL RES 7,C ; Mask out high bit on characters after first DJNZ SCANCMP ; Count down LD A,(DE) ; See if next character in input command CP ' ' ; ..is a space JR NZ,SCANBAD ; If not, user command is longer than commands ; ..in the command table ; Matching command found POP BC ; Adjust the stack LD A,(HL) ; Get address from table into HL INC HL LD H,(HL) LD L,A LD (EXEADR),HL ; Set execution address XOR A ; Set zero flag to show that command found RET IF BANKED ;4.0E CSEG ;4.0E ENDIF ;4.0E ;------------------------------------------------------------------------------ ; This routine Reads a sector of data from the file identified by the Command ; FCB (CMDFCB) to the address pointed to by HL. The DMA Address is altered ; by this routine. After reading the sector, the pointer in HL is incre- ; mented by 128 to point to the next load point. The accumulator and the ; Zero Flag return Read status (0=Ok, <>0 for Error). IF geton OR copyon RDFILE: EX DE,HL ; Put Buffer pointer to DE PUSH BC ; Save counter CALL DMASET ; ..and Set the DMA Addr EX DE,HL ; DMA Addr to HL CALL READCMD ; Read a sector from file at CMDFCB POP BC ; ..restore counter LD DE,128 ; Offset to next Load address ADD HL,DE OR A ; Read Ok? (set flags) RET ENDIF ; geton or copyon ;------------------------------------------------------------------------------ ; This routine Writes the number of sectors contained in BC to a section of ; memory starting with the value in HL. The DMA address is incremented by ; 128 after each sector write. The file to be written is in the current User ; area identified by TFCB information. The routine will return when all ; sectors have been written (A=0, Zero Flag Set) or if a write error occurs ; (A<>0, Zero Reset). IF saveon OR copyon WRFILE: LD DE,TFCB WRFILV: LD A,B ; Are we at the end? OR C RET Z ; ..return if so PUSH BC ; Preserve Counter EX DE,HL ; Buffer ptr to DE CALL DMASET ; ..and set the DMA to Buffer Addr EX DE,HL ; Pointer back to HL LD C,B_FWR ; Write the block CALL BDOSSAVE POP BC ; Restore Counter OR A ; Error? (Disk Full or Write Error) RET NZ ; ..return if So PUSH DE ; Save FCB Ptr LD DE,128 ; Else load Sector size ADD HL,DE ; ..and offset Buffer Ptr POP DE ; Restore FCB Ptr DEC BC ; Count down JR WRFILV ; ..loop til done writing ENDIF ; saveon OR copyon ; End Z40-4.Z80