;**************************************************************
;*
;*                    B D O S   E N T R Y
;*
;**************************************************************
;
IOBYTE  EQU     3       ;i/o definition byte.
;TDRIVE  EQU     4       ;current drive name and user number.
;ENTRY   EQU     5       ;entry point for the cp/m bdos.
TFCB    EQU     5CH     ;default file control block.
TBUFF   EQU     80H     ;i/o buffer and command line storage.
TBASE   EQU     100H    ;transiant program storage area.
;CCP     EQU     100H
BDOS    EQU     fbase
;WBOOT   EQU 0
;
;   Set control character equates.
;
CNTRLC  EQU     3       ;control-c
CNTRLE  EQU     05H     ;control-e
BS      EQU     08H     ;backspace
TAB     EQU     09H     ;tab
LF      EQU     0AH     ;line feed
FF      EQU     0CH     ;form feed
CR      EQU     0DH     ;carriage return
CNTRLP  EQU     10H     ;control-p
CNTRLR  EQU     12H     ;control-r
CNTRLS  EQU     13H     ;control-s
CNTRLU  EQU     15H     ;control-u
CNTRLX  EQU     18H     ;control-x
CNTRLZ  EQU     1AH     ;control-z (end-of-file mark)
DEL     EQU     7FH     ;rubout
;NAMEPN  EQU	018DH	;input line pointer used for error message. Points to
			;start of name in error.
;**************************************************************
;*
;*        B I O S   J U M P   T A B L E
;*
;**************************************************************
;
;
FBASE::	JMP	FBASE1
WBOOTE::
	JMP	Init##	  ;"теплый" старт
	JMP	CONST##		;статус консоли
	JMP	CONIN##		;ввод символа с кносоли в (А)
	JMP	CONOUT##	;вывод символа на консоль из (А)
	JMP	LIST##		;вывод символа на принтер
	JMP	PUNCH##		;вывод символа на посл. интерфейс
	JMP	READER##	;прием символа с посл. интерфейса
	JMP	HOME##		;restore
	JMP	SELDSK##	;выбор диска
	JMP	SETTRK##	;установка #трека
	JMP	SETSEC##	;установка #сектора
	JMP	SETDMA##	;установка вектора DMA
	JMP	READ##		;операция чтения
	JMP	WRITE##		;операция записи

        JMP     LISTST##        ;готовность принтера
	JMP	SECTRAN##	;пересчет логического номера в физ.
StartBdos::
;
;   Bdos error table.
;
BDSCTR:	DW	ERROR1	;bad sector on read or write.
BDSLCT:	DW	ERROR2	;bad disk select.
RODISK:	DW	ERROR3	;disk is read only.
ROFILE:	DW	ERROR4	;file is read only.
;
;   Entry into bdos. (DE) or (E) are the parameters passed. The
; function number desired is in register (C).
;
FBASE1::
        mov     a,c
        cpi     61
        jz      setvect##
        cpi     50
        jz      CallBios##
        XCHG            ;save the (DE) parameters.
	SHLD	PARAMS
	XCHG
	MOV	A,E	;and save register (E) in particular.
	STA	EPARAM
	LXI	H,0
	SHLD	STATUS	;clear return status.
	DAD	SP
	SHLD	USRSTK;save users stack pointer.
;	LXI	SP,STAREA;and set our own.
	XRA	A	;clear auto select storage space.
	STA	AUTOFG
	STA	AUTO
	LXI	H,GOBACK;set return address.
	PUSH	H
	MOV	A,C	;get function number.
	CPI	NFNCTS	;valid function number?
	RNC
	MOV	C,E	;keep single register function here.
	LXI	H,FNCTNS;now look thru the function table.
	MOV	E,A
	MVI	D,0	;(DE)=function number.
	DAD	D
	DAD	D	;(HL)=(start of table)+2*(function number).
	MOV	E,M
	INX	H
	MOV	D,M	;now (DE)=address for this function.
	LHLD	PARAMS	;retrieve parameters.
	XCHG		;now (DE) has the original parameters.
	PCHL		;execute desired function.
;
;   BDOS function jump table.
;
NFNCTS  EQU     47      ;number of functions in followin table.
;
FNCTNS: DW      WBOOT,GETCON,OUTCON,GETRDR,PUNCH,LIST,DIRCIO,GETIOB     ;7
        DW      SETIOB,PRTSTR,RDBF,GETCST,GETVER,RSTDSK,SETDSK,OPNFIL   ;15
        DW      CLOSFL,GETFST,GETNXT,DELFIL,READSQ,WRTSEQ,FCREAT        ;22
        DW      RENFIL,GETLOG,GETCRN,PUTDMA,GTALOC,WRPRTD,GETROV,STATTR ;30
        DW      GTPARM,GTUSER,RDRNDM,WTRNDM,FLSIZE,SETRAN,LOGOFF,RTN    ;38
        DW      RTN,WTSPCL,RTN,RTN,RTN,RTN,RTN,RTN,rtn                  ;47
;       DW      RTN,RTN,  RTN,  RTN,RTN,RTN,RTN,RTN,RTN,RTN,RTN,RTN,RTN ;60
;       dw      SetVector
;
;   Bdos error message section.
;
ERROR1:	LXI	H,BADSEC	;bad sector message.
	CALL	PRTERR	;print it and get a 1 char responce.
	CPI	CNTRLC	;re-boot request (control-c)?
	JZ	0	;yes.
	RET		;no, return to retry i/o function.
;
ERROR2:	LXI	H,BADSEL	;bad drive selected.
	JMP	ERROR5
;
ERROR3:	LXI	H,DISKRO	;disk is read only.
	JMP	ERROR5
;
ERROR4:	LXI	H,FILERO	;file is read only.
;
ERROR5:	CALL	PRTERR
	JMP	0	;always reboot on these errors.
;
BDSERR:	DB	'BDOS ERR ON '
BDSDRV:	DB	' : $'
BADSEC:	DB	'BAD SECTOR$'
BADSEL:	DB	'SELECT$'
FILERO:	DB	'FILE '
DISKRO:	DB	'R/O$'
;
;   Print bdos error message.
;
PRTERR:	PUSH	H	;save second message pointer.
	CALL	OTCRLF	;send (cr)(lf).
	LDA	ACTIVE	;get active drive.
	ADI	'A'	;make ascii.
	STA	BDSDRV	;and put in message.
	LXI	B,BDSERR;and print it.
	CALL	PRTMSG
	POP	B	;print second message line now.
	CALL	PRTMSG
;
;    Check the drive selection and reset it to the previous
;    drive if it was changed for the preceeding command
;
;RSTDR:	LDA	AUTO     ;drive change indicated ?
;	ORA	A
;	RZ
;	DCR	A
;	LHLD	PARAMS
;	MVI	M,0
;	LDA	AUTOFG
;	ORA	A
;	RZ
;	LDA	OLDDRV  ;yes, re-select our old drive
;	STA	EPARAM
;       CALL    SETDSK
;	RET
;
;   Get an input character. We will check our 1 character
; buffer first. This may be set by the console status routine.
;
GTCHAR: LXI	H,CHRBUF;check character buffer.
	MOV	A,M	;anything present already?
	MVI	M,0	;...either case clear it.
	ORA	A
	RNZ		;yes, use it.
	JMP	CONIN	;nope, go get a character responce.
;
;   Input and echo a character.
;
GTECHO:	CALL	GTCHAR	;input a character.
	CALL	CHKCHR	;carriage control?
	RC		;no, a regular control char so don't echo.
	PUSH	PSW	;ok, save character now.
	MOV	C,A
	CALL	OUTCON	;and echo it.
	POP	PSW	;get character and return.
	RET
;
;   Check character in (A). Set the zero flag on a carriage
; control character and the carry flag on any other control
; character.
;
CHKCHR:	CPI	CR	;check for carriage return, line feed, backspace,
	RZ		;or a tab.
	CPI	LF
	RZ
	CPI	TAB
	RZ
	CPI	BS
	RZ
	CPI	' '	;other control char? Set carry flag.
	RET
;
;   Check the console during output. Halt on a control-s, then
; reboot on a control-c. If anything else is ready, clear the
; zero flag and return (the calling routine may want to do
; something).
;
CKCNSL:	LDA	CHRBUF	;check buffer.
	ORA	A	;if anything, just return without checking.
	JNZ	CKCON2
	CALL	CONST	;nothing in buffer. Check console.
	ANI	01H	;look at bit 0.
	RZ		;return if nothing.
	CALL	CONIN	;ok, get it.
	CPI	CNTRLS	;if not control-s, return with zero cleared.
	JNZ	CKCON1
	CALL	CONIN	;halt processing until another char
	CPI	CNTRLC	;is typed. Control-c?
	CZ	F35	;yes, reboot now.
	XRA	A	;no, just pretend nothing was ever ready.
	RET

F35:    PUSH 	PSW
	PUSH 	B
	PUSH 	D
	PUSH	H
	LXI 	D,F35BLC
 	CALL	CallBios##
	POP	H
	POP	D
	POP	B
	POP	PSW
	RET
F35BLC: DB	35
	DB	0

CKCON1:	STA	CHRBUF	;save character in buffer for later processing.
CKCON2:	MVI	A,1	;set (A) to non zero to mean something is ready.
	RET
;
;   Output (C) to the screen. If the printer flip-flop flag
; is set, we will send character to printer also. The console
; will be checked in the process.
;
OUTCHR:	LDA	OUTFLG	;check output flag.
	ORA	A	;anything and we won't generate output.
	JNZ	OTCHR1
	PUSH	B
	CALL	CKCNSL;check console (we don't care whats there).
	POP	B
	PUSH	B
	CALL	CONOUT	;output (C) to the screen.
	POP	B
	PUSH	B
	LDA	PRTFLG	;check printer flip-flop flag.
	ORA	A
	CNZ	LIST	;print it also if non-zero.
	POP	B
OTCHR1:	MOV	A,C	;update cursors position.
	LXI	H,CURPOS
	CPI	DEL	;rubouts don't do anything here.
	RZ
	INR	M	;bump line pointer.
	CPI	' '	;and return if a normal character.
	RNC
	DCR	M	;restore and check for the start of the line.
	MOV	A,M
	ORA	A
	RZ		;ingnore control characters at the start of the line.
	MOV	A,C
	CPI	BS	;is it a backspace?
	JNZ	OTCHR2
	DCR	M	;yes, backup pointer.
	RET
OTCHR2:	CPI	LF	;is it a line feed?
	RNZ		;ignore anything else.
	MVI	M,0	;reset pointer to start of line.
	RET
;
;   Output (A) to the screen. If it is a control character
; (other than carriage control), use ^x format.
;
SHOWIT:	MOV	A,C
	CALL	CHKCHR	;check character.
	JNC	OUTCON	;not a control, use normal output.
	PUSH	PSW
	MVI	C,'^'	;for a control character, preceed it with '^'.
	CALL	OUTCHR
	POP	PSW
	ORI	'@'	;and then use the letter equivelant.
	MOV	C,A
;
;   Function to output (C) to the console device and expand tabs
; if necessary.
;
OUTCON:	MOV	A,C
	CPI	TAB	;is it a tab?
	JNZ	OUTCHR	;use regular output.
OTCON1:	MVI	C,' '	;yes it is, use spaces instead.
	CALL	OUTCHR
	LDA	CURPOS	;go until the cursor is at a multiple of 8
	ANI	07H	;position.
	JNZ	OTCON1
	RET
;
;   Echo a backspace character. Erase the prevoius character
; on the screen.
;
BACKUP:	CALL	BCKUP1	;backup the screen 1 place.
	MVI	C,' '	;then blank that character.
	CALL	CONOUT
BCKUP1:	MVI	C,BS	;then back space once more.
	JMP	CONOUT
;
;   Signal a deleted line. Print a '#' at the end and start
; over.
;
NEWLNE:	MVI	C,'#'
	CALL	OUTCHR	;print this.
	CALL	OTCRLF	;start new line.
NEWLN1:	LDA	CURPOS	;move the cursor to the starting position.
	LXI	H,STRTNG
	CMP	M
	RNC		;there yet?
	MVI	C,' '
	CALL	OUTCHR	;nope, keep going.
	JMP	NEWLN1
;
;   Output a (cr) (lf) to the console device (screen).
;
OTCRLF:	MVI	C,CR
	CALL	OUTCHR
	MVI	C,LF
	JMP	OUTCHR
;
;   Print message pointed to by (BC). It will end with a '$'.
;
PRTMSG:	LDAX	B	;check for terminating character.
	CPI	'$'
	RZ
	INX	B
	PUSH	B	;otherwise, bump pointer and print it.
	MOV	C,A
	CALL	OUTCON
	POP	B
	JMP	PRTMSG
;
;   Function to execute a buffered read.
;
RDBF:	LDA	CURPOS	;use present location as starting one.
	STA	STRTNG
	LHLD	PARAMS	;get the maximum buffer space.
	MOV	C,M
	INX	H	;point to first available space.
	PUSH	H	;and save.
	MVI	B,0	;keep a character count.
RDBUF1:	PUSH	B
	PUSH	H
RDBUF2:	CALL	GTCHAR	;get the next input character.
	ANI	7FH	;strip bit 7.
	POP	H	;reset registers.
	POP	B
	CPI	CR	;en of the line?
	JZ	RDBF17
	CPI	LF
	JZ	RDBF17
	CPI	BS	;how about a backspace?
	JNZ	RDBUF3
	MOV	A,B	;yes, but ignore at the beginning of the line.
	ORA	A
	JZ	RDBUF1
	DCR	B	;ok, update counter.
	LDA	CURPOS	;if we backspace to the start of the line,
	STA	OUTFLG	;treat as a cancel (control-x).
	JMP	RDBF10
RDBUF3:	CPI	DEL	;user typed a rubout?
	JNZ	RDBUF4
	MOV	A,B	;ignore at the start of the line.
	ORA	A
	JZ	RDBUF1
	MOV	A,M	;ok, echo the prevoius character.
	DCR	B	;and reset pointers (counters).
	DCX	H
	JMP	RDBF15
RDBUF4:	CPI	CNTRLE	;physical end of line?
	JNZ	RDBUF5
	PUSH	B	;yes, do it.
	PUSH	H
	CALL	OTCRLF
	XRA	A	;and update starting position.
	STA	STRTNG
	JMP	RDBUF2
RDBUF5:	CPI	CNTRLP	;control-p?
	JNZ	RDBUF6
	PUSH	H	;yes, flip the print flag filp-flop byte.
	LXI	H,PRTFLG
	MVI	A,1	;PRTFLG=1-PRTFLG
	SUB	M
	MOV	M,A
	POP	H
	JMP	RDBUF1
RDBUF6:	CPI	CNTRLX	;control-x (cancel)?
	JNZ	RDBUF8
	POP	H
RDBUF7:	LDA	STRTNG;yes, backup the cursor to here.
	LXI	H,CURPOS
	CMP	M
	JNC	RDBF	;done yet?
	DCR	M	;no, decrement pointer and output back up one space.
	CALL	BACKUP
	JMP	RDBUF7
RDBUF8:	CPI	CNTRLU	;cntrol-u (cancel line)?
	JNZ	RDBUF9
	CALL	NEWLNE	;start a new line.
	POP	H
	JMP	RDBF
RDBUF9:	CPI	CNTRLR	;control-r?
	JNZ	RDBF14
RDBF10:	PUSH	B	;yes, start a new line and retype the old one.
	CALL	NEWLNE
	POP	B
	POP	H
	PUSH	H
	PUSH	B
RDBF11:	MOV	A,B	;done whole line yet?
	ORA	A
	JZ	RDBF12
	INX	H	;nope, get next character.
	MOV	C,M
	DCR	B	;count it.
	PUSH	B
	PUSH	H
	CALL	SHOWIT	;and display it.
	POP	H
	POP	B
	JMP	RDBF11
RDBF12:	PUSH	H	;done with line. If we were displaying
	LDA	OUTFLG	;then update cursor position.
	ORA	A
	JZ	RDBUF2
	LXI	H,CURPOS;because this line is shorter, we must
	SUB	M	;back up the cursor (not the screen however)
	STA	OUTFLG	;some number of positions.
RDBF13:	CALL	BACKUP	;note that as long as (OUTFLAG) is non
	LXI	H,OUTFLG;zero, the screen will not be changed.
	DCR	M
	JNZ	RDBF13
	JMP	RDBUF2	;now just get the next character.
;
;   Just a normal character, put this in our buffer and echo.
;
RDBF14:	INX	H
	MOV	M,A	;store character.
	INR	B	;and count it.
RDBF15:	PUSH	B
	PUSH	H
	MOV	C,A	;echo it now.
	CALL	SHOWIT
	POP	H
	POP	B
	MOV	A,M	;was it an abort request?
	CPI	CNTRLC	;control-c abort?
	MOV	A,B
	JNZ	RDBF16
	CPI	1	;only if at start of line.
	CZ	F35
RDBF16:	CMP	C	;nope, have we filled the buffer?
	JC	RDBUF1
RDBF17:	POP	H	;yes end the line and return.
	MOV	M,B
	MVI	C,CR
	JMP	OUTCHR	;output (cr) and return.
;
;   Function to get a character from the console device.
;
GETCON:	CALL	GTECHO	;get and echo.
	JMP	SETSTA	;save status and return.
;
;   Function to get a character from the tape reader device.
;
GETRDR:	CALL	READER	;get a character from reader, set status and return.
	JMP	SETSTA
;
;  Function to perform direct console i/o. If (C) contains (FF)
; then this is an input request. If (C) contains (FE) then
; this is a status request. Otherwise we are to output (C).
;
DIRCIO:	MOV	A,C	;test for (FF).
	INR	A
	JZ	DIRC1
	INR	A	;test for (FE).
	JZ	CONST
	JMP	CONOUT	;just output (C).
DIRC1:	CALL	CONST	;this is an input request.
	ORA	A
	JZ	GOBCK1	;not ready? Just return (directly).
	CALL	CONIN	;yes, get character.
	JMP	SETSTA	;set status and return.
;
;   Function to return the i/o byte.
;
GETIOB:	LDA	IOBYTE
	JMP	SETSTA
;
;   Function to set the i/o byte.
;
SETIOB:	LXI	H,IOBYTE
	MOV	M,C
	RET
;
;   Function to print the character string pointed to by (DE)
; on the console device. The string ends with a '$'.
;
PRTSTR:	XCHG
	MOV	C,L
	MOV	B,H	;now (BC) points to it.
	JMP	PRTMSG
;
;   Function to interigate the console device.
;
GETCST:	CALL	CKCNSL
;
;   Get here to set the status and return to the cleanup
; section. Then back to the user.
;
SETSTA:	STA	STATUS
RTN:	RET
;
;   Set the status to 1 (read or write error code).
;
IOERR1:	MVI	A,1
	JMP	SETSTA
;
OUTFLG:	DB	0	;output flag (non zero means no output).
STRTNG:	DB	2	;starting position for cursor.
CURPOS:	DB	0	;cursor position (0=start of line).
PRTFLG:	DB	0	;printer flag (control-p toggle). List if non zero.
CHRBUF:	DB	0	;single input character buffer.
;
;   Stack area for BDOS calls.
;
USRSTK:: DW      0       ;save users stack pointer here.
;
;	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;STAREA	EQU	$	;end of stack area.
;	DS	20
;USAREA:
;
USERNO:	DB	0	;current user number.
ACTIVE:	DB	0	;currently active drive.
PARAMS:	DW	0	;save (DE) parameters here on entry.
STATUS:: DW      0       ;status returned from bdos function.
;
;   Select error occured, jump to error routine.
;
SLTERR:	LXI	H,BDSLCT
;
;   Jump to (HL) indirectly.
;
JUMPHL:	MOV	E,M
	INX	H
	MOV	D,M	;now (DE) contain the desired address.
	XCHG
	PCHL
;
;   Block move. (DE) to (HL), (C) bytes total.
;
DE2HL:	INR	C	;is count down to zero?
DE2HL1:	DCR	C
	RZ		;yes, we are done.
	LDAX	D	;no, move one more byte.
	MOV	M,A
	INX	D
	INX	H
	JMP	DE2HL1	;and repeat.
;
;   Select the desired drive.
;
SELECT:
  LDA ACTIVE  ;get active disk.
	MOV	C,A
  CALL  SELDSK  ;select it.
	MOV	A,H	;valid drive?
	ORA	L	;valid drive?
	RZ		;return if not.
;
;   Here, the BIOS returned the address of the parameter block
; in (HL). We will extract the necessary pointers and save them.
;
	MOV	E,M	;yes, get address of translation table into (DE).
	INX	H
	MOV	D,M
	INX	H
	SHLD	SCRCH1	;save pointers to scratch areas.
	INX	H
	INX	H
	SHLD	SCRCH2	;ditto.
	INX	H
	INX	H
	SHLD	SCRCH3	;ditto.
	INX	H
	INX	H
	XCHG		;now save the translation table address.
	SHLD	XLATE
	LXI	H,DIRBUF	;put the next 8 bytes here.
	MVI	C,8	;they consist of the directory buffer
	CALL	DE2HL	;pointer, parameter block pointer,
	LHLD	DISKPB	;check and allocation vectors.
	XCHG
	LXI	H,SECTRS	;move parameter block into our ram.
	MVI	C,15	;it is 15 bytes long.
	CALL	DE2HL
	LHLD	DSKSZE	;check disk size.
	MOV	A,H	;more than 256 blocks on this?
	LXI	H,BIGDSK
	MVI	M,0FFH	;set to samll.
	ORA	A
	JZ	SELCT1
	MVI	M,0	;wrong, set to large.
SELCT1:	MVI	A,0FFH	;clear the zero flag.
	ORA	A
	RET
;
;   Routine to home the disk track head and clear pointers.
;
HOMDRV:	CALL	HOME	;home the head.
	XRA	A
	LHLD	SCRCH2;set our track pointer also.
	MOV	M,A
	INX	H
	MOV	M,A
	LHLD	SCRCH3;and our sector pointer.
	MOV	M,A
	INX	H
	MOV	M,A
	RET
;
;   Do the actual disk read and check the error return status.
;
DOREAD:	CALL	READ
	JMP	IORET
;
;   Do the actual disk write and handle any bios error.
;
DOWRTE:	CALL	WRITE
IORET:	ORA	A
	RZ		;return unless an error occured.
	LXI	H,BDSCTR;bad read/write on this sector.
	JMP	JUMPHL
;
;   Routine to select the track and sector that the desired
; block number falls in.
;
TRKSEC:	LHLD	FLEPOS	;get position of last accessed file
	MVI	C,2	;in directory and compute sector #.
	CALL	SHIFTR	;sector #=file-position/4.
	SHLD	BLNMBR	;save this as the block number of interest.
	SHLD	CKSTBL  ;what's it doing here too?
;
;   if the sector number has already been set (BLKNMBR), enter
; at this point.
;
TRKSC1:	LXI	H,BLNMBR
	MOV	C,M	;move sector number into (BC).
	INX	H
	MOV	B,M
	LHLD	SCRCH3;get current sector number and
	MOV	E,M	;move this into (DE).
	INX	H
	MOV	D,M
	LHLD	SCRCH2;get current track number.
	MOV	A,M	;and this into (HL).
	INX	H
	MOV	H,M
	MOV	L,A
TRKSC2:	MOV	A,C	;is desired sector before current one?
	SUB	E
	MOV	A,B
	SBB	D
	JNC	TRKSC3
	PUSH	H	;yes, decrement sectors by one track.
	LHLD	SECTRS	;get sectors per track.
	MOV	A,E
	SUB	L
	MOV	E,A
	MOV	A,D
	SBB	H
	MOV	D,A	;now we have backed up one full track.
	POP	H
	DCX	H	;adjust track counter.
	JMP	TRKSC2
TRKSC3:	PUSH	H	;desired sector is after current one.
	LHLD	SECTRS	;get sectors per track.
	DAD	D	;bump sector pointer to next track.
	JC	TRKSC4
	MOV	A,C	;is desired sector now before current one?
	SUB	L
	MOV	A,B
	SBB	H
	JC	TRKSC4
	XCHG		;not yes, increment track counter
	POP	H	;and continue until it is.
	INX	H
	JMP	TRKSC3
;
;   here we have determined the track number that contains the
; desired sector.
;
TRKSC4:	POP	H	;get track number (HL).
	PUSH	B
	PUSH	D
	PUSH	H
	XCHG
	LHLD	OFFSET	;adjust for first track offset.
	DAD	D
	MOV	B,H
	MOV	C,L
	CALL	SETTRK	;select this track.
	POP	D	;reset current track pointer.
	LHLD	SCRCH2
	MOV	M,E
	INX	H
	MOV	M,D
	POP	D
	LHLD	SCRCH3  ;reset the first sector on this track.
	MOV	M,E
	INX	H
	MOV	M,D
	POP	B
	MOV	A,C	;now subtract the desired one.
	SUB	E	;to make it relative (1-# sectors/track).
	MOV	C,A
	MOV	A,B
	SBB	D
	MOV	B,A
	LHLD	XLATE	;translate this sector according to this table.
	XCHG
	CALL	SECTRAN	;let the bios translate it.
	MOV	C,L
	MOV	B,H
	JMP	SETSEC	;and select it.
;
;   Compute block number from record number (SAVNRC) and
; extent number (SAVEXT).
;
GETBLK:	LXI	H,BLSHFT;get logical to physical conversion.
	MOV	C,M	;note that this is base 2 log of ratio.
	LDA	SAVNRC	;get record number.
GETBL1:	ORA	A	;compute (A)=(A)/2^BLSHFT.
	RAR
	DCR	C
	JNZ	GETBL1
	MOV	B,A	;save result in (B).
	MVI	A,8
	SUB	M
	MOV	C,A	;compute (C)=8-BLSHFT.
	LDA	SAVEXT
GETBL2:	DCR	C	;compute (A)=SAVEXT*2^(8-BLSHFT).
	JZ	GETBL3
	ORA	A
	RAL
	JMP	GETBL2
GETBL3:	ADD	B
	RET
;
;   Routine to extract the (BC) block byte from the fcb pointed
; to by (PARAMS). If this is a big-disk, then these are 16 bit
; block numbers, else they are 8 bit numbers.
; Number is returned in (HL).
;
EXTBLK:	LHLD	PARAMS	;get fcb address.
	LXI	D,16	;block numbers start 16 bytes into fcb.
	DAD	D
	DAD	B
	LDA	BIGDSK	;are we using a big-disk?
	ORA	A
	JZ	EXTBL1
	MOV	L,M	;no, extract an 8 bit number from the fcb.
	MVI	H,0
	RET
EXTBL1:	DAD	B	;yes, extract a 16 bit number.
	MOV	E,M
	INX	H
	MOV	D,M
	XCHG		;return in (HL).
	RET
;
;   Compute block number.
;
COMBLK:	CALL	GETBLK
	MOV	C,A
	MVI	B,0
	CALL	EXTBLK
	SHLD	BLNMBR
	RET
;
;   Check for a zero block number (unused).
;
CHKBLK:	LHLD	BLNMBR
	MOV	A,L	;is it zero?
	ORA	H
	RET
;
;   Adjust physical block (BLNMBR) and convert to logical
; sector (LOGSEC). This is the starting sector of this block.
; The actual sector of interest is then added to this and the
; resulting sector number is stored back in (BLNMBR). This
; will still have to be adjusted for the track number.
;
LOGICL:	LDA	BLSHFT	;get log2(physical/logical sectors).
	LHLD	BLNMBR	;get physical sector desired.
LOGIC1:	DAD	H	;compute logical sector number.
	DCR	A	;note logical sectors are 128 bytes long.
	JNZ	LOGIC1
	SHLD	LOGSEC	;save logical sector.
	LDA	BLKMSK	;get block mask.
	MOV	C,A
	LDA	SAVNRC	;get next sector to access.
	ANA	C	;extract the relative position within physical block.
	ORA	L	;and add it too logical sector.
	MOV	L,A
	SHLD	BLNMBR	;and store.
	RET
;
;   Set (HL) to point to extent byte in fcb.
;
SETEXT:	LHLD	PARAMS
	LXI	D,12	;it is the twelth byte.
	DAD	D
	RET
;
;   Set (HL) to point to record count byte in fcb and (DE) to
; next record number byte.
;
STHLDE:	LHLD	PARAMS
	LXI	D,15	;record count byte (#15).
	DAD	D
	XCHG
	LXI	H,17	;next record number (#32).
	DAD	D
	RET
;
;   Save current file data from fcb.
;
STRDAT:	CALL	STHLDE
	MOV	A,M	;get and store record count byte.
	STA	SAVNRC
	XCHG
	MOV	A,M	;get and store next record number byte.
	STA	SAVNXT
	CALL	SETEXT	;point to extent byte.
	LDA	EXTMSK	;get extent mask.
	ANA	M
	STA	SAVEXT	;and save extent here.
	RET
;
;   Set the next record to access. If (MODE) is set to 2, then
; the last record byte (SAVNRC) has the correct number to access.
; For sequential access, (MODE) will be equal to 1.
;
SETNRC:	CALL	STHLDE
	LDA	MODE	;get sequential flag (=1).
	CPI	2	;a 2 indicates that no adder is needed.
	JNZ	STNRC1
	XRA	A	;clear adder (random access?).
STNRC1:	MOV	C,A
	LDA	SAVNRC	;get last record number.
	ADD	C	;increment record count.
	MOV	M,A	;and set fcb's next record byte.
	XCHG
	LDA	SAVNXT	;get next record byte from storage.
	MOV	M,A	;and put this into fcb as number of records used.
	RET
;
;   Shift (HL) right (C) bits.
;
SHIFTR:	INR	C
SHFTR1:	DCR	C
	RZ
	MOV	A,H
	ORA	A
	RAR
	MOV	H,A
	MOV	A,L
	RAR
	MOV	L,A
	JMP	SHFTR1
;
;   Compute the check-sum for the directory buffer. Return
; integer sum in (A).
;
CHKSUM: MVI	C,128	;length of buffer.
	LHLD	DIRBUF	;get its location.
	XRA	A	;clear summation byte.
CHKSM1:	ADD	M	;and compute sum ignoring carries.
	INX	H
	DCR	C
	JNZ	CHKSM1
	RET
;
;   Shift (HL) left (C) bits.
;
SHIFTL:	INR	C
SHFTL1:	DCR	C
	RZ
	DAD	H	;shift left 1 bit.
	JMP	SHFTL1
;
;   Routine to set a bit in a 16 bit value contained in (BC).
; The bit set depends on the current drive selection.
;
SETBIT:	PUSH	B	;save 16 bit word.
	LDA	ACTIVE	;get active drive.
	MOV	C,A
	LXI	H,1
	CALL	SHIFTL	;shift bit 0 into place.
	POP	B	;now 'or' this with the original word.
	MOV	A,C
	ORA	L
	MOV	L,A	;low byte done, do high byte.
	MOV	A,B
	ORA	H
	MOV	H,A
	RET
;
;   Extract the write protect status bit for the current drive.
; The result is returned in (A), bit 0.
;
GTWPRT:	LHLD	WRTPRT	;get status bytes.
	LDA	ACTIVE	;which drive is current?
	MOV	C,A
	CALL	SHIFTR	;shift status such that bit 0 is the
	MOV	A,L	;one of interest for this drive.
	ANI	01H	;and isolate it.
	RET
;
;   Function to write protect the current disk.
;
WRPRTD:	LXI	H,WRTPRT ;point to status word.
	MOV	C,M	;set (BC) equal to the status.
	INX	H
	MOV	B,M
	CALL	SETBIT	;and set this bit according to current drive.
	SHLD	WRTPRT	;then save.
	LHLD	DIRSZE	;now save directory size limit.
	INX	H	;remember the last one.
	XCHG
	LHLD	SCRCH1;and store it here.
	MOV	M,E	;put low byte.
	INX	H
	MOV	M,D	;then high byte.
	RET
;
;   Check for a read only file.
;
CKROFL:	CALL	FCB2HL	;set (HL) to file entry in directory buffer.
CKROF1:	LXI	D,9	;look at bit 7 of the ninth byte.
	DAD	D
	MOV	A,M
	RAL
	RNC		;return if ok.
	LXI	H,ROFILE;else, print error message and terminate.
	JMP	JUMPHL
;
;   Check the write protect status of the active disk.
;
CKWPRT:	CALL	GTWPRT
	RZ		;return if ok.
	LXI	H,RODISK;else print message and terminate.
	JMP	JUMPHL
;
;   Routine to set (HL) pointing to the proper entry in the
; directory buffer.
;
FCB2HL:	LHLD	DIRBUF	;get address of buffer.
	LDA	FCBPOS	;relative position of file.
;
;   Routine to add (A) to (HL).
;
ADA2HL:	ADD	L
	MOV	L,A
	RNC
	INR	H	;take care of any carry.
	RET
;
;   Routine to get the 's2' byte from the fcb supplied in
; the initial parameter specification.
;
GETS2:	LHLD	PARAMS	;get address of fcb.
	LXI	D,14	;relative position of 's2'.
	DAD	D
	MOV	A,M	;extract this byte.
	RET
;
;   Clear the 's2' byte in the fcb.
;
CLRS2:	CALL	GETS2	;this sets (HL) pointing to it.
	MVI	M,0	;now clear it.
	RET
;
;   Set bit 7 in the 's2' byte of the fcb.
;
STS2B7:	CALL	GETS2	;get the byte.
	ORI	80H	;and set bit 7.
	MOV	M,A	;then store.
	RET
;
;   Compare (FLEPOS) with (SCRCH1) and set flags based on
; the difference. This checks to see if there are more file
; names in the directory. We are at (FLEPOS) and there are
; (SCRCH1) of them to check.
;
MOREFL:	LHLD	FLEPOS	;we are here.
	XCHG
	LHLD	SCRCH1;and don't go past here.
	MOV	A,E	;compute difference but don't keep.
	SUB	M
	INX	H
	MOV	A,D
	SBB	M	;set carry if no more names.
	RET
;
;   Call this routine to prevent (SCRCH1) from being greater
; than (FLEPOS).
;
CKNMBR:	CALL	MOREFL	;SCRCH1 too big?
	RC
	INX	D	;yes, reset it to (FLEPOS).
	MOV	M,D
	DCX	H
	MOV	M,E
	RET
;
;   Compute (HL)=(DE)-(HL)
;
SUBHL:	MOV	A,E	;compute difference.
	SUB	L
	MOV	L,A	;store low byte.
	MOV	A,D
	SBB	H
	MOV	H,A	;and then high byte.
	RET
;
;   Set the directory checksum byte.
;
SETDIR:	MVI	C,0FFH
;
;   Routine to set or compare the directory checksum byte. If
; (C)=0ffh, then this will set the checksum byte. Else the byte
; will be checked. If the check fails (the disk has been changed),
; then this disk will be write protected.
;
CHKDIR:	LHLD	CKSTBL
	XCHG
	LHLD	ALLOC1
	CALL	SUBHL
	RNC		;ok if (CKSTBL) > (ALLOC1), so return.
	PUSH	B
	CALL	CHKSUM;else compute checksum.
	LHLD	CHKVCT	;get address of checksum table.
	XCHG
	LHLD	CKSTBL
	DAD	D	;set (HL) to point to byte for this drive.
	POP	B
	INR	C	;set or check ?
	JZ	CKDIR1
	CMP	M	;check them.
	RZ		;return if they are the same.
	CALL	MOREFL	;not the same, do we care?
	RNC
	CALL	WRPRTD	;yes, mark this as write protected.
	RET
CKDIR1:	MOV	M,A	;just set the byte.
	RET
;
;   Do a write to the directory of the current disk.
;
DIRWRT:	CALL	SETDIR	;set checksum byte.
	CALL	DIRDMA	;set directory dma address.
	MVI	C,1	;tell the bios to actually write.
	CALL	DOWRTE	;then do the write.
	JMP	DEFDMA
;
;   Read from the directory.
;
DRREAD:	CALL	DIRDMA	;set the directory dma address.
	CALL	DOREAD	;and read it.
;
;   Routine to set the dma address to the users choice.
;
DEFDMA:	LXI	H,USRDMA;reset the default dma address and return.
	JMP	DRDMA1
;
;   Routine to set the dma address for directory work.
;
DIRDMA:	LXI	H,DIRBUF
;
;   Set the dma address. On entry, (HL) points to
; word containing the desired dma address.
;
DRDMA1:	MOV	C,M
	INX	H
	MOV	B,M	;setup (BC) and go to the bios to set it.
	JMP	SETDMA
;
;   Move the directory buffer into user's dma space.
;
MOVDIR:	LHLD	DIRBUF	;buffer is located here, and
	XCHG
	LHLD	USRDMA; put it here.
	MVI	C,128	;this is its length.
	JMP	DE2HL	;move it now and return.
;
;   Check (FLEPOS) and set the zero flag if it equals 0ffffh.
;
CKFLPS:	LXI	H,FLEPOS
	MOV	A,M
	INX	H
	CMP	M	;are both bytes the same?
	RNZ
	INR	A	;yes, but are they each 0ffh?
	RET
;
;   Set location (FLEPOS) to 0ffffh.
;
STFLPS:	LXI	H,0FFFFH
	SHLD	FLEPOS
	RET
;
;   Move on to the next file position within the current
; directory buffer. If no more exist, set pointer to 0ffffh
; and the calling routine will check for this. Enter with (C)
; equal to 0ffh to cause the checksum byte to be set, else we
; will check this disk and set write protect if checksums are
; not the same (applies only if another directory sector must
; be read).
;
NXENTR:	LHLD	DIRSZE	;get directory entry size limit.
	XCHG
	LHLD	FLEPOS	;get current count.
	INX	H	;go on to the next one.
	SHLD	FLEPOS
	CALL	SUBHL	;(HL)=(DIRSZE)-(FLEPOS)
	JNC	NXENT1	;is there more room left?
	JMP	STFLPS  ;no. Set this flag and return.
NXENT1:	LDA	FLEPOS	;get file position within directory.
	ANI	03H	;only look within this sector (only 4 entries fit).
	MVI	B,5	;convert to relative position (32 bytes each).
NXENT2:	ADD	A	;note that this is not efficient code.
	DCR	B	;5 'ADD A's would be better.
	JNZ	NXENT2
	STA	FCBPOS	;save it as position of fcb.
	ORA	A
	RNZ		;return if we are within buffer.
	PUSH	B
	CALL	TRKSEC	;we need the next directory sector.
	CALL	DRREAD
	POP	B
	JMP	CHKDIR
;
;   Routine to to get a bit from the disk space allocation
; map. It is returned in (A), bit position 0. On entry to here,
; set (BC) to the block number on the disk to check.
; On return, (D) will contain the original bit position for
; this block number and (HL) will point to the address for it.
;
CKBMAP:	MOV	A,C	;determine bit number of interest.
	ANI	07H	;compute (D)=(E)=(C and 7)+1.
	INR	A
	MOV	E,A	;save particular bit number.
	MOV	D,A
;
;   compute (BC)=(BC)/8.
;
	MOV	A,C
	RRC		;now shift right 3 bits.
	RRC
	RRC
	ANI	1FH	;and clear bits 7,6,5.
	MOV	C,A
	MOV	A,B
	ADD	A	;now shift (B) into bits 7,6,5.
	ADD	A
	ADD	A
	ADD	A
	ADD	A
	ORA	C	;and add in (C).
	MOV	C,A	;ok, (C) ha been completed.
	MOV	A,B	;is there a better way of doing this?
	RRC
	RRC
	RRC
	ANI	1FH
	MOV	B,A	;and now (B) is completed.
;
;   use this as an offset into the disk space allocation
; table.
;
	LHLD	ALCVCT
	DAD	B
	MOV	A,M	;now get correct byte.
CKBMP1:	RLC		;get correct bit into position 0.
	DCR	E
	JNZ	CKBMP1
	RET
;
;   Set or clear the bit map such that block number (BC) will be marked
; as used. On entry, if (E)=0 then this bit will be cleared, if it equals
; 1 then it will be set (don't use anyother values).
;
STBMAP:	PUSH	D
	CALL	CKBMAP;get the byte of interest.
	ANI	0FEH	;clear the affected bit.
	POP	B
	ORA	C	;and now set it acording to (C).
;
;  entry to restore the original bit position and then store
; in table. (A) contains the value, (D) contains the bit
; position (1-8), and (HL) points to the address within the
; space allocation table for this byte.
;
STBMP1:	RRC		;restore original bit position.
	DCR	D
	JNZ	STBMP1
	MOV	M,A	;and stor byte in table.
	RET
;
;   Set/clear space used bits in allocation map for this file.
; On entry, (C)=1 to set the map and (C)=0 to clear it.
;
STFILE:	CALL	FCB2HL	;get address of fcb
	LXI	D,16
	DAD	D	;get to block number bytes.
	PUSH	B
	MVI	C,17	;check all 17 bytes (max) of table.
SETFL1:	POP	D
	DCR	C	;done all bytes yet?
	RZ
	PUSH	D
	LDA	BIGDSK	;check disk size for 16 bit block numbers.
	ORA	A
	JZ	SETFL2
	PUSH	B	;only 8 bit numbers. set (BC) to this one.
	PUSH	H
	MOV	C,M	;get low byte from table, always
	MVI	B,0	;set high byte to zero.
	JMP	SETFL3
SETFL2:	DCR	C	;for 16 bit block numbers, adjust counter.
	PUSH	B
	MOV	C,M	;now get both the low and high bytes.
	INX	H
	MOV	B,M
	PUSH	H
SETFL3:	MOV	A,C	;block used?
	ORA	B
	JZ	SETFL4
	LHLD	DSKSZE	;is this block number within the
	MOV	A,L	;space on the disk?
	SUB	C
	MOV	A,H
	SBB	B
	CNC	STBMAP;yes, set the proper bit.
SETFL4:	POP	H	;point to next block number in fcb.
	INX	H
	POP	B
	JMP	SETFL1
;
;   Construct the space used allocation bit map for the active
; drive. If a file name starts with '$' and it is under the
; current user number, then (STATUS) is set to minus 1. Otherwise
; it is not set at all.
;
BITMAP:	LHLD	DSKSZE	;compute size of allocation table.
	MVI	C,3
	CALL	SHIFTR	;(HL)=(HL)/8.
	INX	H	;at lease 1 byte.
	MOV	B,H
	MOV	C,L	;set (BC) to the allocation table length.
;
;   Initialize the bitmap for this drive. Right now, the first
; two bytes are specified by the disk parameter block. However
; a patch could be entered here if it were necessary to setup
; this table in a special mannor. For example, the bios could
; determine locations of 'bad blocks' and set them as already
; 'used' in the map.
;
	LHLD	ALCVCT;now zero out the table now.
BTMAP1:	MVI	M,0
	INX	H
	DCX	B
	MOV	A,B
	ORA	C
	JNZ	BTMAP1
	LHLD	ALLOC0	;get initial space used by directory.
	XCHG
	LHLD	ALCVCT;and put this into map.
	MOV	M,E
	INX	H
	MOV	M,D
;
;   End of initialization portion.
;
	CALL	HOMDRV	;now home the drive.
	LHLD	SCRCH1
	MVI	M,3	;force next directory request to read
	INX	H	;in a sector.
	MVI	M,0
	CALL	STFLPS;clear initial file position also.
BTMAP2:	MVI	C,0FFH	;read next file name in directory
	CALL	NXENTR	;and set checksum byte.
	CALL	CKFLPS;is there another file?
	RZ
	CALL	FCB2HL	;yes, get its address.
	MVI	A,0E5H
	CMP	M	;empty file entry?
	JZ	BTMAP2
	LDA	USERNO	;no, correct user number?
	CMP	M
	JNZ	BTMAP3
	INX	H
	MOV	A,M	;yes, does name start with a '$'?
	SUI	'$'
	JNZ	BTMAP3
	DCR	A	;yes, set atatus to minus one.
	STA	STATUS
BTMAP3:	MVI	C,1	;now set this file's space as used in bit map.
	CALL	STFILE
	CALL	CKNMBR	;keep (SCRCH1) in bounds.
	JMP	BTMAP2
;
;   Set the status (STATUS) and return.
;
STSTTS:	LDA	FNDSTA
	JMP	SETSTA
;
;   Check extents in (A) and (C). Set the zero flag if they
; are the same. The number of 16k chunks of disk space that
; the directory extent covers is expressad is (EXTMASK+1).
; No registers are modified.
;
SAMEXT:	PUSH	B
	PUSH	PSW
	LDA	EXTMSK	;get extent mask and use it to
	CMA		;to compare both extent numbers.
	MOV	B,A	;save resulting mask here.
	MOV	A,C	;mask first extent and save in (C).
	ANA	B
	MOV	C,A
	POP	PSW	;now mask second extent and compare
	ANA	B	;with the first one.
	SUB	C
	ANI	1FH	;(* only check buts 0-4 *)
	POP	B	;the zero flag is set if they are the same.
	RET		;restore (BC) and return.
;
;   Search for the first occurence of a file name. On entry,
; register (C) should contain the number of bytes of the fcb
; that must match.
;
FNDFST:	MVI	A,0FFH
	STA	FNDSTA
	LXI	H,COUNTR;save character count.
	MOV	M,C
	LHLD	PARAMS	;get filename to match.
	SHLD	SAVFCB	;and save.
	CALL	STFLPS;clear initial file position (set to 0ffffh).
	CALL	HOMDRV	;home the drive.
;
;   Entry to locate the next occurence of a filename within the
; directory. The disk is not expected to have been changed. If
; it was, then it will be write protected.
;
FNDNXT:	MVI	C,0	;write protect the disk if changed.
	CALL	NXENTR	;get next filename entry in directory.
	CALL	CKFLPS  ;is file position = 0ffffh?
	JZ	FDNXT6	;yes, exit now then.
	LHLD	SAVFCB	;set (DE) pointing to filename to match.
	XCHG
	LDAX	D
	CPI	0E5H	;empty directory entry?
	JZ	FDNXT1	;(* are we trying to reserect erased entries? *)
	PUSH	D
	CALL	MOREFL	;more files in directory?
	POP	D
	JNC	FDNXT6	;no more. Exit now.
FDNXT1:	CALL	FCB2HL	;get address of this fcb in directory.
	LDA	COUNTR	;get number of bytes (characters) to check.
	MOV	C,A
	MVI	B,0	;initialize byte position counter.
FDNXT2:	MOV	A,C	;are we done with the compare?
	ORA	A
	JZ	FDNXT5
	LDAX	D	;no, check next byte.
	CPI	'?'	;don't care about this character?
	JZ	FDNXT4
	MOV	A,B	;get bytes position in fcb.
	CPI	13	;don't care about the thirteenth byte either.
	JZ	FDNXT4
	CPI	12	;extent byte?
	LDAX	D
	JZ	FDNXT3
	SUB	M	;otherwise compare characters.
	ANI	7FH
	JNZ	FNDNXT	;not the same, check next entry.
	JMP	FDNXT4	;so far so good, keep checking.
FDNXT3:	PUSH	B	;check the extent byte here.
	MOV	C,M
	CALL	SAMEXT
	POP	B
	JNZ	FNDNXT	;not the same, look some more.
;
;   So far the names compare. Bump pointers to the next byte
; and continue until all (C) characters have been checked.
;
FDNXT4:	INX	D	;bump pointers.
	INX	H
	INR	B
	DCR	C	;adjust character counter.
	JMP	FDNXT2
FDNXT5:	LDA	FLEPOS	;return the position of this entry.
	ANI	03H
	STA	STATUS
	LXI	H,FNDSTA
	MOV	A,M
	RAL
	RNC
	XRA	A
	MOV	M,A
	RET
;
;   Filename was not found. Set appropriate status.
;
FDNXT6:	CALL	STFLPS;set (FLEPOS) to 0ffffh.
	MVI	A,0FFH	;say not located.
	JMP	SETSTA
;
;   Erase files from the directory. Only the first byte of the
; fcb will be affected. It is set to (E5).
;
ERAFLE:	CALL	CKWPRT	;is disk write protected?
	MVI	C,12	;only compare file names.
	CALL	FNDFST	;get first file name.
ERAFL1:	CALL	CKFLPS  ;any found?
	RZ		;nope, we must be done.
	CALL	CKROFL	;is file read only?
	CALL	FCB2HL	;nope, get address of fcb and
	MVI	M,0E5H	;set first byte to 'empty'.
	MVI	C,0	;clear the space from the bit map.
	CALL	STFILE
	CALL	DIRWRT  ;now write the directory sector back out.
	CALL	FNDNXT	;find the next file name.
	JMP	ERAFL1	;and repeat process.
;
;   Look through the space allocation map (bit map) for the
; next available block. Start searching at block number (BC-1).
; The search procedure is to look for an empty block that is
; before the starting block. If not empty, look at a later
; block number. In this way, we return the closest empty block
; on either side of the 'target' block number. This will speed
; access on random devices. For serial devices, this should be
; changed to look in the forward direction first and then start
; at the front and search some more.
;
;   On return, (DE)= block number that is empty and (HL) =0
; if no empry block was found.
;
FDSPCE:	MOV	D,B	;set (DE) as the block that is checked.
	MOV	E,C
;
;   Look before target block. Registers (BC) are used as the lower
; pointer and (DE) as the upper pointer.
;
FDSPA1:	MOV	A,C	;is block 0 specified?
	ORA	B
	JZ	FDSPA2
	DCX	B	;nope, check previous block.
	PUSH	D
	PUSH	B
	CALL	CKBMAP
	RAR		;is this block empty?
	JNC	FDSPA3	;yes. use this.
;
;   Note that the above logic gets the first block that it finds
; that is empty. Thus a file could be written 'backward' making
; it very slow to access. This could be changed to look for the
; first empty block and then continue until the start of this
; empty space is located and then used that starting block.
; This should help speed up access to some files especially on
; a well used disk with lots of fairly small 'holes'.
;
	POP	B	;nope, check some more.
	POP	D
;
;   Now look after target block.
;
FDSPA2:	LHLD	DSKSZE	;is block (DE) within disk limits?
	MOV	A,E
	SUB	L
	MOV	A,D
	SBB	H
	JNC	FDSPA4
	INX	D	;yes, move on to next one.
	PUSH	B
	PUSH	D
	MOV	B,D
	MOV	C,E
	CALL	CKBMAP  ;check it.
	RAR		;empty?
	JNC	FDSPA3
	POP	D	;nope, continue searching.
	POP	B
	JMP	FDSPA1
;
;   Empty block found. Set it as used and return with (HL)
; pointing to it (true?).
;
FDSPA3:	RAL		;reset byte.
	INR	A	;and set bit 0.
	CALL	STBMP1	;update bit map.
	POP	H	;set return registers.
	POP	D
	RET
;
;   Free block was not found. If (BC) is not zero, then we have
; not checked all of the disk space.
;
FDSPA4:	MOV	A,C
	ORA	B
	JNZ	FDSPA1
	LXI	H,0	;set 'not found' status.
	RET
;
;   Move a complete fcb entry into the directory and write it.
;
FCBSET:	MVI	C,0
	MVI	E,32	;length of each entry.
;
;   Move (E) bytes from the fcb pointed to by (PARAMS) into
; fcb in directory starting at relative byte (C). This updated
; directory buffer is then written to the disk.
;
UPDATE:	PUSH	D
	MVI	B,0	;set (BC) to relative byte position.
	LHLD	PARAMS	;get address of fcb.
	DAD	B	;compute starting byte.
	XCHG
	CALL	FCB2HL	;get address of fcb to update in directory.
	POP	B	;set (C) to number of bytes to change.
	CALL	DE2HL
UPDAT1:	CALL	TRKSEC	;determine the track and sector affected.
	JMP	DIRWRT	;then write this sector out.
;
;   Routine to change the name of all files on the disk with a
; specified name. The fcb contains the current name as the
; first 12 characters and the new name 16 bytes into the fcb.
;
CHGNAM:	CALL	CKWPRT	;check for a write protected disk.
	MVI	C,12	;match first 12 bytes of fcb only.
	CALL	FNDFST	;get first name.
	LHLD	PARAMS	;get address of fcb.
	MOV	A,M	;get user number.
	LXI	D,16	;move over to desired name.
	DAD	D
	MOV	M,A	;keep same user number.
CHGNM1:	CALL	CKFLPS  ;any matching file found?
	RZ		;no, we must be done.
	CALL	CKROFL	;check for read only file.
	MVI	C,16	;start 16 bytes into fcb.
	MVI	E,12	;and update the first 12 bytes of directory.
	CALL	UPDATE
	CALL	FNDNXT	;get te next file name.
	JMP	CHGNM1	;and continue.
;
;   Update a files attributes. The procedure is to search for
; every file with the same name as shown in fcb (ignoring bit 7)
; and then to update it (which includes bit 7). No other changes
; are made.
;
SAVATR:	MVI	C,12	;match first 12 bytes.
	CALL	FNDFST	;look for first filename.
SVATR1:	CALL	CKFLPS  ;was one found?
	RZ		;nope, we must be done.
	MVI	C,0	;yes, update the first 12 bytes now.
	MVI	E,12
	CALL	UPDATE	;update filename and write directory.
	CALL	FNDNXT	;and get the next file.
	JMP	SVATR1	;then continue until done.
;
;  Open a file (name specified in fcb).
;
OPENIT:	MVI	C,15	;compare the first 15 bytes.
	CALL	FNDFST	;get the first one in directory.
	CALL	CKFLPS;any at all?
	RZ
OPNIT1:	CALL	SETEXT	;point to extent byte within users fcb.
	MOV	A,M	;and get it.
	PUSH	PSW	;save it and address.
	PUSH	H
	CALL	FCB2HL	;point to fcb in directory.
	XCHG
	LHLD	PARAMS	;this is the users copy.
	MVI	C,32	;move it into users space.
	PUSH	D
	CALL	DE2HL
	CALL	STS2B7	;set bit 7 in 's2' byte (unmodified).
	POP	D	;now get the extent byte from this fcb.
	LXI	H,12
	DAD	D
	MOV	C,M	;into (C).
	LXI	H,15	;now get the record count byte into (B).
	DAD	D
	MOV	B,M
	POP	H	;keep the same extent as the user had originally.
	POP	PSW
	MOV	M,A
	MOV	A,C	;is it the same as in the directory fcb?
	CMP	M
	MOV	A,B	;if yes, then use the same record count.
	JZ	OPNIT2
	MVI	A,0	;if the user specified an extent greater than
	JC	OPNIT2	;the one in the directory, then set record count to 0.
	MVI	A,128	;otherwise set to maximum.
OPNIT2:	LHLD	PARAMS	;set record count in users fcb to (A).
	LXI	D,15
	DAD	D	;compute relative position.
	MOV	M,A	;and set the record count.
	RET
;
;   Move two bytes from (DE) to (HL) if (and only if) (HL)
; point to a zero value (16 bit).
;   Return with zero flag set it (DE) was moved. Registers (DE)
; and (HL) are not changed. However (A) is.
;
MOVWRD:	MOV	A,M	;check for a zero word.
	INX	H
	ORA	M	;both bytes zero?
	DCX	H
	RNZ		;nope, just return.
	LDAX	D	;yes, move two bytes from (DE) into
	MOV	M,A	;this zero space.
	INX	D
	INX	H
	LDAX	D
	MOV	M,A
	DCX	D	;don't disturb these registers.
	DCX	H
	RET
;
;   Get here to close a file specified by (fcb).
;
CLOSIT:	XRA	A	;clear status and file position bytes.
	STA	STATUS
	STA	FLEPOS
	STA	FLEPOS+1
	CALL	GTWPRT	;get write protect bit for this drive.
	RNZ		;just return if it is set.
	CALL	GETS2	;else get the 's2' byte.
	ANI	80H	;and look at bit 7 (file unmodified?).
	RNZ		;just return if set.
	MVI	C,15	;else look up this file in directory.
	CALL	FNDFST
	CALL	CKFLPS;was it found?
	RZ		;just return if not.
	LXI	B,16	;set (HL) pointing to records used section.
	CALL	FCB2HL
	DAD	B
	XCHG
	LHLD	PARAMS	;do the same for users specified fcb.
	DAD	B
	MVI	C,16	;this many bytes are present in this extent.
CLSIT1:	LDA	BIGDSK	;8 or 16 bit record numbers?
	ORA	A
	JZ	CLSIT4
	MOV	A,M	;just 8 bit. Get one from users fcb.
	ORA	A
	LDAX	D	;now get one from directory fcb.
	JNZ	CLSIT2
	MOV	M,A	;users byte was zero. Update from directory.
CLSIT2:	ORA	A
	JNZ	CLSIT3
	MOV	A,M	;directories byte was zero, update from users fcb.
	STAX	D
CLSIT3:	CMP	M	;if neither one of these bytes were zero,
	JNZ	CLSIT7	;then close error if they are not the same.
	JMP	CLSIT5	;ok so far, get to next byte in fcbs.
CLSIT4:	CALL	MOVWRD  ;update users fcb if it is zero.
	XCHG
	CALL	MOVWRD  ;update directories fcb if it is zero.
	XCHG
	LDAX	D	;if these two values are no different,
	CMP	M	;then a close error occured.
	JNZ	CLSIT7
	INX	D	;check second byte.
	INX	H
	LDAX	D
	CMP	M
	JNZ	CLSIT7
	DCR	C	;remember 16 bit values.
CLSIT5:	INX	D	;bump to next item in table.
	INX	H
	DCR	C	;there are 16 entries only.
	JNZ	CLSIT1  ;continue if more to do.
	LXI	B,0FFECH;backup 20 places (extent byte).
	DAD	B
	XCHG
	DAD	B
	LDAX	D
	CMP	M	;directory's extent already greater than the
	JC	CLSIT6	;users extent?
	MOV	M,A	;no, update directory extent.
	LXI	B,3	;and update the record count byte in
	DAD	B	;directories fcb.
	XCHG
	DAD	B
	MOV	A,M	;get from user.
	STAX	D	;and put in directory.
CLSIT6:	MVI	A,0FFH	;set 'was open and is now closed' byte.
	STA	CLSFLG
	JMP	UPDAT1	;update the directory now.
CLSIT7:	LXI	H,STATUS;set return status and then return.
	DCR	M
	RET
;
;   Routine to get the next empty space in the directory. It
; will then be cleared for use.
;
GETEMT:	CALL	CKWPRT	;make sure disk is not write protected.
	LHLD	PARAMS	;save current parameters (fcb).
	PUSH	H
	LXI	H,EMTFCB;use special one for empty space.
	SHLD	PARAMS
	MVI	C,1	;search for first empty spot in directory.
	CALL	FNDFST	;(* only check first byte *)
	CALL	CKFLPS  ;none?
	POP	H
	SHLD	PARAMS	;restore original fcb address.
	RZ		;return if no more space.
	XCHG
	LXI	H,15	;point to number of records for this file.
	DAD	D
	MVI	C,17	;and clear all of this space.
	XRA	A
GETMT1:	MOV	M,A
	INX	H
	DCR	C
	JNZ	GETMT1
	LXI	H,13	;clear the 's1' byte also.
	DAD	D
	MOV	M,A
	CALL	CKNMBR	;keep (SCRCH1) within bounds.
	CALL	FCBSET	;write out this fcb entry to directory.
	JMP	STS2B7	;set 's2' byte bit 7 (unmodified at present).
;
;   Routine to close the current extent and open the next one
; for reading.
;
GTNEXT:	XRA	A
	STA	CLSFLG;clear close flag.
	CALL	CLOSIT	;close this extent.
	CALL	CKFLPS
	RZ		;not there???
	LHLD	PARAMS	;get extent byte.
	LXI	B,12
	DAD	B
	MOV	A,M	;and increment it.
	INR	A
	ANI	1FH	;keep within range 0-31.
	MOV	M,A
	JZ	GTNXT1	;overflow?
	MOV	B,A	;mask extent byte.
	LDA	EXTMSK
	ANA	B
	LXI	H,CLSFLG;check close flag (0ffh is ok).
	ANA	M
	JZ	GTNXT2	;if zero, we must read in next extent.
	JMP	GTNXT3	;else, it is already in memory.
GTNXT1:	LXI	B,2	;Point to the 's2' byte.
	DAD	B
	INR	M	;and bump it.
	MOV	A,M	;too many extents?
	ANI	0FH
	JZ	GTNXT5	;yes, set error code.
;
;   Get here to open the next extent.
;
GTNXT2:	MVI	C,15	;set to check first 15 bytes of fcb.
	CALL	FNDFST	;find the first one.
	CALL	CKFLPS  ;none available?
	JNZ	GTNXT3
	LDA	RWRFLG  ;no extent present. Can we open an empty one?
	INR	A	;0ffh means reading (so not possible).
	JZ	GTNXT5	;or an error.
	CALL	GETEMT  ;we are writing, get an empty entry.
	CALL	CKFLPS  ;none?
	JZ	GTNXT5	;error if true.
	JMP	GTNXT4	;else we are almost done.
GTNXT3:	CALL	OPNIT1	;open this extent.
GTNXT4:	CALL	STRDAT	;move in updated data (rec #, extent #, etc.)
	XRA	A	;clear status and return.
	JMP	SETSTA
;
;   Error in extending the file. Too many extents were needed
; or not enough space on the disk.
;
GTNXT5:	CALL	IOERR1	;set error code, clear bit 7 of 's2'
	JMP	STS2B7	;so this is not written on a close.
;
;   Read a sequential file.
;
RDSEQ:	MVI	A,1	;set sequential access mode.
	STA	MODE
RDSEQ1:	MVI	A,0FFH	;don't allow reading unwritten space.
	STA	RWRFLG
	CALL	STRDAT	;put rec# and ext# into fcb.
	LDA	SAVNRC	;get next record to read.
	LXI	H,SAVNXT;get number of records in extent.
	CMP	M	;within this extent?
	JC	RDSEQ2
	CPI	128	;no. Is this extent fully used?
	JNZ	RDSEQ3	;no. End-of-file.
	CALL	GTNEXT	;yes, open the next one.
	XRA	A	;reset next record to read.
	STA	SAVNRC
	LDA	STATUS	;check on open, successful?
	ORA	A
	JNZ	RDSEQ3	;no, error.
RDSEQ2:	CALL	COMBLK	;ok. compute block number to read.
	CALL	CHKBLK	;check it. Within bounds?
	JZ	RDSEQ3	;no, error.
	CALL	LOGICL	;convert (BLNMBR) to logical sector (128 byte).
	CALL	TRKSC1	;set the track and sector for this block #.
	CALL	DOREAD	;and read it.
	JMP	SETNRC	;and set the next record to be accessed.
;
;   Read error occured. Set status and return.
;
RDSEQ3:	JMP	IOERR1
;
;   Write the next sequential record.
;
WTSEQ:	MVI	A,1	;set sequential access mode.
	STA	MODE
WTSEQ1:	MVI	A,0	;allow an addition empty extent to be opened.
	STA	RWRFLG
	CALL	CKWPRT	;check write protect status.
	LHLD	PARAMS
	CALL	CKROF1	;check for read only file, (HL) already set to fcb.
	CALL	STRDAT	;put updated data into fcb.
	LDA	SAVNRC	;get record number to write.
	CPI	128	;within range?
	JNC	IOERR1	;no, error(?).
	CALL	COMBLK	;compute block number.
	CALL	CHKBLK	;check number.
	MVI	C,0	;is there one to write to?
	JNZ	WTSEQ6	;yes, go do it.
	CALL	GETBLK;get next block number within fcb to use.
	STA	RELBLK;and save.
	LXI	B,0	;start looking for space from the start
	ORA	A	;if none allocated as yet.
	JZ	WTSEQ2
	MOV	C,A	;extract previous block number from fcb
	DCX	B	;so we can be closest to it.
	CALL	EXTBLK
	MOV	B,H
	MOV	C,L
WTSEQ2:	CALL	FDSPCE;find the next empty block nearest number (BC).
	MOV	A,L	;check for a zero number.
	ORA	H
	JNZ	WTSEQ3
	MVI	A,2	;no more space?
	JMP	SETSTA
WTSEQ3:	SHLD	BLNMBR	;save block number to access.
	XCHG		;put block number into (DE).
	LHLD	PARAMS	;now we must update the fcb for this
	LXI	B,16	;newly allocated block.
	DAD	B
	LDA	BIGDSK	;8 or 16 bit block numbers?
	ORA	A
	LDA	RELBLK	;(* update this entry *)
	JZ	WTSEQ4	;zero means 16 bit ones.
	CALL	ADA2HL	;(HL)=(HL)+(A)
	MOV	M,E	;store new block number.
	JMP	WTSEQ5
WTSEQ4:	MOV	C,A	;compute spot in this 16 bit table.
	MVI	B,0
	DAD	B
	DAD	B
	MOV	M,E	;stuff block number (DE) there.
	INX	H
	MOV	M,D
WTSEQ5:	MVI	C,2	;set (C) to indicate writing to un-used disk space.
WTSEQ6:	LDA	STATUS	;are we ok so far?
	ORA	A
	RNZ
	PUSH	B	;yes, save write flag for bios (register C).
	CALL	LOGICL	;convert (BLKNMBR) over to loical sectors.
	LDA	MODE	;get access mode flag (1=sequential,
	DCR	A	;0=random, 2=special?).
	DCR	A
	JNZ	WTSEQ9
;
;   Special random i/o from function #40. Maybe for M/PM, but the
; current block, if it has not been written to, will be zeroed
; out and then written (reason?).
;
	POP	B
	PUSH	B
	MOV	A,C	;get write status flag (2=writing unused space).
	DCR	A
	DCR	A
	JNZ	WTSEQ9
	PUSH	H
	LHLD	DIRBUF	;zero out the directory buffer.
	MOV	D,A	;note that (A) is zero here.
WTSEQ7:	MOV	M,A
	INX	H
	INR	D	;do 128 bytes.
	JP	WTSEQ7
	CALL	DIRDMA	;tell the bios the dma address for directory access.
	LHLD	LOGSEC	;get sector that starts current block.
	MVI	C,2	;set 'writing to unused space' flag.
WTSEQ8:	SHLD	BLNMBR	;save sector to write.
	PUSH	B
	CALL	TRKSC1	;determine its track and sector numbers.
	POP	B
	CALL	DOWRTE	;now write out 128 bytes of zeros.
	LHLD	BLNMBR	;get sector number.
	MVI	C,0	;set normal write flag.
	LDA	BLKMSK	;determine if we have written the entire
	MOV	B,A	;physical block.
	ANA	L
	CMP	B
	INX	H	;prepare for the next one.
	JNZ	WTSEQ8	;continue until (BLKMASK+1) sectors written.
	POP	H	;reset next sector number.
	SHLD	BLNMBR
	CALL	DEFDMA	;and reset dma address.
;
;   Normal disk write. Set the desired track and sector then
; do the actual write.
;
WTSEQ9:	CALL	TRKSC1	;determine track and sector for this write.
	POP	B	;get write status flag.
	PUSH	B
	CALL	DOWRTE	;and write this out.
	POP	B
	LDA	SAVNRC	;get number of records in file.
	LXI	H,SAVNXT;get last record written.
	CMP	M
	JC	WTSQ10
	MOV	M,A	;we have to update record count.
	INR	M
	MVI	C,2
;
;*   This area has been patched to correct disk update problem
;* when using blocking and de-blocking in the BIOS.
;
WTSQ10:  DCR	C	;was 'dcr c'
	 DCR	C	;was 'dcr c'
	 JNZ    WTSQ99	;was 'jnz wtseq99'
;
; *   End of patch.
;
	PUSH	PSW
	CALL	GETS2	;set 'extent written to' flag.
	ANI	7FH	;(* clear bit 7 *)
	MOV	M,A
	POP	PSW	;get record count for this extent.
WTSQ99:	CPI	127	;is it full?
	JNZ	WTSQ12
	LDA	MODE	;yes, are we in sequential mode?
	CPI	1
	JNZ	WTSQ12
	CALL	SETNRC	;yes, set next record number.
	CALL	GTNEXT	;and get next empty space in directory.
	LXI	H,STATUS;ok?
	MOV	A,M
	ORA	A
	JNZ	WTSQ11
	DCR	A	;yes, set record count to -1.
	STA	SAVNRC
WTSQ11:	MVI	M,0	;clear status.
WTSQ12:	JMP	SETNRC	;set next record to access.
;
;   For random i/o, set the fcb for the desired record number
; based on the 'r0,r1,r2' bytes. These bytes in the fcb are
; used as follows:
;
;       fcb+35            fcb+34            fcb+33
;  |     'r-2'      |      'r-1'      |      'r-0'     |
;  |7             0 | 7             0 | 7             0|
;  |0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0|
;  |    overflow   | | extra |  extent   |   record #  |
;  | ______________| |_extent|__number___|_____________|
;                     also 's2'
;
;   On entry, register (C) contains 0ffh if this is a read
; and thus we can not access unwritten disk space. Otherwise,
; another extent will be opened (for writing) if required.
;
POSITN:	XRA	A	;set random i/o flag.
	STA	MODE
;
;   Special entry (function #40). M/PM ?
;
POSTN1:	PUSH	B	;save read/write flag.
	LHLD	PARAMS	;get address of fcb.
	XCHG
	LXI	H,33	;now get byte 'r0'.
	DAD	D
	MOV	A,M
	ANI	7FH	;keep bits 0-6 for the record number to access.
	PUSH	PSW
	MOV	A,M	;now get bit 7 of 'r0' and bits 0-3 of 'r1'.
	RAL
	INX	H
	MOV	A,M
	RAL
	ANI	1FH	;and save this in bits 0-4 of (C).
	MOV	C,A	;this is the extent byte.
	MOV	A,M	;now get the extra extent byte.
	RAR
	RAR
	RAR
	RAR
	ANI	0FH
	MOV	B,A	;and save it in (B).
	POP	PSW	;get record number back to (A).
	INX	H	;check overflow byte 'r2'.
	MOV	L,M
	INR	L
	DCR	L
	MVI	L,6	;prepare for error.
	JNZ	POSTN5	;out of disk space error.
	LXI	H,32	;store record number into fcb.
	DAD	D
	MOV	M,A
	LXI	H,12	;and now check the extent byte.
	DAD	D
	MOV	A,C
	SUB	M	;same extent as before?
	JNZ	POSTN2
	LXI	H,14	;yes, check extra extent byte 's2' also.
	DAD	D
	MOV	A,B
	SUB	M
	ANI	7FH
	JZ	POSTN3  ;same, we are almost done then.
;
;  Get here when another extent is required.
;
POSTN2:	PUSH	B
	PUSH	D
	CALL	CLOSIT	;close current extent.
	POP	D
	POP	B
	MVI	L,3	;prepare for error.
	LDA	STATUS
	INR	A
	JZ	POSTN4	;close error.
	LXI	H,12	;put desired extent into fcb now.
	DAD	D
	MOV	M,C
	LXI	H,14	;and store extra extent byte 's2'.
	DAD	D
	MOV	M,B
	CALL	OPENIT	;try and get this extent.
	LDA	STATUS	;was it there?
	INR	A
	JNZ	POSTN3
	POP	B	;no. can we create a new one (writing?).
	PUSH	B
	MVI	L,4	;prepare for error.
	INR	C
	JZ	POSTN4	;nope, reading unwritten space error.
	CALL	GETEMT;yes we can, try to find space.
	MVI	L,5	;prepare for error.
	LDA	STATUS
	INR	A
	JZ	POSTN4	;out of space?
;
;   Normal return location. Clear error code and return.
;
POSTN3:	POP	B	;restore stack.
	XRA	A	;and clear error code byte.
	JMP	SETSTA
;
;   Error. Set the 's2' byte to indicate this (why?).
;
POSTN4:	PUSH	H
	CALL	GETS2
	MVI	M,0C0H
	POP	H
;
;   Return with error code (presently in L).
;
POSTN5:	POP	B
	MOV	A,L	;get error code.
	STA	STATUS
	JMP	STS2B7
;
;   Read a random record.
;
READRN:	MVI	C,0FFH	;set 'read' status.
	CALL	POSITN;position the file to proper record.
	CZ	RDSEQ1	;and read it as usual (if no errors).
	RET
;
;   Write to a random record.
;
WRTRAN:	MVI	C,0	;set 'writing' flag.
	CALL	POSITN;position the file to proper record.
	CZ	WTSEQ1	;and write as usual (if no errors).
	RET
;
;   Compute the random record number. Enter with (HL) pointing
; to a fcb an (DE) contains a relative location of a record
; number. On exit, (C) contains the 'r0' byte, (B) the 'r1'
; byte, and (A) the 'r2' byte.
;
;   On return, the zero flag is set if the record is within
; bounds. Otherwise, an overflow occured.
;
CMPRND:	XCHG		;save fcb pointer in (DE).
	DAD	D	;compute relative position of record #.
	MOV	C,M	;get record number into (BC).
	MVI	B,0
	LXI	H,12	;now get extent.
	DAD	D
	MOV	A,M	;compute (BC)=(record #)+(extent)*128.
	RRC		;move lower bit into bit 7.
	ANI	80H	;and ignore all other bits.
	ADD	C	;add to our record number.
	MOV	C,A
	MVI	A,0	;take care of any carry.
	ADC	B
	MOV	B,A
	MOV	A,M	;now get the upper bits of extent into
	RRC		;bit positions 0-3.
	ANI	0FH	;and ignore all others.
	ADD	B	;add this in to 'r1' byte.
	MOV	B,A
	LXI	H,14	;get the 's2' byte (extra extent).
	DAD	D
	MOV	A,M
	ADD	A	;and shift it left 4 bits (bits 4-7).
	ADD	A
	ADD	A
	ADD	A
	PUSH	PSW	;save carry flag (bit 0 of flag byte).
	ADD	B	;now add extra extent into 'r1'.
	MOV	B,A
	PUSH	PSW	;and save carry (overflow byte 'r2').
	POP	H	;bit 0 of (L) is the overflow indicator.
	MOV	A,L
	POP	H	;and same for first carry flag.
	ORA	L	;either one of these set?
	ANI	01H	;only check the carry flags.
	RET
;
;   Routine to setup the fcb (bytes 'r0', 'r1', 'r2') to
; reflect the last record used for a random (or other) file.
; This reads the directory and looks at all extents computing
; the largerst record number for each and keeping the maximum
; value only. Then 'r0', 'r1', and 'r2' will reflect this
; maximum record number. This is used to compute the space used
; by a random file.
;
RANSZE:	MVI	C,12	;look thru directory for first entry with
	CALL	FNDFST	;this name.
	LHLD	PARAMS	;zero out the 'r0, r1, r2' bytes.
	LXI	D,33
	DAD	D
	PUSH	H
	MOV	M,D	;note that (D)=0.
	INX	H
	MOV	M,D
	INX	H
	MOV	M,D
RANSZ1:	CALL	CKFLPS  ;is there an extent to process?
	JZ	RANSZ3	;no, we are done.
	CALL	FCB2HL	;set (HL) pointing to proper fcb in dir.
	LXI	D,15	;point to last record in extent.
	CALL	CMPRND  ;and compute random parameters.
	POP	H
	PUSH	H	;now check these values against those
	MOV	E,A	;already in fcb.
	MOV	A,C	;the carry flag will be set if those
	SUB	M	;in the fcb represent a larger size than
	INX	H	;this extent does.
	MOV	A,B
	SBB	M
	INX	H
	MOV	A,E
	SBB	M
	JC	RANSZ2
	MOV	M,E	;we found a larger (in size) extent.
	DCX	H	;stuff these values into fcb.
	MOV	M,B
	DCX	H
	MOV	M,C
RANSZ2:	CALL	FNDNXT	;now get the next extent.
	JMP	RANSZ1	;continue til all done.
RANSZ3:	POP	H	;we are done, restore the stack and
	RET		;return.
;
;   Function to return the random record position of a given
; file which has been read in sequential mode up to now.
;
SETRAN:	LHLD	PARAMS	;point to fcb.
	LXI	D,32	;and to last used record.
	CALL	CMPRND;compute random position.
	LXI	H,33	;now stuff these values into fcb.
	DAD	D
	MOV	M,C	;move 'r0'.
	INX	H
	MOV	M,B	;and 'r1'.
	INX	H
	MOV	M,A	;and lastly 'r2'.
	RET
;
;   This routine select the drive specified in (ACTIVE) and
; update the login vector and bitmap table if this drive was
; not already active.
;
LOGDRV:	LHLD	LOGIN	;get the login vector.
	LDA	ACTIVE	;get the default drive.
	MOV	C,A
	CALL	SHIFTR	;position active bit for this drive
	PUSH	H	;into bit 0.
	XCHG
	CALL	SELECT	;select this drive.
	POP	H
	CZ	SLTERR	;valid drive?
	MOV	A,L	;is this a newly activated drive?
	RAR
	RC
	LHLD	LOGIN	;yes, update the login vector.
	MOV	C,L
	MOV	B,H
	CALL	SETBIT
	SHLD	LOGIN	;and save.
	JMP	BITMAP	;now update the bitmap.
;
;   Function to set the active disk number.
;
SETDSK:	LDA	EPARAM	;get parameter passed and see if this
	LXI	H,ACTIVE;represents a change in drives.
	CMP	M
	RZ
	MOV	M,A	;yes it does, log it in.
	JMP	LOGDRV
;
;   This is the 'auto disk select' routine. The firsst byte
; of the fcb is examined for a drive specification. If non
; zero then the drive will be selected and loged in.
;
AUTOSL:	MVI	A,0FFH	;say 'auto-select activated'.
	STA	AUTO
	LHLD	PARAMS	;get drive specified.
	MOV	A,M
	ANI	1FH	;look at lower 5 bits.
	DCR	A	;adjust for (1=A, 2=B) etc.
	STA	EPARAM	;and save for the select routine.
	CPI	1EH	;check for 'no change' condition.
	JNC	AUTOS1	;yes, don't change.
	LDA	ACTIVE	;we must change, save currently active
	STA	OLDDRV	;drive.
	MOV	A,M	;and save first byte of fcb also.
	STA	AUTOFG;this must be non-zero.
	ANI	0E0H	;whats this for (bits 6,7 are used for
	MOV	M,A	;something)?
	CALL	SETDSK	;select and log in this drive.
AUTOS1:	LDA	USERNO	;move user number into fcb.
	LHLD	PARAMS	;(* upper half of first byte *)
	ORA	M
	MOV	M,A
	RET		;and return (all done).
;
;   Function to return the current cp/m version number.
;
GETVER:	MVI	A,022h	;version 2.2
	JMP	SETSTA
;
;   Function to reset the disk system.
;
RSTDSK:	LXI	H,0	;clear write protect status and log
	SHLD	WRTPRT	;in vector.
	SHLD	LOGIN
        XRA     A       ;select drive 'A'.
        STA     ACTIVE
	LXI	H,TBUFF	;setup default dma address.
	SHLD	USRDMA
  call  resdsk##
	CALL	DEFDMA
;        ret
;        lxi     d,resdfn
;        call    CallBios##      ;вызываем сброс железа
        JMP     LOGDRV;now log in drive 'A'.
;resdfn: db      13h             ;это код вызова дискового BIOS
;        db       0              ;а это признак сброса

;
;   Function to open a specified file.
;
OPNFIL:	CALL	CLRS2	;clear 's2' byte.
	CALL	AUTOSL	;select proper disk.
	JMP	OPENIT	;and open the file.
;
;   Function to close a specified file.
;
CLOSFL:	CALL	AUTOSL	;select proper disk.
	JMP	CLOSIT	;and close the file.
;
;   Function to return the first occurence of a specified file
; name. If the first byte of the fcb is '?' then the name will
; not be checked (get the first entry no matter what).
;
GETFST:	MVI	C,0	;prepare for special search.
	XCHG
	MOV	A,M	;is first byte a '?'?
	CPI	'?'
	JZ	GTFST1	;yes, just get very first entry (zero length match).
	CALL	SETEXT	;get the extension byte from fcb.
	MOV	A,M	;is it '?'? if yes, then we want
	CPI	'?'	;an entry with a specific 's2' byte.
	CNZ	CLRS2	;otherwise, look for a zero 's2' byte.
	CALL	AUTOSL	;select proper drive.
	MVI	C,15	;compare bytes 0-14 in fcb (12&13 excluded).
GTFST1:	CALL	FNDFST	;find an entry and then move it into
	JMP	MOVDIR	;the users dma space.
;
;   Function to return the next occurence of a file name.
;
GETNXT:	LHLD	SAVFCB	;restore pointers. note that no
	SHLD	PARAMS	;other dbos calls are allowed.
	CALL	AUTOSL	;no error will be returned, but the
	CALL	FNDNXT	;results will be wrong.
	JMP	MOVDIR
;
;   Function to delete a file by name.
;
DELFIL:	CALL	AUTOSL	;select proper drive.
	CALL	ERAFLE	;erase the file.
	JMP	STSTTS  ;set status and return.
;
;   Function to execute a sequential read of the specified
; record number.
;
READSQ:	CALL	AUTOSL	;select proper drive then read.
	JMP	RDSEQ
;
;   Function to write the net sequential record.
;
WRTSEQ:	CALL	AUTOSL	;select proper drive then write.
	JMP	WTSEQ
;
;   Create a file function.
;
FCREAT:	CALL	CLRS2	;clear the 's2' byte on all creates.
	CALL	AUTOSL	;select proper drive and get the next
	JMP	GETEMT  ;empty directory space.
;
;   Function to rename a file.
;
RENFIL:	CALL	AUTOSL	;select proper drive and then switch
	CALL	CHGNAM  ;file names.
	JMP	STSTTS
;
;   Function to return the login vector.
;
GETLOG:	LHLD	LOGIN
	JMP	GTPRM1
;
;   Function to return the current disk assignment.
;
GETCRN:	LDA	ACTIVE
	JMP	SETSTA
;
;   Function to set the dma address.
;
PUTDMA:	XCHG
	SHLD	USRDMA	;save in our space and then get to
	JMP	DEFDMA	;the bios with this also.
;
;   Function to return the allocation vector.
;
GTALOC:	LHLD	ALCVCT
	JMP	GTPRM1
;
;   Function to return the read-only status vector.
;
GETROV:	LHLD	WRTPRT
	JMP	GTPRM1
;
;   Function to set the file attributes (read-only, system).
;
STATTR:	CALL	AUTOSL	;select proper drive then save attributes.
	CALL	SAVATR
	JMP	STSTTS
;
;   Function to return the address of the disk parameter block
; for the current drive.
;
GTPARM:	LHLD	DISKPB
GTPRM1:	SHLD	STATUS
	RET
;
;   Function to get or set the user number. If (E) was (FF)
; then this is a request to return the current user number.
; Else set the user number from (E).
;
GTUSER:	LDA	EPARAM	;get parameter.
	CPI	0FFH	;get user number?
	JNZ	SETUSR
	LDA	USERNO	;yes, just do it.
	JMP	SETSTA
SETUSR:	ANI	1FH	;no, we should set it instead. keep low
	STA	USERNO	;bits (0-4) only.
	RET
;
;   Function to read a random record from a file.
;
RDRNDM:	CALL	AUTOSL	;select proper drive and read.
	JMP	READRN
;
;   Function to compute the file size for random files.
;
WTRNDM:	CALL	AUTOSL	;select proper drive and write.
	JMP	WRTRAN
;
;   Function to compute the size of a random file.
;
FLSIZE:	CALL	AUTOSL	;select proper drive and check file length
	JMP	RANSZE
;
;   Function #37. This allows a program to log off any drives.
; On entry, set (DE) to contain a word with bits set for those
; drives that are to be logged off. The log-in vector and the
; write protect vector will be updated. This must be a M/PM
; special function.
;
LOGOFF:	LHLD	PARAMS	;get drives to log off.
	MOV	A,L	;for each bit that is set, we want
	CMA		;to clear that bit in (LOGIN)
	MOV	E,A	;and (WRTPRT).
	MOV	A,H
	CMA
	LHLD	LOGIN	;reset the login vector.
	ANA	H
	MOV	D,A
	MOV	A,L
	ANA	E
	MOV	E,A
	LHLD	WRTPRT
	XCHG
	SHLD	LOGIN	;and save.
	MOV	A,L	;now do the write protect vector.
	ANA	E
	MOV	L,A
	MOV	A,H
	ANA	D
	MOV	H,A
	SHLD	WRTPRT	;and save. all done.
	RET
;
;   Get here to return to the user.
;
GOBACK:	LDA	AUTO	;was auto select activated?
	ORA	A
	JZ	GOBCK1
	LHLD	PARAMS	;yes, but was a change made?
	MVI	M,0	;(* reset first byte of fcb *)
	LDA	AUTOFG
	ORA	A
	JZ	GOBCK1
	MOV	M,A	;yes, reset first byte properly.
	LDA	OLDDRV	;and get the old drive and select it.
	STA	EPARAM
	CALL	SETDSK
GOBCK1:	LHLD	USRSTK;reset the users stack pointer.
	SPHL
	LHLD	STATUS	;get return status.
	MOV	A,L	;force version 1.4 compatability.
	MOV	B,H
	RET		;and go back to user.
;
;   Function #40. This is a special entry to do random i/o.
; For the case where we are writing to unused disk space, this
; space will be zeroed out first. This must be a M/PM special
; purpose function, because why would any normal program even
; care about the previous contents of a sector about to be
; written over.
;
WTSPCL:	CALL	AUTOSL	;select proper drive.
	MVI	A,2	;use special write mode.
	STA	MODE
	MVI	C,0	;set write indicator.
	CALL	POSTN1	;position the file.
	CZ	WTSEQ1	;and write (if no errors).
	RET
;
;**************************************************************
;*
;*     BDOS data storage pool.
;*
;**************************************************************
;
EMTFCB:	DB	0E5H	;empty directory segment indicator.
WRTPRT:	DW	0	;write protect status for all 16 drives.
LOGIN:	DW	0	;drive active word (1 bit per drive).
USRDMA:	DW	080H	;user's dma address (defaults to 80h).
;
;   Scratch areas from parameter block.
;
SCRCH1:	DW	0	;relative position within dir segment for file (0-3).
SCRCH2:	DW	0	;last selected track number.
SCRCH3:	DW	0	;last selected sector number.
;
;   Disk storage areas from parameter block.
;
DIRBUF:	DW	0	;address of directory buffer to use.
DISKPB:	DW	0	;contains address of disk parameter block.
CHKVCT:	DW	0	;address of check vector.
ALCVCT:	DW	0	;address of allocation vector (bit map).
;
;   Parameter block returned from the bios.
;
SECTRS:	DW	0	;sectors per track from bios.
BLSHFT:	DB	0	;block shift.
BLKMSK:	DB	0	;block mask.
EXTMSK:	DB	0	;extent mask.
DSKSZE:	DW	0	;disk size from bios (number of blocks-1).
DIRSZE:	DW	0	;directory size.
ALLOC0:	DW	0	;storage for first bytes of bit map (dir space used).
ALLOC1:	DW	0
OFFSET:	DW	0	;first usable track number.
XLATE:	DW	0	;sector translation table address.
;
;
CLSFLG:	DB	0	;close flag (=0ffh is extent written ok).
RWRFLG:	DB	0	;read/write flag (0ffh=read, 0=write).
FNDSTA:	DB	0	;filename found status (0=found first entry).
MODE:	DB	0	;I/o mode select (0=random, 1=sequential, 2=special random).
EPARAM:	DB	0	;storage for register (E) on entry to bdos.
RELBLK:	DB	0	;relative position within fcb of block number written.
COUNTR:	DB	0	;byte counter for directory name searches.
SAVFCB:	DW	0,0	;save space for address of fcb (for directory searches).
BIGDSK:	DB	0	;if =0 then disk is > 256 blocks long.
AUTO:	DB	0	;if non-zero, then auto select activated.
OLDDRV:	DB	0	;on auto select, storage for previous drive.
AUTOFG:	DB	0	;if non-zero, then auto select changed drives.
SAVNXT:	DB	0	;storage for next record number to access.
SAVEXT:	DB	0	;storage for extent number of file.
SAVNRC:	DW	0	;storage for number of records in file.
BLNMBR:	DW	0	;block number (physical sector) used within a file or logical sector.
LOGSEC:	DW	0	;starting logical (128 byte) sector of block (physical sector).
FCBPOS:	DB	0	;relative position within buffer for fcb of file of interest.
FLEPOS:	DW	0	;files position within directory (0 to max entries -1).
;
;   Disk directory buffer checksum bytes. One for each of the
; 16 possible drives.
;
CKSTBL:	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;
;   Extra space ?
;
	DB	0,0,0,0
;
END
;
;*
;******************   E N D   O F   C P / M   *****************
;*
;   Disk directory buffer checksum bytes. One for each of the
; 16 possible drives.
;
CKSTBL:	DB	0,0,0,0,0,0,0,0,0,