;-------------------------------------------------------------------------- ; SSTAT.ASM - a SuperSTAT program. ; ; Copyright (C) by David Jewett, III, 1986. All rights reserved. You shall ; not use this program for commercial purposes or for monetary gain without ; first obtaining written permission from the author. ; ; Your comments and bug reports are welcome: ; ; David Jewett, III ; 10376 Iron Mill Road ; Richmond, VA 23235 ; Day Phone: (804) 794-7667 ; ; or upload them to Data-Plus(tm) (914) 246-6906 and/or leave a message. ; ; Thanks to Harry Kaemmerer for his help with this program. ; ; Version 1.0 - 1/25/86 ; ; ------------------------------------------------------------------------- ; Revisions in reverse order: ; ver. 1.9 02/29/88 Converted code to assemble with ZAS. Changed help ; to "/" character to go with Z-System. Fixed "menu" ; to accurately reflect movement commands. fg ; ; ver. 1.8 02/21/86 Inserted one more ANI 7FH in the TYPE routine. Many ; terminals (e.g. newer Kaypros) use a set high bit as ; a signal to change character sets or go to a graphics ; mode. One line added, marked with `b/m'. ; - Bruce Morgen ; ; ver. 1.7 02/18/86 Fixed bad instuction in MAKPTR routine. Added YES/NO ; equates - D. Jewett, III ; ; ver. 1.6 02/13/86 Changed "name line" routines for speedier output. ; Eliminated file space display at startup. ; - D. Jewett, III ; ; ver. 1.5 02/06/86 Now program knows if you change an attribute back to ; its original state so no change is needed to the ; directory. Also, attribute strings for R/O, SYS, and ; ARC reflect the directory status, not the tagged ; status. And, added command to turn auto advance on/off ; as desired - D. Jewett, III ; ; ver. 1.4 02/01/86 Changed toggle routine to auto advance to next file ; after tagging and still re-display the change first. ; - Harry Kaemmerer ; ; ver. 1.3 01/31/86 Fixed problem with sort where attributes were affecting ; the sorting order - D. Jewett, III ; ; ver. 1.2 01/29/86 Various cosmetic changes - D. Jewett, III ; ; ver. 1.1 01/25/86 Added additionals to command table .. you now can use ; the original and WS commands interchangably at will. ; The command table is nolonger part of the help display. ; Added message that end of BIOS can't be detected by ; this PGM as this changes with every system and there ; are no rules for CP/M to indicate end of BIOS. ; - Harry Kaemmerer ; ; ------------------------------------------------------------------------- ; Index to modules ; ---------------- ; ; ID Function ; ----- --------------------------------------- ; {0} = Main Program and User Customizable data ; {2} = File Name Processing ; {3} = Disk I/O ; {4} = Array Processing ; {5} = System Information ; {6} = Arithmetic/Logic Subroutines ; {7} = Console I/O ; {8} = Utility Subroutines ; {9} = Initialized Data ; ; ------------------------------------------------------------------------- ; Equates ; ------- ; YES EQU 0FFH ; Byte-sized logical true NO EQU 0 ; Byte-sized logical false ; VTENS EQU '1' ; Version number tens VUNITS EQU '9' ; Version number units ; ; CP/M functions ; CONIN EQU 1 ; Get console input CONOUT EQU 2 ; Console output DCONIO EQU 6 ; Direct console input/output GETIOB EQU 7 ; Get iobyte CONSTAT EQU 11 ; Get console status RSTDSK EQU 13 ; Reset disk system SELDSK EQU 14 ; Select disk SRCHFST EQU 17 ; Search for first directory occurrence SRCHNXT EQU 18 ; Search for next directory occurrence GETDSK EQU 25 ; Get current disk number SETDMA EQU 26 ; Set DMA address GETALV EQU 27 ; Get allocation vector address SETATT EQU 30 ; Set file attributes GETDPB EQU 31 ; Get disk parameter block address SETUSR EQU 32 ; 2.x set user number GETUSR EQU SETUSR ; 2.x get current user number ; ; Addresses ; SBASE EQU 0000H ; System base address WBOOT EQU SBASE ; Warm start BDOS EQU SBASE+0005H ; Bdos entry address CMDBUF EQU SBASE+0080H ; Cp/m's default DMA/command buffer TPA EQU SBASE+0100H ; Where this program lives ; ; ASCII values ; BELL EQU 7 ; Ring console bell BS EQU 8 ; Back space TAB EQU 9 ; Horizontal tab LF EQU 10 ; Line feed CR EQU 13 ; Carriage return ESC EQU 27 ; Escape SPACE EQU ' ' ; Blank DEL EQU 127 ; Rubout ; ; FCB definitions ; FCBSZ EQU 32 ; Size of entire fcb (without cr,r0,r1,r2) FNAMSZ EQU 11 ; Size of file name in fcb (name + type) FTYPSZ EQU 3 ; Size of file "type" field in file name EXTOFF EQU 12 ; Offset to extent byte in fcb RCOFF EQU 15 ; Offset to Record Count byte in fcb ; ; Miscellaneous equates ; FATTS EQU 11 ; Offset in our file names to attribute word NTRYSZ EQU 15 ; Size of a file name entry in our table ; ;========================================================================== ; {0} Start of code - MAIN module ; --------------------------- ; ORG TPA ; Code starts at transient program area. JP START ; Jump to real start of program ; ;========================================================================== ; User customizable data ; ATTON: DEFB ESC,')',0,0 ; String to turn video attribute ON DEFB 0 ; String terminator ATTOFF: DEFB ESC,'(',0,0 ; String to turn video attribute OFF DEFB 0 ; String terminator MAXDRV: DEFB 'P' ; Max. accessible drive (A..P character) MAXUSR: DEFB 15 ; Max. accessible user (0..15 only) MAXNARG:DEFW 1024 ; Max. no. of directory entries to load ADVANC: DEFB YES ; YES = do auto advance, NO = do not ; ; ------------------------------------------------------------------------- ; Command Table. Each entry consists of a command character followed by ; a vector address. Command STRINGS are NOT supported. Thus the "arrow" ; keys on many computers will not work. ; CMDTBL: DEFB 'X'-64 ; Control-X = forward one file DEFW FORWRD DEFB 'E'-64 ; Control-E = back one file DEFW BACK DEFB 'S'-64 ; Control-S = cursor left DEFW LEFT DEFB 'D'-64 ; Control-D = cursor right DEFW RIGHT DEFB 'A'-64 ; Set file attributes DEFW SFA DEFB 'T'-64 ; Toggle attribute DEFW TOGGLE DEFB CR ; Move forward one file DEFW FORWRD DEFB 'B' ; Move back one file DEFW BACK DEFB BS ; Move cursor left DEFW LEFT DEFB SPACE ; Move cursor right DEFW RIGHT DEFB 'T' ; Toggle attribute DEFW TOGGLE DEFB 'A' ; Set file attributes DEFW SFA DEFB 'F' ; Find a file DEFW FIND DEFB 'I' ; Print disk Information DEFW PSTAT DEFB 'L' ; Log new DU: DEFW LOGON DEFB 'N' ; Next line auto advance on/off DEFW AUTAD DEFB 'S' ; Print free Space DEFW PFREE DEFB 'X' ; Quit DEFW DONE DEFB '/' ; Print help menu DEFW MENU DEFB 'C'-64 ; Quit and warm boot DEFW SBASE DEFB 0 ; Zero marks the end of the table ; ;========================================================================== ; Main line code ; ; Set up stack and save drive and user at startup ; START: LD HL,0 ; Save old stack pointer ADD HL,SP LD (OLDSP),HL LD SP,STACK ; Set local stack CALL GETDU ; Save drive and user at startup LD A,(DRIVE) LD (LOGDRV),A LD A,(USER) LD (LOGUSR),A CALL ILPRT ; Print a help message DEFB 26,23,4,8 ; Clear screen 4 char & terminal set up DEFB CR,LF,' SSTAT, Version ',VTENS,'.',VUNITS DEFB ' - Use "/" for help.',CR,LF,0 ; ; Get argument, if any, from the command line ; LD HL,CMDBUF ; Point to command buffer LD A,(HL) ; Get character count OR A ; Any argument? JP Z,NOARG ; NO - then we're done here BLANKS: INC HL ; Point to next character in command line LD A,(HL) ; Get a character from the line CP SPACE ; A space? JP Z,BLANKS ; YES - look until we get to the real stuff JP C,NOARG ; Else no argument if we hit end of line LD DE,ARGBUF ; Else point to our local buffer GETARG: LD (DE),A ; Move a character to our buffer INC HL ; Bump pointers INC DE LD A,(HL) ; Get next char. from command line CP SPACE+1 ; End of line? JP NC,GETARG ; NO - then get more XOR A ; Else terminate our buffer with a zero LD (DE),A ; ; Process a file name entered in the command line ; LD HL,ARGBUF ; Point to our copy of the command PUSH HL ; And save that address LD A,(HL) ; Get a character CP '/' ; Is it a '/' JP NZ,GOTFN ; NO - then move on INC HL ; Point to next character LD A,(HL) ; Get it CP 0 ; Is it a zero (end of string) ? GOTFN: POP HL ; In any event, restore arg's address JP NZ,LOADIR ; Not '?' so start the program CALL ILPRT ; Else print a help message DEFB CR,LF,'Syntax: SSTAT []',CR,LF,0 JP DONEX ; And quit now ; ; User did not specify a file name, so make FCB all wild, current DU: ; ALLWLD: DEFB '*.*',0 ; A totally wild file name NOARG: LD HL,ALLWLD ; Point to wild file name ; ; Log onto a drive/user and get all matching extents ; LOADIR: CALL LOG ; Log onto the disk JP C,LOGON ; If error, ask user to log new drive ; LD HL,0 ; Initialize index to first name LD (FNDX),HL LD A,8 ; Initialize attribute LD (ATT),A CALL RING ; Set up the name "ring" ; OBEY0: CALL NEWLINE ; Next console line OBEY1: CALL NAMOUT ; Print the "name line" OBEY2: CALL GETCHAR ; Get a character LD B,A ; And put it in B LD HL,CMDTBL ; Point to the command table OBYLUP: LD A,(HL) ; Get a command from table INC HL ; And increment pointer to address OR A ; Test "command" for zero JP Z,OBEY2 ; If zero, end of table, so loop CP B ; Else compare with character JP Z,DSPATCH ; If MATCH then DISPATCH INC HL ; Else increment pointer over INC HL ; The address JP OBYLUP ; And try another entry from the table DSPATCH: LD A,(HL) ; Load low byte of the vector address INC HL ; Point to high byte LD H,(HL) ; Get the high byte LD L,A ; HL now contains entire vector JP (HL) ; So go to it ; ; move cursor right ; RIGHT: LD HL,(CPTR) ; Get the character pointer LD A,(HL) ; Retrieve the character it points to CALL TYPE ; Type it to move cursor over INC HL ; Point to next character LD A,(ATT) ; Get attribute INC A ; Bump it CP 8 ; Is it the first "type" character? JP NZ,RIGHT1 ; NO - then move on PUSH AF ; Else we must type the period LD A,'.' CALL TYPE POP AF RIGHT1: CP 11 ; Are we at the end of the name? JP NZ,RIGHT9 ; NO - then boogie INC A ; Else adjust ATT and cptr so INC HL ; We can RIGHT2: CALL BAKSPC ; Back-space to beginning of name DEC HL ; (12 times total) DEC A JP NZ,RIGHT2 RIGHT9: LD (CPTR),HL ; Save new character pointer LD (ATT),A ; And save new attribute index JP OBEY2 ; And go get next command ; ; move the cursor left ; LEFT: LD HL,(CPTR) ; Get the character pointer LD A,(ATT) ; And the attribute index OR A ; Attribute = 0? JP NZ,LEFT3 ; NO - then we don't need to: LEFT1: PUSH AF ; Print the entire file name LD A,(HL) CALL TYPE POP AF CP 7 ; If at the 7th char., print period JP NZ,LEFT2 PUSH AF LD A,'.' CALL TYPE POP AF LEFT2: INC HL INC A CP 11 ; Do all 11 characters of the name JP NZ,LEFT1 LEFT3: DEC A ; Adjust ATT back one DEC HL ; Similarly for character pointer CALL BAKSPC ; Type a backspace CP 7 ; Are we at the 7th char.? JP NZ,LEFT9 ; NO - then we don't need to: CALL BAKSPC ; Type an extra BS to move over the period LEFT9: LD (ATT),A ; Save resultant ATT LD (CPTR),HL ; And character pointer JP OBEY2 ; Go get next command ; ; move back one file ; BACK: LD HL,(FNDX) ; Get file index LD A,L ; And test for zero OR H JP NZ,BACK1 ; Move on if NOT zero LD HL,(NARGC) ; Else use MAX file count CALL NEWLINE ; And issue an extra cr/lf BACK1: DEC HL ; File index -= 1 LD (FNDX),HL ; Save new file number JP OBEY0 ; Print name line & get next command ; ; move forward one file ; FORWRD: LD HL,(FNDX) ; Get file index INC HL ; Bump it EX DE,HL ; Save it in DE LD HL,(NARGC) ; Get MAX file count CALL COMPARE ; Compare (index - MAX) EX DE,HL ; And put index back in HL JP C,FORWR1 ; If (index < MAX) then move on LD HL,0 ; Else new index will be first file CALL NEWLINE ; And issue an extra cr/lf FORWR1: LD (FNDX),HL ; Save new file index JP OBEY0 ; Print name line & get next command ; ; toggle file attribute ; TOGGLE: LD HL,(CPTR) ; Get character pointer LD A,(HL) ; Get a character from the file name RLA ; Roll 8th bit into carry CCF ; And complement the carry RRA ; Then put the 8th bit back LD (HL),A ; And save the result LD A,(ADVANC) ; Get auto advance flag OR A ; Test - is it on ? JP Z,OBEY1 ; NO - then back to command loop CALL NAMOUT ; Else update the current "name line" JP FORWRD ; Then go display next line ; ; turn auto-advance on/off ; AUTAD: LD A,(ADVANC) ; Get auto advance flag CPL ; Flip it LD (ADVANC),A ; Put it back JP OBEY2 ; Get next command ; ; find a file ; FIND: CALL ASK ; Ask user for file spec JP Z,OBEY0 ; Ignore if nothing was entered LD DE,MYFCB ; Convert the string to FCB format CALL MAKEFCB LD HL,FNAMSZ+1 ; Get offset to end of file name ADD HL,DE ; Add base address FIND1: DEC HL ; Move pointer back in file name LD A,(HL) ; Get the character there CP SPACE ; Is it a space? JP NZ,FIND2 ; NO - then break LD (HL),0 ; Else replace it with zero JP FIND1 ; And continue to look for end of name FIND2: LD HL,FCOMP ; Install address of find-compare routine LD (MYCOMP),HL ; Into generic compare routine LD HL,MYFCB+1 ; Get pointer to key to search for CALL ISRCH ; Search for match JP Z,OBEY0 ; Oops, no match LD (FNDX),HL ; Else store index of matching entry JP OBEY0 ; And then back to main loop ; ; Set File Attributes ; SFA: CALL NEWLINE ; Move to next console line LD HL,(NARGC) ; Get count of files EX DE,HL ; Into DE LD HL,0 ; Get index to first name SFA1: CALL NEWATT ; Set attributes as needed INC HL ; Bump to next file CALL COMPARE ; Compare (count - index) JP NZ,SFA1 ; Repeat for all files CALL NEWLINE ; Finish up the last line JP OBEY0 ; Re-print old line and get next command ; ; log a new drive/user ; LOGON: CALL ASK ; Get file spec from user JP NZ,LOADIR ; If something was entered go log new DU: LD HL,(NARGC) ; Else get file count LD A,H ; Test for zero OR L JP Z,LOGON ; If no files, repeat the question JP OBEY0 ; Else "never mind" ; ; ask user to enter a file spec ; ASK: CALL ILPRT ; Issue prompt DEFB CR,LF,LF,'Enter filespec: ',0 LD HL,ARGBUF ; Get address of console buffer LD A,40 ; And max. length of input (arbitrary) CALL GETS ; Get a string OR A ; Test length RET ; And return ; ; Exit point ; DONE: LD C,CONSTAT ; Clean up console queue CALL BDOS OR A JP Z,DONE1 LD C,CONIN CALL BDOS DONE1: LD A,(LOGDRV) ; Log back to original DU: LD B,A LD A,(LOGUSR) LD C,A CALL SETDU DONEX: LD HL,(OLDSP) ; Restore original stack LD SP,HL RET ; Go back to CP/M ; ;========================================================================== ; Subroutines ;========================================================================== ; {2} File name module ; ---------------- ; This module validates file names, extracts disk/user numbers, expands ; wild card characters, and installs names into file control blocks. ; ------------------------------------------------------------------------- ; Expand a possibly ambiguous file name and fill buffer with all matches ; from the specified drive and user area. ; ; Entry: HL = pointer to UPPER CASE file name ; DE = pointer to fcb that will receive file name (fcb[0-1] = user) ; ; Return: NARGC = number of matches found ; NARGBUF contains names in fixed length fields ; DE preserved, other registers destroyed ; CARRY SET if file name error or no match found ; DOWILD: PUSH HL ; Save pointer LD HL,0 ; So we can initialize NARGC: to zero LD (NARGC),HL POP HL CALL EXPAND ; Expand the name into the fcb RET C ; Quit if name error ; CALL DIRSRCH ; Search directory for all matches RET NC ; Return now if match found CALL ERRET ; Else error DEFB 'no file ',0 ; ; ------------------------------------------------------------------------- ; Expand a possibly ambiguous file name into an fcb. ; ; Entry: HL = pointer to UPPER CASE file name ; DE = pointer to fcb (fcb[0-1] = user) ; ; Return: if successful, carry clear and HL = file name without du: ; if error, carry set and HL = complete file name ; BC and PSW destroyed ; CARRY SET if error ; EXPAND: CALL STRIPDU ; Strip off the disk/user spec JP NC,EXPND1 ; Continue if du: is ok CALL ERRET ; ERROR - print message and return DEFB 'bad drive/user spec in ',0 ; EXPND1: LD A,(HL) ; Get next character CP SPACE+1 ; Delimiter? JP C,WILDFN ; YES - then make the name wild and return CALL MAKEFCB ; Else make an fcb for the file name RET NC ; Return if file name is ok CALL ERRET ; ERROR - print msg. and ret DEFB 'invalid characters in ',0 ; ; ------------------------------------------------------------------------- ; "Strip" the disk/user spec off a file name and put it in fcb. ; ; Entry: HL = pointer to UPPER CASE file name ; DE = pointer to fcb (fcb[0-1] = user) ; DRIVE = current drive ; USER = current user ; ; Return: HL = pointer to next byte after DU: spec or unaffected ; if no DU: spec found ; DE unaffected ; BC and PSW destroyed ; CARRY SET if error ; STRIPDU: PUSH DE ; Save fcb address LD A,(DRIVE) ; Put current du: in BC LD B,A LD A,(USER) LD C,A ; COLON: LD A,':' ; Load a colon CALL POS ; Find position of it (if any) in name JP Z,STRPDUN ; If no colon appears use default du: ; PUSH HL ; Put original pointer to name on stack DEC A ; Test to see if ':' is first character JP Z,BADDU ; Bad du: if so LD A,(MAXDRV) ; Get MAXDRV in Acc. CP (HL) ; If drive > MAXDRV JP C,BADDU ; Then error LD A,(HL) ; Get the drive character in Acc. SUB 'A' ; Is it less than 'A' ? JP C,STRUSR ; Then it's not a drive - try user LD B,A ; Else get drive in B INC HL ; Point to next character ; STRUSR: CALL GETASCD ; Get a digit and convert to binary JP C,STRUSR1 ; We're done here if not a good digit LD C,A ; Else save a copy of the first number INC HL ; Point to second (possible) digit CALL GETASCD ; And try to get another digit JP C,STRUSR1 ; Move on if no second digit LD D,A ; Else save it INC HL ; Point to whatever is next LD A,C ; Get the 10's digit back RLCA ; Multiply times 2 LD C,A ; Save this RLCA ; Times 4 RLCA ; Times 8 ADD A,C ; C * 10 = C * 2 + C * 8 ADD A,D ; Plus units LD C,A ; Put new user code into C STRUSR1: LD A,(HL) ; Get a character CP ':' ; If it is not a colon then JP NZ,BADDU ; We have a bad user spec INC HL ; Else point to rest of file name LD A,(MAXUSR) ; Get MAXUSR CP C ; Compare with our user spec JP C,BADDU ; Error if user > MAXUSR POP DE ; Discard original pointer to name ; STRPDUN: POP DE ; Restore fcb pointer DEC DE ; Set to fcb[0-1] LD A,C ; Get user number in Acc. LD (DE),A ; And store it INC DE ; Point to fcb[0] LD A,B ; Get drive code in Acc. INC A ; Increment since in fcb, A=1, B=2, etc. LD (DE),A ; Store it XOR A ; Clear Acc./carry RET ; Done ; BADDU: POP HL ; Restore original name pointer POP DE ; And fcb address SCF ; Set carry = ERROR RET ; And return ; ; ------------------------------------------------------------------------- ; Make an fcb from a file name. ; ; Entry: HL = pointer to file name WITHOUT DRIVE/USER SPEC ; DE = pointer to fcb ; ; Return: HL and DE unaffected ; BC and PSW destroyed ; CARRY SET if BAD file name. ; MAKEFCB: CALL CLRFCB ; Clear the fcb PUSH HL ; Keep a copy of the file name pointer PUSH DE ; And a copy of the fcb pointer INC DE ; Set pointer to the name field LD B,FNAMSZ-FTYPSZ ; Character count for name, less type MKNAME: LD A,(HL) ; Get character from file name CALL CHKCHR ; Check it for validity JP NZ,BADNAME ; Break if bad character LD A,(HL) ; Get it back again INC HL ; Point to next CP '.' ; If '.' JP Z,MKTYPE ; Then go do file type field CP '*' ; If '*' JP Z,MKWILD ; Then make wild CP SPACE+1 ; If delimiter JP C,MKDUN ; Then all done LD (DE),A ; Else put the character into the fcb INC DE ; Point to next fcb character DEC B ; Decrement count JP NZ,MKNAME ; Do more until end of file name field MKNAM1: LD A,(HL) ; We did all allowable name characters INC HL ; Are there any more ? CP '.' ; Must have type now JP Z,MKTYP1 ; So keep looking until we find '.' CP SPACE+1 ; Or if we find a delimiter JP C,MKDUN ; Then done JP MKNAM1 ; Extra name chars are ignored ; MKWILD: LD A,'?' ; Fill rest of name field with '?' MKWLD1: LD (DE),A INC DE DEC B JP NZ,MKWLD1 JP MKNAM1 ; And find type field ; MKTYPE: INC DE ; Increment DE to file type field DEC B JP NZ,MKTYPE MKTYP1: LD B,FTYPSZ ; Get type field size in counter MKTYP2: LD A,(HL) ; Get character from file type CALL CHKCHR ; Check it for validity JP NZ,BADNAME ; Break if bad character LD A,(HL) ; Get character again INC HL ; Point to next CP '.' ; If '.' JP Z,BADNAME ; Then break - '.' can't occur in type CP '*' ; If '*' JP Z,MKWLD2 ; Then make wild CP SPACE+1 ; If delimiter JP C,MKDUN ; Done LD (DE),A ; Else put the character into the fcb INC DE ; Point to next fcb character DEC B ; Decrement count JP NZ,MKTYP2 ; Do more until end of file name field ; MKDUN: POP DE ; Get original fcb address back POP HL ; Restore original file name pointer XOR A ; Make sure flags reflect GOOD NAME RET ; And return ; MKWLD2: LD A,'?' ; Make file type wild LD (DE),A INC DE DEC B JP NZ,MKWLD2 JP MKDUN ; And we're done ; BADNAME: POP DE ; Restore original fcb address POP HL ; Restore pointer to file name SCF ; Set error flag RET ; Quit ; ; ------------------------------------------------------------------------ ; Clear an fcb (exclusive of the user and drive bytes). ; ; Entry: DE = fcb address ; ; Return: FCB name and type filled with spaces. Rest of fcb set to zero. ; all but PSW are unaffected. ; CLRFCB: PUSH BC ; Save caller's reg. PUSH DE ; Save fcb address INC DE ; And point to name field LD B,FNAMSZ ; Get size of file name LD A,SPACE ; Get a space character in Acc. CALL PAD ; And pad file name with spaces POP DE ; Restore old fcb CALL ZEROFCB ; Initialize rest of fcb POP BC ; Restore old B reg. RET ; ; ------------------------------------------------------------------------- ; "Zero" an fcb for access by setting all bytes after file name/type to 0. ; ; Entry DE = fcb address ; ; Return: The business-end of the fcb is set to all zeros. ; all but PSW are unaffected ; ZEROFCB: PUSH BC ; Save all PUSH DE PUSH HL LD HL,EXTOFF ; Get offset to extent byte ADD HL,DE ; Added to address of fcb EX DE,HL ; In DE reg. XOR A ; Clear Acc. LD B,FCBSZ-EXTOFF ; Get size fcb less name & drive CALL PAD ; And pad with zeros POP HL ; Restore all POP DE POP BC RET ; And done ; ; ------------------------------------------------------------------------- ; Set file name and type wild. ; ; Entry: DE = fcb address ; ; Return: only PSW is affected ; WILDFN: PUSH DE ; Save old regs PUSH BC CALL CLRFCB ; Clear the fcb INC DE ; Point to file name field LD B,FNAMSZ ; Get size of file name and type LD A,'?' ; Get a question mark CALL PAD ; Fill the fcb name with '?' POP BC ; Restore POP DE RET ; All done ; ; ------------------------------------------------------------------------- ; Set extent, s1 and s2 (overflow extent) wild in an fcb. ; ; Entry: DE = fcb address ; ; Return: only PSW is affected ; WILDEXT: PUSH HL ; Save caller's HL LD HL,EXTOFF ; Get offset to extent in HL ADD HL,DE ; Add fcb base address LD A,'?' ; Get a "wild" character LD (HL),A ; Set extent wild INC HL ; And set s1 wild LD (HL),A INC HL LD (HL),A ; And set s2 wild POP HL ; Restore RET ; And done ; ; ------------------------------------------------------------------------- ; Check to see if a character is a legal file name componet. ; ; Entry: Acc. = character to check ; ; Return: ZERO SET if character is OK ; or Acc. contains relative position of invalid character ; CHKCHR: PUSH HL ; Save caller's HL LD HL,ILLTBL ; And use it to reference "illegal table" CALL POS ; See if there is a match in table POP HL ; Restore old RET ; And done ; ILLTBL: DEFB ',;:=''"',0 ; ; ------------------------------------------------------------------------- ; Print a file name. ; ; Entry: HL = pointer to fcb image of filename ; ; Return: Nothing. DE preserved. Other regs. affected. ; PRNFN: LD A,FNAMSZ-1 ; Load INDEX to last character of name ; ; ------------------------------------------------------------------------- ; Print up to the Nth character of a file name ; ; Entry: HL = pointer to fcb image of filename ; Acc. = index to Nth character (0 .. n) ; ; Return: Nothing. DE preserved. Other regs. affected ; PRNFNA: LD C,A ; Move index to C INC C ; Increment index to make COUNT (1 .. n+1) LD B,FNAMSZ ; Load count of all characters in file name PRNF1: LD A,(HL) ; Get a character from file name INC HL ; Point to next CALL TYPE ; Print one char. on console DEC B ; Decrement "done" count LD A,FTYPSZ ; Are we at the "type" field? CP B JP NZ,PRNF2 ; NO - then don't need to: LD A,'.' ; Print the period CALL TYPE ; Between the name and type PRNF2: DEC C ; Decrement "to do" count RET Z ; Return if all done JP PRNF1 ; Else loop ; ; ------------------------------------------------------------------------- ; Print drive/user. ; ; Entry: B = drive number (0 .. 15) ; C = user number (0 .. 15, 32-user environments not supported) ; ; Return: all registers except PSW are preserved. ; PRNDU: LD A,B ; Get drive in Acc. ADD A,'A' ; Add character offset so 0 = 'A' CALL TYPE ; Type it LD A,C ; Get user in Acc. CP 10 ; Is user < 10? JP C,PRNDU1 ; If so, skip SUB 10 ; Else, subtract 10 to find units PUSH AF ; And save this LD A,'1' ; First digit will always be '1' CALL TYPE ; So print that POP AF ; Restore units value PRNDU1: ADD A,'0' ; Add ascii offset CALL TYPE ; And type it LD A,':' ; Get a colon CALL TYPE ; Type that RET ; All done ; ;-------------------------------------------------------------------------- ; Set up the file name ring. ; ; Entry: none ; ; Return: Nothing, all regs affected ; ; Each entry in our file name list will have the following format: ; ; bytes --> 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ; ------------------------------------------- ; contains --> F I L E N A M E T Y P al ah rl rh ; ; where al/ah is a 16-bit image of the file attributes ; and rl/rh is a 16-bit record count for the file ; ;..... ; ; First, make the pointer array ; RING: LD HL,(NARGC) ; Get number of extents LD B,H ; Into BC LD C,L LD HL,NARGBUF ; Get pointer to first entry EX DE,HL ; Into DE LD HL,(IOBUF) ; Get address of free memory in HL LD (NARGV),HL ; Save this as pointer to our pointer array MAKPTR: LD (HL),E ; Put one pointer into array INC HL LD (HL),D INC HL PUSH HL ; Save array address LD HL,NTRYSZ ; Get size of an entry in HL ADD HL,DE ; Add that to last EX DE,HL ; With result in DE POP HL ; Retrieve array address DEC BC ; Decrement count LD A,B ; Test for zero OR C JP NZ,MAKPTR ; Repeat until a pointer is made for all INC HL ; Make room for extra pointer used by INC HL ; Linear search routine LD (IOBUF),HL ; Save new address of I/O buffer ; ; then, sort the file names ; LD HL,(NARGC) ; Get number of entries into HL LD (ASIZE),HL ; Initialize ASIZE for sort LD HL,(NARGV) ; Get base address of NARG pointer array LD (ABASE),HL ; Initialize ABASE for sort LD HL,XCOMP ; Get address of compare routine LD (MYCOMP),HL ; Initialize MYCOMP for sort CALL SSORT ; Ready to sort the array ; ; eliminate duplicate extents (all but last) ; LD HL,(NARGC) ; Get number of entries LD B,H ; Into BC LD C,L LD HL,STRNCMP ; Get address of compare function LD (MYCOMP),HL ; Poke this into place LD DE,0 ; Init. index (i) to first entry CULL1: DEC BC ; Count - 1 LD A,B ; Test for zero OR C JP Z,CULL9 ; Break when all compares have been done LD H,D ; HL = (i) LD L,E INC DE ; DE = (i + 1) = (j) PUSH BC ; Save count LD B,FNAMSZ ; B = size of the file name CALL SSCOMP ; Compare the two file names JP NZ,CULL2 ; Move on if the names are different CALL IDEL ; Else delete the first matching entry DEC DE ; Adjust index for retest CULL2: POP BC ; Restore the file name count JP CULL1 ; Loop until all names (extents) are done ; CULL9: LD HL,(ASIZE) ; Get new size of the list LD (NARGC),HL ; And update the "new argument count" ; ; calculate size of all files ; CALCSZ: LD B,H ; Put nargc into BC LD C,L LD HL,0 ; Init. HL as index (i) to first name LD (DUSAGE),HL ; Init. usage to 0 CALCS1: PUSH HL ; Save index PUSH BC ; And count CALL IGET ; Get address of this name in DE EX DE,HL ; Now put it in HL CALL CMPRES ; Compress attributes into DE PUSH HL ; Save address for attribute word PUSH DE ; And save attribute word LD D,(HL) ; Grab the EXTENT byte from fcb image INC HL ; Point to OVERFLOW EXTENT INC HL LD A,(HL) ; Grab that INC HL ; Point to RECORDS left in last extent LD E,(HL) ; Grab it, too LD L,A ; Make overflow extents a 16-bit number LD H,0 LD B,5 ; Set up for multiply CALL SHL16 ; Extents = overflow extents * 32 LD A,D ; Get extent byte ADD A,L ; And add that in LD L,A LD A,H ADC A,0 LD H,A LD B,7 ; Set up for multiply CALL SHL16 ; Records = extents * 128 LD A,E ; Get records left ADD A,L ; Add that LD L,A LD A,H ADC A,0 LD H,A EX DE,HL ; Put record count into DE POP BC ; Get attribute word into BC POP HL ; Point to address for att. word LD (HL),C ; Install attribute word INC HL LD (HL),B INC HL LD (HL),E ; Install record count INC HL LD (HL),D EX DE,HL ; Put record count back in HL LD A,(BLM) ; Get block mask PUSH AF ; Save a copy ADD A,L ; And add it to record count LD L,A ; In order to round record count LD A,H ; Up to the nearest block ADC A,0 LD H,A POP AF ; Get blm back CPL ; Complemented AND L ; Mask record count - blm LD L,A LD B,3 ; Prepare for divide CALL SHR16 ; K bytes = records / 8 EX DE,HL ; Put K bytes for this file in DE LD HL,(DUSAGE) ; Get total usage for this DU: ADD HL,DE ; Add usage for this file LD (DUSAGE),HL ; And save that POP BC ; Restore count POP HL ; Restore (i)ndex INC HL ; And bump to index next name DEC BC ; Decrement name count LD A,B ; And test for zero OR C JP NZ,CALCS1 ; Repeat for all names RET ; And ring is done ; ; ------------------------------------------------------------------------- ; Compress file name attributes into a 16-bit word ; ; Entry: HL points to file name ; ; Return: DE = 16-bit word with bits 0 - 10 set to correspond ; to file name attributes for characters 10 - 0 ; ; 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 ; ----------------------------------------------- ; 0 0 0 0 0 F I L E N A M E T Y P ; ; All registers are used. ; ; CMPRES: LD DE,0 ; Initialize DE LD B,11 ; Initialize B as count for all char's. CMPRS1: LD A,(HL) ; Get a character from file name INC HL ; Bump pointer RLA ; Roll 8th bit (attribute) into carry LD A,E ; Get low byte of att word RLA ; Roll the attribute into att word low LD E,A ; And put the result back LD A,D ; Get high byte of att word RLA ; Roll bit 7 of low word into it LD D,A ; Put result back DEC B ; Decrement count JP NZ,CMPRS1 ; And repeat for all file name characters RET ; Then return ; ; end of file name module ;========================================================================== ; {3} Disk I/O module ; --------------- ; ------------------------------------------------------------------------- ; Set CP/M Direct Memory Access address. ; ; Entry: HL = the address of the next record to be read/written ; ; Return: none ; all regs. except PSW preserved ; FACCESS: PUSH BC ; Save all PUSH DE PUSH HL EX DE,HL ; Put DMA address in DE LD C,SETDMA ; Do CP/M function to CALL BDOS ; Set the DMA address to our buffer POP HL ; Restore POP DE POP BC RET ; ; ------------------------------------------------------------------------- ; Set file attributes as necessary. ; ; Entry: HL = index (i) to file name ; ABASE: must be previously set to base address of pointer array ; ; The attribute word at FILENAME[0] + FATTS is used to determine ; whether attributes need to be changed. ; ; MYFCB should be ready to receive the file name. ; ; Return: None ; Where necessary the attributes are changed and the attribute ; word is updated to reflect directory status. ; All registers except PSW are preserved. ; NEWATT: PUSH BC ; Save all PUSH DE PUSH HL ; CALL IGET ; Get address of file name in DE EX DE,HL ; Put it in HL CALL CMPRES ; Compress current attributes into DE LD A,E ; Get current, low byte, into Acc. CP (HL) ; Compare with original JP NZ,NEWAT1 ; Break if changed INC HL ; Point to original, high byte LD A,D ; Get current, high byte, into Acc. CP (HL) ; Compare with original JP Z,NEWAT9 ; Quit now if no change necessary DEC HL ; Else point back to original, low byte ; NEWAT1: LD (HL),E ; Install new attribute byte INC HL LD (HL),D LD DE,0-(FATTS+1) ; Get offset back to beginning of name ADD HL,DE ; Add that to get address of name in HL PUSH HL ; Save name's address LD DE,MYFCB ; Point to our fcb PUSH DE ; Save fcb address INC DE ; Now DE points to file name field in fcb LD B,FNAMSZ ; B = number of character in file name CALL MOVE ; Move the name into the fcb POP DE ; Get fcb address back LD C,SETATT ; Do CP/M set attribute function CALL BDOS CALL ILPRT ; Tell the user what's going on DEFB CR,LF,'Setting --> ',0 POP HL ; Restore file name CALL PRNFN ; Print entire file name ; NEWAT9: POP HL ; Restore all POP DE POP BC RET ; Done with one name ; ; ------------------------------------------------------------------------- ; Set user. ; ; Entry: DE = fcb address. User number must be at fcb[0-1] ; ; Return: none ; all registers preserved except PSW ; FSETUSR: PUSH BC ; Save all PUSH DE PUSH HL DEC DE ; Point to user as specified in fcb LD A,(DE) ; Get it LD E,A ; Put it in E LD C,SETUSR ; Get CP/M function number CALL BDOS ; Set the user POP HL ; Restore all POP DE POP BC RET ; And done ; ; ------------------------------------------------------------------------- ; Set drive and user. ; ; Entry: B = drive (0 = A) ; C = user ; ; Return: DE and HL are preserved ; SETDU: PUSH DE ; Save regs. PUSH HL PUSH BC ; Save original LD E,B ; Put drive number into E LD C,SELDSK ; And select new drive CALL BDOS POP BC ; Get original back LD E,C ; Put user number in E LD C,SETUSR ; And set new user CALL BDOS POP HL ; And old regs. POP DE RET ; And done ; ; ------------------------------------------------------------------------- ; Select user and see if a file exists. ; ; Entry: DE = pointer to fcb[0]. Drive byte should be set, and ; fcb[0-1] must be set with user number. ; ; Return: HL and DE are preserved ; BC and PSW are destroyed ; if found, Acc. = offset byte from BDOS search call ; if not found, CARRY SET ; FXIST: PUSH HL ; Save reg. PUSH DE ; Save fcb address CALL ZEROFCB ; Set extent thru end of fcb to zero CALL WILDEXT ; SPECIAL CASE - get any extent LD HL,CMDBUF ; Set DMA address CALL FACCESS ; To CP/M's default buffer CALL FSETUSR ; And set user LD C,SRCHFST ; Get search-first function number CALL BDOS ; Do it POP DE ; Fcb address now back in DE POP HL ; Restore reg ADD A,1 ; Set carry if result = 0ffh DEC A ; But restore original offset in Acc. RET ; Done ; ; ------------------------------------------------------------------------- ; Translate the offset that CP/M gives us after a "search" call to an ; absolute address in the default CP/M DMA block. ; ; Entry: Acc. = offset from search call ; ; Returns: HL = address in DMA of the matching file name ; PSW destroyed, other registers preserved. ; FNOFF: PUSH DE ; Save reg. ADD A,A ; Shift result * 2 ADD A,A ; * 4 ADD A,A ; * 8 ADD A,A ; * 16 ADD A,A ; * 32 LD L,A ; Put name offset in HL LD H,0 LD DE,CMDBUF ; And add this offset to DMA[0] address ADD HL,DE POP DE ; Restore old DE RET ; And return ; ; ------------------------------------------------------------------------ ; Search the directory for all matches to a name. ; ; Entry: DE = pointer to completed fcb, with user number at fcb[0-1]. ; ; Return: NARGC = number of matches found ; NARGBUF will contain the names in fcb-image fields. ; DE preserved, all other registers affected ; CARRY SET if no match was found. ; DIRSRCH: CALL FXIST ; See if file exists RET C ; Return now if not ; LD HL,NARGBUF ; Initialize IOBUF pointer LD (IOBUF),HL ; (it points to end of NARGBUF) DIRS1: PUSH DE ; Save the fcb address from harm CALL FNOFF ; Get address of file name in HL INC HL ; Skip the drive byte EX DE,HL ; Put this address in DE LD HL,(IOBUF) ; Get buffer pointer in HL EX DE,HL ; Now HL = file name, DE = buffer LD B,RCOFF ; B = size of name+typ+ext+s1+s2+rc CALL MOVE ; Move the name, etc., into narg. buffer EX DE,HL ; Get buffer pointer back in HL LD (IOBUF),HL ; And store pointer for next time LD HL,(NARGC) ; Get new argument count INC HL ; Increment it LD (NARGC),HL ; And re-save it EX DE,HL ; Then put it in DE LD HL,(MAXNARG) ; Get max. count CALL SUB16 ; And find difference POP DE ; Restore fcb address RET Z ; And return if we've reached max. count PUSH DE ; Else, save fcb address from harm LD C,SRCHNXT ; Search for next occurrence CALL BDOS POP DE ; Get fcb address back INC A ; Test result for 0ffh, but make sure OR A ; Carry is off since we found at least one RET Z ; Return if no more matches DEC A ; Else undo what the test did JP DIRS1 ; And repeat ; ; ------------------------------------------------------------------------- ; Log into a DU: ; ; Entry: HL points to a possibly ambiguous file name ; in the form DU:NAME.TYP ; ; Return: CARRY SET if error ; else disk system has been reset and DU: has been selected ; and all matching EXTENTS have been loaded into NARGBUF. ; NARGC will contain the number of matching EXTENTS ; LOG: PUSH HL ; Save file name LD C,RSTDSK ; Get function number to reset disk system CALL BDOS ; Do it POP HL ; Restore file name LD DE,MYFCB ; Point to our local fcb CALL DOWILD ; Expand wild cards and load names RET C ; Return now if error LD A,(MYFCB) ; Get DRIVE from fcb DEC A ; In fcb A=1, so make A=0 LD B,A ; Put in B LD A,(MYUSR) ; Get USER LD C,A ; Into C CALL SETDU ; Select drive and user CALL INQ ; Update local tables RET ; All done ; ; end of disk i/o module ;========================================================================== ; {4} Array Processing ; ---------------- ; ------------------------------------------------------------------------- ; Shell sort of an integer array. Based on Kernighan and Ritchie, ; "The C Programming Language", page 58. ; ; Entry: ABASE: and ASIZE: should be set with base address and size ; (in elements) of the integer array. Also, MYCOMP: must be ; loaded with the address of the compare routine that should ; be used. This routine expects access to variables GAP: and N: ; ; Return: none ; all registers affected ; SSORT: LD HL,(ASIZE) ; Get size of array LD (N),HL ; Initialize n LD (GAP),HL ; Initialize gap (gap = n) ; ; for (gap = n/2; gap > 0; gap /= 2) ; SSORT1: LD HL,(GAP) ; HL = gap LD A,H ; Divide gap by 2: get high byte RRA ; Roll it right LD H,A ; Put result back LD A,L ; Get low byte RRA ; Roll it with carry from high byte LD L,A ; Put it back LD (GAP),HL ; Save gap/2 LD A,H ; Test if gap > 0 OR L RET Z ; Done if gap = 0 ; ; for (i = gap; i < n; i++) ; SSORT2: EX DE,HL ; DE = i [ = gap 1st time] LD HL,(N) ; Get n into HL CALL COMPARE ; Test if (i < n) JP NC,SSORT1 ; NO - back to outer loop PUSH DE ; Else save (i) ; ; for (j = i-gap; j >= 0 && comp(array[j],array[j+gap]) > 0; j -= gap) ; SSORT3: LD HL,(GAP) ; HL = gap, DE = (j) [(i) on 1st loop] CALL SUB16 ; Now DE = (j - gap) [(i-gap) on 1st loop] JP C,SSORT4 ; If (j < 0) then break ADD HL,DE ; HL = (j+gap) EX DE,HL ; So now DE = (j+gap), HL = (j) CALL SSCOMP ; Compare (array[j+gap] - array[j]) JP NC,SSORT4 ; Move on if array[j+gap] >= array[j] CALL ISWAP ; Else swap the integers EX DE,HL ; Put (j) back in DE JP SSORT3 ; And repeat ; SSORT4: POP HL ; Retrieve (i) INC HL ; I++ JP SSORT2 ; Repeat ; ;-------------------------------------------------------------------------- ; Generic compare routine for Shell sort an other array routines. ; ; When this routine is called the HL and DE registers contain INDICES to ; two elements in the integer array. The integers may be the objects that ; we want to compare OR they may be pointers to the objects we want to ; compare. For maximum flexibility, a label is provided that will enable ; us to specify the address of the compare routine that should be used. ; The default routine is COMPARE: ; ; Entry: HL = index (i) ; DE = index (j) ; MYCOMP: = address of specific compare routine ; ; Returns: carry set if (i > j) ; zero set if (i = j) ; else (i < j) ; ; only PSW is affected ; SSCOMP: PUSH HL ; Save (i) PUSH DE ; Save (j) ; PUSH DE ; Save (j) again CALL IGET ; Get base[i] in DE EX DE,HL ; Now HL = base[i], DE = (i) EX (SP),HL ; Now HL = (j), stack = base[i] CALL IGET ; Get base[j] into DE POP HL ; Get base[i] into HL ; MYCOMP EQU $+1 ; Address to poke with specific routine CALL COMPARE ; (if Shell (object[j+gap] - object[j])) ; POP DE ; Restore original regs. POP HL RET ; And return with flags set ; ; ------------------------------------------------------------------------- ; Linear search for an element in an array. ; ; Entry: ASIZE: = size of array ; ABASE: = base address of array ; HL = pointer to key ; MYCOMP: should be loaded with address of appropriate compare ; routine. ; ; Note: we must be able to modify array[asize] (after the end ; of the array because array indices are referenced to zero). ; ; Return: If MATCH, zero flag is RESET and HL = index (i) to matching ; element. Zero flag is SET if NO MATCH. ; BC and DE are preserved. ; ISRCH: PUSH DE ; Save DE from harm EX DE,HL ; Put pointer to key in DE LD HL,(ASIZE) ; Get size of array CALL IPUT ; Put key after end of array (array[asize]) EX DE,HL ; Size of array now in DE LD HL,0 ; Init. index to first element in HL ISRCH1: CALL SSCOMP ; Compare array[asize] - array[i] JP Z,ISRCH9 ; Break if equal INC HL ; Else bump index to next element JP ISRCH1 ; And search again ISRCH9: CALL COMPARE ; Compare (ASIZE - index) POP DE ; Restore caller's DE RET ; Index to match is in HL if NOT ZERO ; ; ------------------------------------------------------------------------- ; Compare routine for the "FIND" function ; ; Entry: HL = pointer to name in NARGBUF ; DE = pointer to file name we want to find ; The names should be in FCB format. ; ; Wild cards match anything and a zero in the target terminates ; the search and returns zero (MATCH == true). ; ; Returns: zero flag set if match ; all registers are modified ; FCOMP: LD B,FNAMSZ ; Init. B = size of file name FCOMP1: LD A,(DE) ; Get a byte from the key OR A ; Test for zero RET Z ; Return - MATCH CP '?' ; Wildcard? JP Z,FCOMP2 ; YES - it counts for anything LD C,A ; Save the character from the key LD A,(HL) ; Get byte from NARGBUF AND 07FH ; Mask attribute CP C ; Compare RET NZ ; Return if not the same FCOMP2: DEC B ; Characters match - decrement "bytes to do" RET Z ; Return if we've done them all INC HL ; Else bump pointers INC DE JP FCOMP1 ; And repeat ; ;-------------------------------------------------------------------------- ; Compare of two file extents. ; ; Entry: HL = pointer to first string ; DE = pointer to second string ; ; Returns: carry set if (i > j) ; zero set if (i = j) ; else (i < j) ; ; only PSW is affected ; XCOMP: PUSH BC ; Save all PUSH DE PUSH HL LD B,FNAMSZ ; Get length of file name in B CALL STRNCMP ; Compare JP NZ,XCOMP9 ; Return now if names are different LD B,(HL) ; Else get extent bytes LD A,(DE) ; Into BC LD C,A INC HL ; Point to s2 (overflow extent) bytes INC HL INC DE INC DE LD A,(DE) ; Get one of them CP (HL) ; Compare to the other JP NZ,XCOMP9 ; Return now if they are different LD A,C ; Else get one extent CP B ; Compare to the other XCOMP9: POP HL ; Restore old regs. POP DE POP BC RET ; And return with flags set ; ;-------------------------------------------------------------------------- ; Swap two integers in an array ; ; Entry: HL = index (i) and DE = index (j) ; ; Return: only PSW is affected ; ISWAP: PUSH BC ; Save caller's BC PUSH HL ; Save (i) PUSH DE ; Save (j) ; PUSH HL ; Save (i) again PUSH DE ; Save (j) again CALL IGET ; Get base[i] in DE EX DE,HL ; Now HL = base[i], DE = (i) EX (SP),HL ; Now HL = (j), stack = base[i] CALL IGET ; Get base[j] into DE EX DE,HL ; HL = base[j], DE = (j) EX (SP),HL ; HL = base[i], stack = base[j] EX DE,HL ; DE = base[i], HL = (j) CALL IPUT ; Base[j] = base[i] POP DE ; DE = base[j] POP HL ; HL = (i) CALL IPUT ; Base[i] = base[j] ; POP DE ; Restore caller's regs. POP HL POP BC RET ; ; ------------------------------------------------------------------------- ; Get element (i) from an array of integers ; ; Entry: HL = index (i) ; ABASE: = base address of the array ; ; Return: DE = abase[i], other register left intact ; IGET: PUSH HL ; Save index EX DE,HL ; Put index in DE LD HL,(ABASE) ; Get base address ADD HL,DE ; Add (index * 2) [ 2 = sizeof(int) ] ADD HL,DE LD E,(HL) ; Retrieve the integer at that address INC HL LD D,(HL) POP HL ; Restore index RET ; And done ; ; ------------------------------------------------------------------------- ; Save an element at position (i) in an array of integers ; ; Entry: HL = index (i) ; DE = integer to save ; ABASE: = base address of the array ; ; Return: nothing, only PSW is affected ; IPUT: PUSH HL ; Save index PUSH DE ; Save integer EX DE,HL ; Put index in DE LD HL,(ABASE) ; Get base address ADD HL,DE ; Add ( (i) * sizeof(int) ) ADD HL,DE ; Where sizeof(int) = 2 POP DE ; Restore the integer LD (HL),E ; Put the integer into the array address INC HL LD (HL),D POP HL ; Restore index RET ; Bingo ; ; ------------------------------------------------------------------------- ; Delete element (i) from an integer array. ; ; Entry: HL = index (i) (0 .. n) ; ASIZE: = size of array (in elements 1 .. n) ; ; Return: nothing, only PSW is affected ; IDEL: PUSH DE ; Save callers regs PUSH HL ; EX DE,HL ; DE = index LD HL,(ASIZE) ; HL = size DEC HL ; HL = size - 1 = new size after delete LD (ASIZE),HL ; Save new size EX DE,HL ; Now DE = size, HL = index CALL SUB16 ; Now DE = (size - index), HL = index JP C,IDEL9 ; Return if (index > size) ; IDEL1: LD A,D ; Test to see if swap count = zero OR E JP Z,IDEL9 ; YES - then done PUSH DE ; Else save swap count (size-index) LD D,H ; Let DE = index LD E,L INC DE ; Now DE = index + 1, HL = index CALL ISWAP ; Swap adjacent elements INC HL ; HL = index + 1 POP DE ; Retrieve count of elements to swap DEC DE ; Less one JP IDEL1 ; And repeat ; IDEL9: POP HL ; Restore old regs. POP DE RET ; ; end of array processing module ;========================================================================== ; {5} System Info Module ; ------------------ ; ; Routines in this module will get the disk parameter info, read the allo- ; cation vector, find system memory limits, etc. ; ------------------------------------------------------------------------ ; Get all system information ; ; Entry: none ; ; Return: local tables will be completed ; any registers may be affected ; INQ: CALL MEMMAP ; Get memory map CALL GETDU ; Get drive and user CALL DSKINF ; Get disk information RET ; ; ------------------------------------------------------------------------- ; Print all system information ; ; Entry: call INQ: first ; ; Return: none - any regs. may be affected ; PSTAT: CALL ILPRT ; Print title DEFB CR,LF DEFB LF,TAB,'System information',CR DEFB LF,TAB,'------------------',CR,LF DEFB 'MEMORY:',CR,LF DEFB TAB,'TPA = ',0 LD HL,TPA ; Print TPA limits CALL DHXOUT CALL ILPRT DEFB ' - ',0 LD HL,(MYBDOS) DEC HL CALL DHXOUT CALL ILPRT DEFB CR,LF,TAB,'CCP = ',0 ; Print CCP limits LD HL,(MYCCP) CALL DHXOUT CALL ILPRT DEFB ' - ',0 LD HL,(MYBDOS) DEC HL CALL DHXOUT CALL ILPRT DEFB CR,LF,TAB,'S/N = ',0 ; Print Serial No. limits INC HL CALL DHXOUT CALL ILPRT DEFB ' - ',0 LD DE,5 ADD HL,DE CALL DHXOUT CALL ILPRT DEFB CR,LF,TAB,'BDOS = ',0 ; Print BDOS limits INC HL CALL DHXOUT CALL ILPRT DEFB ' - ',0 LD HL,(MYBIOS) DEC HL CALL DHXOUT CALL ILPRT DEFB CR,LF,TAB,'BIOS = ',0 ; Print BIOS limits INC HL CALL DHXOUT CALL ILPRT DEFB ' - End of BIOS can''t be detected.' ; DEFB CR,LF,LF,'IOBYTE:',0 ; Print device assignments LD C,GETIOB ; Get system iobyte CALL BDOS LD HL,DEVCON ; Point to device name table LD B,5 ; Set up to do 4 devices ; IOBLUP: DEC B ; Decrement device count JP Z,PDSK ; Quit when all done PUSH AF ; Save iobyte CALL ILPRT ; Print logical device name DEFB CR,LF,TAB,0 CALL PRINT CALL ILPRT DEFB ': is ',0 POP AF ; Retrieve iobyte PUSH AF ; And save it again PUSH HL ; Save pointer to device names AND 3 ; Mask 2 bits for one device ADD A,A ; And multiply this ADD A,A ; Times 4 to get index into table LD E,A ; Put the index into DE LD D,0 ADD HL,DE ; And add this to the base address CALL PRINT ; Then print the physical dev. name LD A,':' ; Plus a colon CALL TYPE POP HL ; Restore base address LD DE,16 ; Increment for next logical device ADD HL,DE POP AF ; Restore the iobyte RRCA ; And roll bits for next device RRCA ; Into bits 0 and 1 JP IOBLUP ; Do it again ; PDSK: CALL ILPRT ; We've filled the screen so let DEFB CR,LF,LF,'Hit any key ...',0 ; User tell us to proceed CALL GETCHAR CALL ILPRT DEFB CR,LF,LF,'DISK:',CR,LF,LF,' ',0 ; Print disk info LD A,(DRIVE) ; Print drive letter ADD A,'A' CALL TYPE CALL ILPRT DEFB ': Drive Characteristics',CR,LF,0 LD HL,(DSKSZR) ; Print all this other junk CALL DECO5 CALL ILPRT DEFB ': 128 Byte Record Capacity',CR,LF,0 LD HL,(DSKSZK) CALL DECO5 CALL ILPRT DEFB ': Kilobyte Drive Capacity (',0 LD HL,(DSM) INC HL CALL DECO0 CALL ILPRT DEFB ' blocks)',CR,LF,0 LD HL,(DIRSZ) CALL DECO5 CALL ILPRT DEFB ': 32 Byte Directory Entries (',0 LD HL,(DIRSZR) CALL DECO0 CALL ILPRT DEFB ' records, ',0 LD HL,(DIRSZK) CALL DECO0 CALL ILPRT DEFB 'k)',CR,LF,0 LD HL,(CHEKD) CALL DECO5 CALL ILPRT DEFB ': Checked Directory Entries',CR,LF,0 LD HL,(EXTSZR) CALL DECO5 CALL ILPRT DEFB ': Records/ Extent',CR,LF,0 LD HL,(BLKSZR) CALL DECO5 CALL ILPRT DEFB ': Records/ Block (',0 LD HL,(BLKSZK) CALL DECO0 CALL ILPRT DEFB 'k/ Block)',CR,LF,0 LD HL,(SPT) CALL DECO5 CALL ILPRT DEFB ': Sectors/ Track',CR,LF,0 LD HL,(TOFF) CALL DECO5 CALL ILPRT DEFB ': Reserved Tracks',0 PFREE: CALL NEWLINE CALL NEWLINE LD HL,(NARGC) ; Print number of files CALL DECO5 CALL ILPRT DEFB ': Files',CR,LF,0 LD HL,(DUSAGE) CALL DECO5 CALL ILPRT DEFB ': Kilobytes used on User',0 LD A,(USER) ; Print user number LD L,A ; (DECO wants 16 bits in HL) LD H,0 CALL DECO3 CALL ILPRT DEFB ':',CR,LF,0 LD HL,(KBUSED) CALL DECO5 CALL ILPRT DEFB ': Kilobytes used on Drive ',0 LD A,(DRIVE) ADD A,'A' CALL TYPE CALL ILPRT DEFB ':',CR,LF,0 LD HL,(KBFREE) CALL DECO5 CALL ILPRT DEFB ': Kilobytes Free',CR,LF,0 JP OBEY0 ; Back to obey loop ; ; ------------------------------------------------------------------------- ; Get Memory Map ; ; Entry: none ; ; Return: HL and DE destroyed ; MEMMAP: LD HL,(BDOS+1) ; Get bdos address LD DE,-6 ; Minus 6 = real start of BDOS (serial no.) ADD HL,DE LD (MYBDOS),HL ; Save bdos address PUSH HL ; With an extra copy on stack LD DE,-129 ; Subtract 128 + 1 bytes ADD HL,DE ; (128 gives us a "safe" area) LD (MEMTOP),HL ; And save that as top of usable memory POP HL ; Restore bdos address LD DE,-800H ; Minus 0800h = start of CCP ADD HL,DE LD (MYCCP),HL ; Save that LD HL,(SBASE+1) ; Get warmstart address DEC HL ; Minus 3 = start of BIOS DEC HL DEC HL LD (MYBIOS),HL ; Save that RET ; Done ; ; ------------------------------------------------------------------------- ; Get all disk info ; ; Entry: none ; ; Return: completes our local info table ; uses all registers ; DSKINF: LD C,GETDPB ; Get function number CALL BDOS ; To get DPB address in HL LD DE,DPBSTRT ; Point to beginning of our DPB LD B,DPBEND-DPBSTRT ; Load size of our DBP CALL MOVE ; Move DPB into place LD A,(BSH) ; Get block shift factor LD B,A ; Into B PUSH BC ; Save a copy of bsh on stack LD HL,1 ; Block size in records CALL SHL16 ; = 1 << bsh LD (BLKSZR),HL ; Save that LD B,3 ; Block size in K bytes CALL SHR16 ; = blkszr >> 3 LD (BLKSZK),HL ; Save it LD HL,(DSM) ; Get max. block on disk INC HL ; + 1 = disk size in blocks LD (DSKSZB),HL ; Save that POP BC ; Retrieve bsh CALL SHL16 ; Disk size in recs LD (DSKSZR),HL ; = (dsm + 1) << bsh LD B,3 ; Disk size in K bytes CALL SHR16 ; = dskszr >> 3 LD (DSKSZK),HL ; Squirrel it away LD HL,(DRM) ; Get max. directory entry INC HL ; + 1 = dir. size in entries LD (DIRSZ),HL ; For a rainy day LD B,2 ; Dir. size in records CALL SHR16 ; = (drm + 1) >> 2 LD (DIRSZR),HL ; Finders keepers LD B,3 ; Dir. size in K bytes CALL SHR16 ; = (drm + 1) >> 5 LD (DIRSZK),HL ; Losers weepers LD A,(EXM) ; Get extent mask INC A ; + 1 LD L,A ; Into HL LD H,0 ; With high byte set to zero LD B,7 ; Records per extent CALL SHL16 ; = (exm + 1) << 7 LD (EXTSZR),HL ; Saved LD HL,(CKS) ; Get (DRM + 1)/4 (for removable media) LD B,2 ; Or (0) (for fixed media) CALL SHL16 ; Number of checked dir. entries LD (CHEKD),HL ; = cks << 2 ; LD C,GETALV ; Get function number CALL BDOS ; To retrieve allocation vector EX DE,HL ; Put ALV into HL register LD HL,(DSM) ; Get maximum block on disk INC HL ; + 1 = total blocks LD BC,0 ; Initialize count of free blocks ; INQFRE: PUSH DE ; Save the ALV address LD A,(DE) ; Get a byte from the alloc. table LD E,8 ; Set up a count for 8 bits/byte INQFR1: RLA ; Roll an alloc. bit into carry JP C,INQFR2 ; Jump if block NOT FREE (bit = 1) INC BC ; Else increment free count INQFR2: LD D,A ; Save the alloc. byte from harm DEC HL ; Decrement blocks left to check LD A,H ; And test for zero OR L JP Z,INQFR3 ; Quit when all blocks are done LD A,D ; Retrieve the alloc. byte DEC E ; Decrement bit count JP NZ,INQFR1 ; Loop until all 8 bits done POP DE ; Then retrieve ALV address INC DE ; Set to next alloc. byte in table JP INQFRE ; And loop until all bytes are done INQFR3: POP DE ; Then clean up the stack LD D,B ; Move free count to DE LD E,C LD HL,(DSM) ; Get max. blocks in HL INC HL ; + 1 = total blocks EX DE,HL ; Now DE = total, HL = free CALL SUB16 ; Now DE = (total - free) = blks. used LD A,(BSH) ; Get bsh - 3 SUB 3 ; (referenced to K bytes not recs) LD B,A ; Put that in b PUSH BC ; And save a copy CALL SHL16 ; Kilobytes free = blocks free << (bsh - 3) LD (KBFREE),HL ; Save Kb free POP BC ; Retrieve (bsh - 3) EX DE,HL ; Put number of blocks used in HL CALL SHL16 ; Kilobytes used = blocks used << (bsh - 3) EX DE,HL ; Now DE = total Kb used, including DIR. LD HL,(DIRSZK) ; And HL = directory size in Kb CALL SUB16 ; Now DE = (total Kb used - directory size) EX DE,HL ; So now HL = Kb used by FILES LD (KBUSED),HL ; Save Kb used RET ; And we're done ; ; ------------------------------------------------------------------------- ; Get current drive and user. ; ; Entry: none ; ; Return: DRIVE: and USER: are filled ; all registers may be affected ; GETDU: LD C,GETDSK ; Get current disk drive number CALL BDOS LD (DRIVE),A ; Save it LD E,0FFH ; Get current user number LD C,GETUSR CALL BDOS LD (USER),A ; Save it RET ; ; end of system info module ;========================================================================== ; {6} Arithmetic/Logic subroutines ; ---------------------------- ; ------------------------------------------------------------------------- ; 16-bit subtract. ; ; Entry: DE = minuend ; HL = subtrahend ; ; Return: DE = DE - HL ; BC and HL preserved, PSW destroyed ; SUB16: LD A,E ; Subtract low nybble SUB L LD E,A ; Result in E LD A,D ; Subtract high nybble, with borrow SBC A,H LD D,A ; Result in D RET ; ; ------------------------------------------------------------------------- ; 16-bit logical shift RIGHT 0 --> [ HL ] --> [cf] ; ; Entry: HL = value to shift ; B = number of times to shift ; ; Return: HL = result, B = 0 ; PSW affected ; SHR16: LD A,B ; Get count in Acc. OR A ; Test for zero and clear carry flag RET Z ; Return when count is zero LD A,H ; Get high byte RRA ; Roll it right LD H,A ; Put result back LD A,L ; Get low byte RRA ; Roll it with carry from high byte LD L,A ; Put it back DEC B ; Decrement count JP SHR16 ; Repeat until count is zero ; ; ------------------------------------------------------------------------- ; 16-bit logical shift LEFT [cf] <-- [ HL ] <-- 0 ; ; Entry: HL = value to shift ; B = number of times to shift ; ; Return: HL = result, B = 0 ; PSW affected ; SHL16: LD A,B ; Get count OR A ; Test for zero RET Z ; Return when count is zero ADD HL,HL ; Shift HL left (multiply HL * 2) DEC B ; Decrement count JP SHL16 ; Repeat until count is zero ; ; ------------------------------------------------------------------------- ; Unsigned compare of two integers. ; ; Entry: HL = integer i, DE = integer j ; ; Returns: carry set if (i > j) ; zero set if (i = j) ; else (i < j) ; COMPARE: LD A,D ; Test high bytes CP H RET NZ ; Return now if (i <> j) LD A,E ; Test low bytes CP L RET ; And done ; ; ------------------------------------------------------------------------- ; Compare two STRINGS of a specified length. ; ; Entry: HL points to string #1 ; DE points to string #2 ; B = width (number of characters to compare) ; ; Return: ---> if carry set then HL > DE ; else if zero clear then HL < DE ; else if zero set then HL = DE ; ; HL and DE are incremented to end or first unequal byte ; B and Acc. destroyed ; STRNCMP: LD A,(HL) ; Get a byte from [HL] AND 07FH ; Mask parity bit LD C,A ; Hold it LD A,(DE) ; Get a byte from [DE] AND 07FH ; Mask parity bit CP C ; Compare two bytes RET NZ ; Return if not the same INC DE ; Increment pointers INC HL DEC B ; And count JP NZ,STRNCMP ; Continue until B bytes are done RET ; ; end of arithmetic/logic module ;========================================================================== ; {7} Console I/O module ; ------------------ ;-------------------------------------------------------------------------- ; Print one file name, complete with size and attribute information ; ; Entry: FNDX: = index (i) to the name ; ; Return: nothing, all but PSW are preserved ; NAMOUT: PUSH DE ; Save caller's regs. PUSH BC PUSH HL ; LD HL,(FNDX) ; Get index CALL IGET ; Then get address of name EX DE,HL ; Into HL LD (CPTR),HL ; This will be the base of char. pointer LD A,FNAMSZ-1 ; Get index to last char. of file name CALL NAMO5 ; Print file number and file name CALL FENCE ; Plus the "fence" LD A,(HL) ; Get the attribute word, low byte RRCA ; And align first "type" RRCA ; Attribute with RRCA ; Bit 7 LD C,A ; Put result in C INC HL INC HL ; Point to record count EX DE,HL ; And put that in DE LD HL,ATTAGS ; Get address of attribute tag strings LD B,3 ; Count of tags to do NAMO1: LD A,SPACE ; Print a space CALL TYPE LD A,C ; Get attribute byte RLCA ; Roll attribute bit into carry LD C,A ; Save result PUSH AF ; Save the flags CALL NC,PRINT ; If attribute is "off" print string INC HL ; Increment pointer to next string INC HL INC HL INC HL POP AF ; Restore flags JP NC,NAMO2 ; Move on if "off" tag was printed CALL VIDON ; Else turn video attribute on CALL PRINT ; Print the "on" tag CALL VIDOFF ; Turn the video attribute off NAMO2: DEC B ; Decrement count JP NZ,NAMO1 ; And repeat till all attributes done CALL FENCE ; Print next "fence" LD A,SPACE ; And a space CALL TYPE LD A,(DE) ; Get record count LD L,A INC DE LD A,(DE) LD H,A CALL DECO5 ; Print it, right justified 5 places CALL ILPRT DEFB ' Recs ',0 PUSH HL ; Save "raw" record count LD A,(BLM) ; Get block mask PUSH AF ; Save a copy ADD A,L ; And add it to record count LD L,A ; In order to round record count LD A,H ; Up to the nearest block ADC A,0 LD H,A POP AF ; Get blm back CPL ; Complemented AND L ; Mask record count - blm LD L,A LD B,3 ; Prepare for divide CALL SHR16 ; K bytes = records / 8 CALL DECO3 ; Print rounded K bytes CALL ILPRT DEFB 'K (',0 POP HL ; Retrieve old record count PUSH HL ; Resave it LD DE,7 ; And use blm for 1k ADD HL,DE ; To round to nearest 1k LD B,3 ; K bytes = records / 8 CALL SHR16 CALL DECO3 ; And print real K bytes CALL ILPRT DEFB 'K) ',0 POP HL ; Get old record count again LD B,7 ; Divide again CALL SHR16 ; LOGICAL EXTENTS = records / 128 LD A,(EXM) ; Get extent mask NAMO3: RRCA ; Divide logical extents by JP NC,NAMO4 ; 1, 2, 4, 8, or 16 PUSH AF ; To solve for PHYSICAL EXTENTS LD B,1 CALL SHR16 POP AF JP NAMO3 NAMO4: INC HL ; Reference to 1 CALL DECO3 ; Print physical extents CALL ILPRT DEFB ' ext',0 CALL FENCE ; End fence LD A,(ATT) ; Get attribute index PUSH AF ; Save it CALL NAMO5 ; Re-print file name up to ATT character CALL BAKSPC ; Backspace to proper character POP AF ; Retrieve att CP 7 ; At end of "name" field? CALL Z,BAKSPC ; YES - then backspace again to skip '.' LD E,A ; Put ATT in DE to make a 16-bit number LD D,0 LD HL,(CPTR) ; Retrieve base char. pointer ADD HL,DE ; And add ATT index to base LD (CPTR),HL ; Save this as new char. ptr ; POP HL ; Restore old regs POP BC POP DE RET ; Done printing file name, etc. ; ; print the file number and file name portions of the file name line ; NAMO5: PUSH AF ; Save index to file name character LD A,CR ; Make sure we're at the start of the line CALL TYPE LD HL,(FNDX) ; Get file index INC HL ; Reference index to 1 CALL DECO3 ; Print the FILE NUMBER CALL ILPRT ; Print separator DEFB '. ',0 LD A,(DRIVE) ; Get drive LD B,A LD A,(USER) ; And user LD C,A CALL PRNDU ; Print them LD HL,(CPTR) ; Get pointer to file name POP AF ; Restore index to character CALL PRNFNA ; Print the file name or portion thereof RET ; And return ; ; ------------------------------------------------------------------------- ; Get a string of characters from the console. Warmboots if control-C ; is entered. ; ; Entry: HL = address of buffer ; Acc. = maximum length of string ; ; Return: Acc. = number of characters in string ; HL = address of string ; string will be all upper case, zero-terminated ; all regs. may be affected ; GETS: PUSH BC ; Save caller's BC PUSH HL ; Save address of buffer' LD B,A ; Max. length in B LD C,0 ; Current length in C GETS1: CALL GETCHAR ; Get a character CP 'C'-64 ; Control-C? JP Z,SBASE ; YES - warm boot CP CR ; Carriage return? JP Z,GETS9 ; YES - all done CP BS ; Backspace? JP Z,GETSBK ; YES - do it CP DEL ; Delete ? JP Z,GETSBK ; YES - treat like BS CP SPACE ; Printable? JP C,GETS1 ; NO - then ignore it LD (HL),A ; Else store it CALL TYPE ; And type it INC HL ; Increment pointer INC C ; Increment length DEC B ; Decrement max. length JP NZ,GETS1 ; And repeat if not zero GETS9: CALL NEWLINE ; Else do cr/lf LD (HL),0 ; Terminate the string LD A,C ; Length in Acc. POP HL ; Restore original address of buffer POP BC ; Restore old BC RET ; And done ; GETSBK: LD A,C ; Get length OR A ; Test JP Z,GETS1 ; And do nothing if length = 0 CALL ILPRT ; Else erase one character DEFB BS,SPACE,BS,0 DEC C ; And decrement length DEC HL ; Decrement pointer INC B ; And increment max. length JP GETS1 ; And get some more ; ; ------------------------------------------------------------------------- ; Console character input. Waits for input. No echo. ; ; Entry: none ; ; Return: character is in Acc. CONVERTED TO UPPER CASE ; other regs. intact ; GETCHAR: PUSH BC ; Save old environment PUSH DE PUSH HL GETCH1: LD E,0FFH LD C,DCONIO ; Get character from console CALL BDOS OR A JP Z,GETCH1 AND 07FH ; Mask parity CALL TOUPPER ; Convert to upper case POP HL ; Restore other stuff POP DE POP BC RET ; And done ; ; -------------------------------------------------------------------------- ; Type a backspace on the console ; ; Entry: none ; ; Return: none, all registers preserved ; BAKSPC: PUSH AF ; Save Acc. LD A,BS ; Get backspace character CALL TYPE ; Type it POP AF ; Restore Acc. RET ; All done ; ; ------------------------------------------------------------------------- ; In-Line PRinT routine. ; ; Entry: Stack = ; ; Return: All registers except PSW are preserved. ; ILPRT: EX (SP),HL ; Stack points to message so put in HL CALL PRINT ; Print it EX (SP),HL ; HL has return address so put it on stack RET ; And get old HL back, and done. ; ; ------------------------------------------------------------------------- ; Print a string. ; ; Entry: HL = pointer to ZERO TERMINATED string ; ; Return: HL += string length + 2 (points to the character after the zero) ; PSW destroyed. Rest preserved. ; PRINT: LD A,(HL) ; Get a character INC HL ; Point to next OR A ; Test for zero RET Z ; Return if zero CALL TYPE ; Else type the character JP PRINT ; And repeat ; ; ------------------------------------------------------------------------- ; Type a character - supports video attribute ; ; Entry: none ; ; Return: none ; all registers preserved ; TYPE: PUSH BC ; Save regs. PUSH DE PUSH HL ; OR A ; Test - is 8th bit set? JP P,TYPE1 ; NO - type as usual PUSH AF ; Else, set video attribute ON LD HL,ATTON CALL PRINT POP AF ; Retrieve character ; TYPE1: PUSH AF ; Save the character AND 7FH ; Mask hi-bit for CRT - b/m LD E,A ; Type the character LD C,CONOUT CALL BDOS POP AF ; Retrieve original character ; JP P,TYPE2 ; Move on if 8th bit not set PUSH AF ; Else, turn video attribute OFF LD HL,ATTOFF CALL PRINT POP AF ; TYPE2: POP HL ; Restore regs. POP DE POP BC RET ; And return ; ; ------------------------------------------------------------------------- ; Print a carriage return/line feed combination on console. ; ; Entry: none ; ; Return: All registers preserved. ; NEWLINE: PUSH AF ; Save caller's Acc/status CALL ILPRT ; Send it DEFB CR,LF,0 POP AF ; Restore RET ; And that's it ; ; --------------------------------------------------------------------------- ; Print the "fence" character ; ; Entry: none ; ; Return: none ; PSW destroyed ; FENCE: CALL ILPRT DEFB ' |',0 RET ; ; --------------------------------------------------------------------------- ; Turn video attributes ON/OFF ; ; Entry: none ; ; Return: none, all regs. preserved ; VIDON: PUSH HL ; Save all PUSH AF LD HL,ATTON ; Get address of attribute string CALL PRINT ; Send it POP AF ; Restore all POP HL RET ; And done ; VIDOFF: PUSH HL ; Similarly PUSH AF LD HL,ATTOFF CALL PRINT POP AF POP HL RET ; ;-------------------------------------------------------------------------- ; Decimal output routines. ; ; Entry: HL = number to type ; expects access to DWID: (1 byte) ; ; Return: nothing ; all but PSW are preserved; ; DECO0: XOR A ; Call here if justify is not req'd LD (DWID),A JP DECO ; DECO3: LD A,3 ; Call here to output width 3 LD (DWID),A JP DECO ; DECO5: LD A,5 ; Call here to output width 5 LD (DWID),A ; DECO: PUSH BC ; Save all PUSH DE PUSH HL LD A,(DWID) ; Get decimal width OR A ; Is it zero? JP Z,DECOA ; YES - then we don't need to DEC A ; Decrement width count by one LD (DWID),A DECOA: LD BC,-10 ; Get a minus 10 for subtract LD DE,-1 ; Init. result to minus 1 ; DECOB: ADD HL,BC ; Subtract 10 INC DE ; And bump result JP C,DECOB ; Until overflow LD BC,10 ; Add 10 back in ADD HL,BC ; To leave remainder in HL EX DE,HL ; Swap - put result into HL LD A,H ; Test result OR L ; For zero CALL NZ,DECO ; And call recursively until all digits done CALL JSTFY ; Justify the number to the right as necessary LD A,E ; Get a result digit ADD A,'0' ; Add ascii offset CALL TYPE ; Send it POP HL ; And restore all POP DE POP BC RET ; That's all ; JSTFY: LD A,(DWID) ; Get decimal width OR A ; Is it already zero? RET Z ; YEP - then we do nothing DEC A ; Else decrement the width count LD (DWID),A ; And save that so we can LD A,SPACE ; Get a pad character in Acc. CALL TYPE ; And send that JP JSTFY ; DWID times ; ; ------------------------------------------------------------------------- ; Output a 4-digit hex number. ; ; Entry: HL = number to output ; ; Return: None ; all registers preserved ; DHXOUT: PUSH HL ; Save regs PUSH AF LD A,H ; Get MSB CALL HEXO ; Send it LD A,L ; Get LSB CALL HEXO ; Send it POP AF POP HL ; Restore RET ; That's it ; ; ------------------------------------------------------------------------- ; Output one hex byte to the console. ; ; Entry: Acc. = byte to output ; ; Return: none ; PSW destroyed, other registers unaffected ; HEXO: PUSH AF ; Save original digit RRA ; Roll high nybble into low nybble RRA RRA RRA CALL NYBBLE ; And send it POP AF ; Restore original (to do low part) NYBBLE: AND 0FH ; Mask out the high nybble CP 10 ; Is it < 10? JP C,DIGOUT ; YES - ready to send digit ADD A,7 ; Else, add alpha offset DIGOUT: ADD A,'0' ; Add ascii offset CALL TYPE ; And send it to the console RET ; ; end of console i/o module ;========================================================================== ; {8} Various utility subroutines ; --------------------------- ; ------------------------------------------------------------------------- ; Retrieve an ascii decimal digit. ; ; Entry: HL = pointer to digit ; ; Return: Acc. = legal decimal digit or CARRY SET if illegal ; GETASCD: LD A,'9' ; Is character > '9' CP (HL) RET C ; Return if so LD A,(HL) ; Get the character SUB '0' ; And subtract ascii offset RET ; And done ; ; ------------------------------------------------------------------------- ; Convert character in Acc. to upper case ; ; Entry: Acc. = character to convert ; ; Return: Acc. = converted character IF character was lower case ; other regs. unaffected ; TOUPPER: CP 'a' ; Skip if not 'a' <= CHAR <= 'z' RET C CP 'z'+1 RET NC AND 05FH ; Else change to upper case RET ; ; ------------------------------------------------------------------------- ; Move memory. ; ; Entry: HL = source ; DE = destination ; B = count of bytes to move ; ; Return: HL += B, DE += B, B = 0, PSW destroyed. ; MOVE: LD A,(HL) ; Get source LD (DE),A ; Put at destination INC HL ; Bump pointers INC DE DEC B ; Decrement counter JP NZ,MOVE ; Until zero RET ; And finis ; ; ------------------------------------------------------------------------- ; Pad (fill) memory. ; ; Entry: Acc. = byte to use for pad ; DE = starting location ; B = count of bytes to fill ; ; Return: DE += B, B = 0, character still in Acc., zero flag set ; PAD: LD (DE),A ; Put byte at [DE] INC DE ; Point to next DEC B ; Decrement count JP NZ,PAD ; Continue until all bytes done RET ; And return ; ; ------------------------------------------------------------------------- ; Return POSition of byte in Acc. as it occurs in a string. ; ; Entry: Acc. = byte to look for ; HL = pointer to ZERO-TERMINATED character string ; ; Return: Acc. = position indexed 1 .. n if found or ; ZERO flag SET if NOT FOUND ; all other registers preserved ; POS: PUSH BC ; Get a register to use PUSH HL ; Save pointer to string LD B,0 ; Initialize position counter LD C,A ; Byte to look for is in C POS1: INC B ; Bump counter LD A,(HL) ; Get byte from string INC HL ; Point to next OR A ; Test for terminator JP Z,POSDUN ; Quit if end of string CP C ; Compare the two bytes JP NZ,POS1 ; Do another if not equal LD A,B ; We found it - put position in Acc. OR A ; Set flags (non-zero) POSDUN: POP HL ; Restore old regs. POP BC RET ; And back we go ; ; ------------------------------------------------------------------------- ; Print an error message and return. ; ; Entry: Stack: ; (similar to ILPRT) ; HL = pointer to another optional message OR 0000h ; ; Return: CARRY always SET ; PSW and HL destroyed, other regs. preserved. ; ERRET: PUSH HL ; Save HL (second optional message) CALL ILPRT ; Identify this as an error message DEFB CR,LF,'ERROR: ',BELL,0 POP HL ; Get 2nd error message off stack EX (SP),HL ; Swap it with primary one CALL PRINT ; Print primary one POP HL ; Get 2nd again LD A,H ; See if it is a good address OR L CALL NZ,PRINT ; Print if HL > 0000 CALL NEWLINE ; Print carriage return/line feed SCF ; Also set carry as error indicator RET ; And go give 'em the bad news ; ; ------------------------------------------------------------------------- ; Print help menu. ; ; Entry: none ; ; Return: none ; terminates with a jump to OBEY0 loop ; PSW is affected ; MENU: CALL ILPRT ; Print help message DEFB CR,LF,LF ; DEFB ' or move forward one file',CR,LF DEFB ' or move back one file',CR,LF DEFB ' or move the cursor left',CR,LF DEFB ' or move cursor right',CR,LF DEFB ' or toggle file attribute',CR,LF DEFB ' or set file attributes',CR,LF DEFB ' find a file',CR,LF DEFB ' print disk information',CR,LF DEFB ' log new DU:',CR,LF DEFB ' next line auto-advance on/off',CR,LF DEFB ' print free space',CR,LF DEFB ' quit and return to CP/M',CR,LF DEFB ' print this help menu',CR,LF DEFB 0 ; <<=== terminating zero must be here... JP OBEY0 ; All done ; ; end of utility routines ;========================================================================== ; {9} Initialized Data ; ---------------- ; ------------------------------------------------------------------------- ; Device assignments ; DEVCON: DEFB 'CON',0,'TTY',0,'CRT',0,'BAT',0,'UC1',0 DEVRDR: DEFB 'RDR',0,'TTY',0,'PTR',0,'UR1',0,'UR2',0 DEVPUN: DEFB 'PUN',0,'TTY',0,'PTP',0,'UP1',0,'UP2',0 DEVLST: DEFB 'LST',0,'TTY',0,'CRT',0,'LPT',0,'UL1',0 ; ; Attribute tags ; ATTAGS: DEFB 'R/W',0,'R/O',0 DEFB 'DIR',0,'SYS',0 DEFB 'NEW',0,'ARC',0 ; ;========================================================================== ; Local stack ; ----------- ; VERS: DEFB 'SSTAT - version ',VTENS,'.',VUNITS DEFB 'Copyright (C) David Jewett, III - 1986' DEFS VERS + 128 - $ STACK EQU $ ; ;========================================================================== ; Uninitialized Data ; ------------------ ; ; Information on our environment ; LOGDRV: DEFS 1 ; Drive at startup LOGUSR: DEFS 1 ; User at startup DRIVE: DEFS 1 ; Current drive USER: DEFS 1 ; Current user OLDSP: DEFS 2 ; System's stack pointer at startup IOBUF: DEFS 2 ; Address of free memory space MEMTOP: DEFS 2 ; Address of top of usable memory MYCCP: DEFS 2 ; Address of CCP MYBDOS: DEFS 2 ; Address of BDOS MYBIOS: DEFS 2 ; Address of BIOS ; DPBSTRT EQU $ ; Start of our copy of the DBP SPT: DEFS 2 ; Sectors per track BSH: DEFS 1 ; Block shift factor BLM: DEFS 1 ; Block mask EXM: DEFS 1 ; Extent mask DSM: DEFS 2 ; Disk size in blocks - 1 DRM: DEFS 2 ; Directory size in entries - 1 AL0: DEFS 1 ; Directory allocation mask low AL1: DEFS 1 ; Directory allocation mask high CKS: DEFS 2 ; (DRM + 1)/4 or 0 (removable/fixed media) TOFF: DEFS 2 ; Number of reserved tracks DPBEND EQU $ ; End of our DPB ; BLKSZK: DEFS 2 ; Allocation block size in Kilobytes BLKSZR: DEFS 2 ; Allocation block size in 128-byte records DSKSZB: DEFS 2 ; Disk capacity in blocks DSKSZK: DEFS 2 ; Disk capacity in Kilobytes DSKSZR: DEFS 2 ; Disk capacity in 128-byte records DIRSZ: DEFS 2 ; Directory size in entries DIRSZK: DEFS 2 ; Directory size in Kilobytes DIRSZR: DEFS 2 ; Directory size in 128-byte records KBFREE: DEFS 2 ; Number of free Kilobytes on disk KBUSED: DEFS 2 ; Number of used Kilobytes on disk CHEKD: DEFS 2 ; Number of checked directory entries EXTSZR: DEFS 2 ; Records per extent ; ; Other data ; DWID: DEFS 1 ; Decimal output width CPTR: DEFS 2 ; Character pointer into file names ATT: DEFS 1 ; Attribute index (0-10) FNDX: DEFS 2 ; File index (0-NARGC) DUSAGE: DEFS 2 ; Kilobytes used on current DU: ; ; File control block. Note that MYFCB - 1 = user number. ; MYUSR: DEFS 1 ; User number MYFCB: DEFS 1 ; Drive number DEFS 8 ; File name DEFS 3 ; File type DEFS 1 ; Extent DEFS 1 ; S1 DEFS 1 ; S2 DEFS 1 ; Record count DEFS 16 ; Allocation info ; ; Support for integer array processing ; ABASE: DEFS 2 ; Base address of array ASIZE: DEFS 2 ; Size of array (in elements) GAP: DEFS 2 ; Gap for Shell sort N: DEFS 2 ; N variable for Shell sort ; ; Argument Buffer Support ; ARGBUF: DEFS 128 ; Storage for command argument and strings NARGC: DEFS 2 ; Argument count after expand NARGV: DEFS 2 ; Pointer to pointers to file names ; ; New Argument Buffer (filenames) starts here. ; NARGBUF EQU $ ; END