.z80
subttl	Copyright Information
	title	Personal CP/M BDOS, Version 1.0, April 1984
;*****************************************************************
;*****************************************************************
;**                                                             **
;**                 P E R S O N A L   C P / M                   **
;**                                                             **
;**   B a s i c    D i s k   O p e r a t i n g   S y s t e m    **
;**                                                             **
;**              I n t e r f a c e   M o d u l e                **
;**                                                             **
;*****************************************************************
;*****************************************************************
;
;	Copyright (c) 1984
;	Digital Research
;	Box 579, Pacific Grove
;	California

subttl	Equates

on	equ	0ffffh
off	equ	00000h

data_low equ	on		;code segment will be at lower address
				;than data segment

standard equ	on		;data not in separate segment

	org	0000H
BASE	equ	$

;	bios value defined at end of module

SSIZE	equ	32	;32 level stack

;	low memory locations

reboot	equ	0000h	;reboot system

;
;	equates for non graphic characters
;
ctlc	equ	03h	;control c
ctle	equ	05h	;physical eol
ctlh	equ	08h	;backspace
ctlp	equ	10h	;prnt toggle
ctlr	equ	12h	;repeat line
ctls	equ	13h	;stop/start screen
ctlu	equ	15h	;line delete
ctlx	equ	18h	;=ctl-u
ctlz	equ	1Ah	;end of file
rubout	equ	7Fh	;char delete
tab	equ	09h	;tab char
cr	equ	0Dh	;carriage return
lf	equ	0Ah	;line feed
ctl	equ	5Eh	;up arrow

subttl	PUBLICs and EXTRNs

	name	('BDOS')

	cseg

				;used
				; by
	public	BDOS$CD
	public	BDOS$DT
	public	?bdos
	public	?bdosc
	public	?bdosw

				;defined in
	
	extrn	?flush		;bios
	extrn	?discd		;bios
	extrn	?mov		;bios
	extrn	?auxis		;bios
	extrn	?auxos		;bios
	extrn	?dscrf		;bios
	extrn	?bytbc		;bios
	extrn	?bytba		;bios

subttl	BDOS Front End
BDOS$CD:
	defb	'654321'

;	enter here from the user's program with function number in c,
;	and information address in d,e

	if	not data_low
?bdos:
	endif

	jp	bdose	;past parameter block

	defw	SUB$FLAG
	defw	OLOG

front_size	equ	$-bdos$cd	;size of block to load to front of
					;data segment

bdose:				;arrive here from user programs
	ld	(info),de	;info = DE

	ld	A,C		;FX=BDOS FUNCTION NUMBER
	ld	(FX),a

	ld	a,(ACTDSK)	;SELDSK=ACTDSK
	ld	(SELDSK),a

	ld	hl,0		;return value defaults to 0000
	ld	(aret),hl

	ld	(RESEL),hl	; RESEL,RELOG = FALSE

;save user's stack pointer, set to local stack
	ld	(entsp),sp	;entsp = SP

	ld	sp,lstack	;local stack setup
	ld	hl,goback	;return here after all functions
	push	hl		;jmp goback equivalent to ret
	ld	A,C
	cp	nfuncs
	jr	nc,HIGH$FX

	ld	C,E		;possible output character to C
	ld	hl,functab
DISPATCH:
	ld	E,A		;DE=func, HL=.ciotab
	ld	D,0

	add	hl,de		;DE=functab(func)
	add	hl,de
	ld	E,(hl)
	inc	hl
	ld	D,(hl)

	ld	hl,(info)	;info in DE for later xchg
	ex	de,hl		;dispatched
	jp	(hl)
subttl	Imbedded copyright message

	defb	'COPYRIGHT (C) 1984, '
	defb	'DIGITAL RESEARCH '
	defb	'042384'
subttl

HIGH$FX:
	cp	45
	jp	z,FUNC45

	cp	48
	jp	z,FUNC48

	cp	124
	jp	z,func124

	cp	125
	jp	z,func125

	ld	hl,XFUNCTAB
	sub	109		; RETURN IF FX < 109
	ret	c

	cp	XNFUNCS		; RETURN IF FX >= 109 + XNFUNCS
	ret	nc

	jr	DISPATCH

subttl	Cold & Warm Start Initialization
?bdosc:				;COLD START INITIALIZATION ROUTINE
	call	FUNC13
	ld	C,CS$INIT$SIZE
	jr	WS$INIT0

?bdosw:				;WARM START INITIALIZATION ROUTINE
	ld	C,WS$INIT$SIZE
WS$INIT0:
	xor	A
	ld	hl,COLUMN

WS$INIT1:
	ld	(hl),A
	inc	hl
	dec	C
	jr	nz,WS$INIT1

	if	data_low	
	ld	bc,front_size	;move an image of the front of the BDOS code
	ld	de,bdos$dt	;segment to the front of the data segment for
	ld	hl,bdos$cd	;systems with data lower in RAM
	ldir
	endif

	ld	A,'$'
	ld	(OUT$DELIM),a

	call	SCAN$DRIVE
	jp	NOSELECT1
subttl	Dispatch Table for Functions
functab:
	defw	wbootf,func1,func2,func3
	defw	punchf,listf,func6,func7
	defw	func8,func9,func10,func11
diskf	equ	($-functab)/2	;disk funcs
	defw	func12,func13,func14,func15
	defw	func16,func17,func18,func19
	defw	func20,func21,func22,func23
	defw	func24,func25,func26,func27
 defw	func28,func29,func30,func31
	defw	func32,func33,func34,func35
	defw	func36,func37,func38,func39
	defw	func40
nfuncs	equ	($-functab)/2

XFUNCTAB:
	defw	FUNC109,FUNC110,FUNC111,FUNC112,FUNC113
XNFUNCS	equ	($-XFUNCTAB)/2
subttl Error Subroutine

ERROR:	; C = ERROR #, A = FF => RETURN & DISPLAY MODE
	push	af
	push	bc
	call	CPMERR
	pop	bc
	pop	af
	inc	A
	ret	z

	dec	C
	call	z,RESET$DRIVE
	call	CONINF
	jp	WBOOTF
subttl	Console Handlers

conin:
;read console character to A
	ld	hl,kbchar
	ld	A,(hl)
	ld	(hl),0
	or	A
	ret	nz

				;no previous keyboard character ready
	jp	coninf		;get character externally
	;ret

conech:				;read character with echo
	call	conin
	call	echoc
	jr	c,CONECH1

;character must be echoed before return
	push	af
	ld	C,A
	call	tabout
	pop	af
	ret			;with character in A

CONECH1:
	cp	CTLS
	ret	nz

	call	CONBS
	jr	CONECH

echoc:
;echo character if graphic
;cr, lf, tab, or backspace
	cp	cr
	ret	z		;carriage return?

	cp	lf
	ret	z		;line feed?

	cp	tab
	ret	z		;tab?

	cp	ctlh
	ret	z		;backspace?

	cp	' '
	ret			;carry set if not graphic

CONBRKX:
	ld	a,(KBCHAR)
	or	A
	jr	nz,CONB1

CONBRKX1:
	call	CONSTF
	and	1
	ret

conbrk:				;check for character ready
	ld	a,(KBCHAR)
	or	A
	jr	nz,CONB1

CONBRK1:
	call	CONBRKX1
	ret	z		; RETURN IF CHARACTER NOT READY

;character ready, read it
	call	coninf		;to A
	cp	ctls
	jr	nz,conb0	;check stop screen function

CONBS:
;found ctls, read next character
	call	coninf		;to A
	cp	ctlc
	jp	z,reboot	;ctlc implies re-boot

;not a reboot, act as if nothing has happened
	xor	A
	ret			;with zero in accumulator

conb0:
;character in accum, save it
	ld	(kbchar),a
conb1:
;return with true set in accumulator
	ld	A,-1
	ret

conout:
;compute character position/write console char from C
;compcol = true if computing column position
	ld	a,(compcol)
	or	A
	jr	nz,compout

;write the character, then compute the column
;write console character from C
	push	bc
	call	CONBRK1		;check for screen stop function
	pop	bc
	push	bc		;recall/save character
	call	conoutf		;externally, to console
				;may be copying to the list device
	ld	a,(listcp)
	or	A
	call	nz,listf	;to printer, if so
	pop	bc		;recall the character
compout:
	ld	A,C		;recall the character
				;and compute column position
	ld	hl,column	;A = char, HL = .column
	cp	rubout
	ret	z		;no column change if nulls

	inc	(hl)		;column = column + 1
	cp	' '
	ret	nc		;return if graphic

;not graphic, reset column position
	dec	(hl)		;column = column - 1
	ld	A,(hl)
	or	A
	ret	z		;return if at zero

;not at zero, may be backspace or end line
	ld	A,C		;character back to A
	cp	ctlh
	jr	nz,notbacksp

;backspace character
	dec	(hl)		;column = column - 1
	ret

notbacksp:
;not a backspace character, eol?
	cp	lf
	ret	nz		;return if not

;end of line, column = 0
	ld	(hl),0		;column = 0
	ret

ctlout:
;send C character with possible preceding up-arrow
	ld	A,C
	call	echoc		;cy if not graphic (or special case)
	jr	nc,tabout	;skip if graphic, tab, cr, lf, or ctlh

;send preceding up arrow
	push	af
	ld	C,ctl
	call	conout		;up arrow
	pop	af
	or	40h		;becomes graphic letter
	ld	C,A		;ready to print
	;(drop through to tabout)

tabout:
;expand tabs to console
	ld	a,(FX)
	dec	A
	jr	z,TABOUT1

	ld	a,(CONMODE)
	and	10H
	jp	nz,CONOUTF

TABOUT1:
	ld	A,C
	cp	tab
	jr	nz,conout	;direct to conout if not

;tab encountered, move to next tab position
tab0:
	ld	C,' '
	call	conout		;another blank
	ld	a,(column)
	and	111b		;column mod 8 = 0 ?
	jr	nz,tab0		;back for another if not

	ret

backup:
;back-up one screen position
	call	pctlh
	ld	C,' '
	call	conoutf
	;(drop through to pctlh)

pctlh:
;send ctlh to console without affecting column count
	ld	C,ctlh
	jp	conoutf
	;ret

crlfp:
;print #, cr, lf for ctlx, ctlu, ctlr functions
;then move to strtcol (starting column)
	ld	C,'#'
	call	conout
	call	crlf

;column = 0, move to position strtcol
crlfp0:
	ld	a,(column)
	ld	hl,strtcol
	cp	(hl)
	ret	nc		;stop when column reaches strtcol

	ld	C,' '
	call	conout		;print blank
	jr	crlfp0

crlf:
;carriage return line feed sequence
	ld	C,cr
	call	conout
	ld	C,lf
	jp	conout
	;ret

print:
;print message until M(BC) = '$'
	ld	hl,OUT$DELIM
	ld	a,(bc)
	cp	(hl)
	ret	z		;stop on $

;more to print
	inc	bc
	push	bc
	ld	C,A		;char to C
	call	tabout		;another character printed
	pop	bc
	jr	print

read:	;read to info address (max length, current length, buffer)
	ld	A,1
	ld	(FX),a
	ld	a,(column)
	ld	(strtcol),a	;save start for ctl-x, ctl-h
	ld	hl,(info)
	ld	C,(hl)
	inc	hl
	push	hl
	xor	A
	ld	B,A
	ld	(SAVE$POS),a

;B = current buffer length,
;C = maximum buffer length,
;HL= next to fill - 1
readnx:
;read next character, BC, HL active
	push	bc
	push	hl		;blen, cmax, HL saved
readn0:
	call	conin		;next char in A
	pop	hl
	pop	bc		;reactivate counters
	cp	cr
	jp	z,readen	;end of line?

	cp	lf
	jp	z,readen	;also end of line

	cp	ctlh
	jr	nz,noth	;backspace?

;do we have any characters to back over?
	ld	a,(STRTCOL)
	ld	D,A
	ld	a,(COLUMN)
	cp	D
	jr	z,readnx

	ld	(COMPCOL),a	;COL>0
;characters remain in buffer, can we backup one
	ld	a,b		;check character count	SCC	22 Apr 84
	or	a		;			SCC	22 Apr 84
	jr	z,linelen	;already 0, don't decr	SCC	22 Apr 84

	dec	B		;remove one character
;compcol > 0 marks repeat as length compute
	jr	linelen		;uses same code as repeat
noth:
;not a backspace
	cp	rubout
	jr	nz,notrub	;rubout char?

;rubout encountered, rubout if possible
	ld	A,B
	or	A
	jr	z,readnx	;skip if len=0

;buffer has characters, resend last char
	ld	A,(hl)
	dec	B
	dec	hl		;A = last char
;blen=blen-1, next to fill - 1 decremented
	jp	rdech1		;act like this is an echo

notrub:
;not a rubout character, check end line
	cp	ctle
	jr	nz,note		;physical end line?

;yes, save active counters and force eol
	push	bc
	ld	A,B
	ld	(SAVE$POS),a
	push	hl
	call	crlf
	xor	A
	ld	(strtcol),a	;start position = 00
	jr	readn0		;for another character

note:
;not end of line, list toggle?
	cp	ctlp
	jr	nz,notp		;skip if not ctlp

;list toggle - change parity
	push	hl		;save next to fill - 1
	ld	hl,listcp	;HL=.listcp flag
	ld	A,1
	sub	(hl)		;True-listcp
	ld	(hl),A		;listcp = not listcp
	pop	hl
	jr	readnx		;for another char

notp:
;not a ctlp, line delete?
	cp	ctlx
	jr	nz,notx

	pop	hl		;discard start position
;loop while column > strtcol
backx:
	ld	a,(strtcol)
	ld	hl,column
	cp	(hl)
	jr	nc,read		;start again

	dec	(hl)		;column = column - 1
	call	backup		;one position
	jr	backx

notx:
;not control-X, control-U?
	cp	ctlu
	jr	nz,notu		;skip if not

;delete line (ctlu)
	call	crlfp		;physical eol
	pop	hl		;discard starting position
	jp	read		;to start all over

notu:
;not line delete, repeat line?
	cp	ctlr
	jr	nz,notr

	xor	A
	ld	(SAVE$POS),a
linelen:
;repeat line, or compute line len (ctlh)
;if compcol > 0
	push	bc
	call	crlfp		;save line length
	pop	bc
	pop	hl
	push	hl
	push	bc
;bcur, cmax active, beginning buff at HL
rep0:
	ld	A,B
	or	A
	jr	z,rep1		;count len to 00

	inc	hl
	ld	C,(hl)		;next to print
	dec	B
	pop	de
	push	de
	ld	A,D
	sub	B
	ld	D,A
	push	bc
	push	hl		;count length down
	ld	a,(save$pos)
	cp	D
	call	c,CTLOUT	;character echoed
	pop	hl
	pop	bc		;recall remaining count
	jr	rep0		;for the next character

rep1:
;end of repeat, recall lengths
;original BC still remains pushed
	push	hl		;save next to fill
	ld	a,(compcol)
	or	A		;>0 if computing length
	jp	z,readn0	;for another char if so

;column position computed for ctlh
	ld	hl,column
	sub	(hl)		;diff > 0
	ld	(compcol),a	;count down below
;move back compcol-column spaces
backsp:
;move back one more space
	call	backup		;one space
	ld	hl,compcol
	dec	(hl)
	jr	nz,backsp

	jp	readn0		;for next character

notr:
;not a ctlr, place into buffer
rdecho:
	inc	hl
	ld	(hl),A		;character filled to mem
	inc	B		;blen = blen + 1
rdech1:
;look for a random control character
	push	bc
	push	hl		;active values saved
	ld	C,A		;ready to print
	call	ctlout		;may be up-arrow C
	pop	hl
	pop	bc
	ld	A,(hl)		;recall char
	cp	ctlc		;set flags for reboot test
	ld	A,B		;move length to A
	jr	nz,notc		;skip if not a control c

	cp	1		;control C, must be length 1
	jp	z,reboot	;reboot if blen = 1

				;length not one, so skip reboot
notc:
				;not reboot, are we at end of buffer?
	cp	C
	jp	c,readnx	;go for another if not

readen:
;end of read operation, store blen
	pop	hl
	ld	(hl),B		;M(current len) = B
	ld	C,cr
	jp	conout		;return carriage
	;ret

subttl	Character I/O Functions
func1:
;return console character with echo
	call	conech
	jr	sta$ret

func2	equ	tabout
;write console character with tab expansion

func3:
;return reader character
	call	readerf
	jr	sta$ret

;func4:	equated to punchf
;write punch character

;func5:	equated to listf
;write list character
;write to list device

func6:
;direct console i/o - read if 0FFh
	ld	A,C
	inc	A
	jr	z,dirinp	;0FFh => 00h, means input mode

	inc	A
	jp	nz,CONOUTF	;DIRECT OUTPUT FUNCTION

;0FEh => STATUS
	call	CONBRKX
	ret	z

	jp	LRET$EQ$FF
  
dirinp:
	call	CONBRKX		;status check
	ret	z		;skip, return 00 if not ready

;character is ready, get it
	call	CONIN		;to A
	jr	sta$ret

FUNC7:	;READER STATUS
	call	?auxis
	jr	STA$RET

FUNC8:	;PUNCH STATUS
	call	?auxos
	jr	STA$RET

func9:
;write line until $ encountered
	ex	de,hl		;was lhld info
	ld	C,L
	ld	B,H		;BC=string address
	jp	print		;out to console

func10	equ	read
;read a buffered console line

func11:
;check console status
	call	conbrk
	;(drop through to sta$ret)

sta$ret:
;store the A register to aret
	ld	(aret),a
func$ret:
	ret			;jmp goback (pop stack for non cp/m functions)

setlret1:
;set lret = 1
	ld	A,1
	jr	sta$ret
subttl	CP/M-Plus Function 
FUNC109:	;GET/SET CONSOLE MODE

; CONMODE BITS = 7 6 5 4 3 2 1 0
; DEFINED BITS =       4
;
; BIT 4 = 0:	Normal BDOS operation
;	  1:	Supress BDOS expansion of tabs, ^P and ^S handling on
;		console output

	ld	hl,CONMODE
TEST$SET:
	ld	A,D
	and	E
	inc	A
	ld	A,(hl)
	jr	z,STA$RET

	ld	(hl),E
	ret

FUNC110:	;GET/SET OUTPUT DELIMITER
	ld	hl,OUT$DELIM
	jr	TEST$SET

FUNC111:	;PRINT BLOCK TO CONSOLE

FUNC112:	;PRINT BLOCK TO LIST
;
	ex	de,hl
	ld	E,(hl)
	inc	hl
	ld	D,(hl)
	inc	hl
	ld	C,(hl)
	inc	hl
	ld	B,(hl)
	ex	de,hl
;HL = ADDR OF STRING
;BC = LENGTH OF STRING
BLK$OUT:
	ld	A,B
	or	C
	ret	z

	push	bc
	push	hl
	ld	C,(hl)
	call	BLK$OUT1
	pop	hl
	inc	hl
	pop	bc
	dec	bc
	jr	BLK$OUT

BLK$OUT1:
	ld	a,(FX)
	rra
	jp	c,TABOUT

	jp	LISTF
subttl 	New Personal CP/M Functions
FUNC113:	;PERFORM SCREEN FUNCTION
	call	?dscrf
	jp	sthl$ret

func124:			;Byte BLT copy
	call	?bytbc
	jr	sta$ret

func125:			;Byte BLT alter
	call	?bytba
	jr	sta$ret

;
;	end of Basic I/O System

subttl	BDOS Disk functions

;*****************************************************************
;*****************************************************************
;**                                                             **
;**   B a s i c    D i s k   O p e r a t i n g   S y s t e m    **
;**                                                             **
;*****************************************************************
;*****************************************************************

dvers	equ	28h	;Personal CP/M 1.0

;
;	module addresses
;

;;;	literal constants

true	equ	0ffh	;constant true
false	equ	000h	;constant false
enddir	equ	0ffffh	;end of directory
byte	equ	1	;number of bytes for "byte" type
word	equ	2	;number of bytes for "word" type

;
;	fixed addresses in low memory
;

tfcb	equ	005ch	;default fcb location
tbuff	equ	0080h	;default buffer location
subttl
;
;	error message handlers
;

rod$error:
;report read/only disk error
	ld	C,2
	jr	GOERR

rof$error:
;report read/only file error
	ld	C,3
	jr	GOERR

sel$error:
	call	RESET$DRIVE
;report select error
	ld	C,4

goerr:
	ld	H,C
	ld	L,0FFH
	ld	(ARET),hl

GOERR1:
	ld	a,(ERRMODE)
	inc	A
	call	nz,ERROR

	ld	A,0FFH
	ld	(CURDSK),a

	ld	a,(FX)
	cp	27
	jp	z,GOBACK0

	cp	31
	jp	z,GOBACK0

	jp	GOBACK
subttl	Local Subroutines for Bios Interface
MOVE:
	ld	B,0	;move number of bytes in C
;
MOVEX:
	call	?mov
	ret
subttl	Select Disk
selectdisk:

;select the disk drive given by register D, and fill
;the base addresses curtrka - alloca, then fill
;the values of the disk parameter block

	ld	C,D		;current disk# to c
	ld	hl,LSN$NS
	ld	B,0
	add	hl,bc
	ld	(LSN$ADD),hl
;lsb of e = 0 if not yet logged - in
	call	seldskf		;HL filled by call
;HL = 0000 if error, otherwise disk headers
	ld	A,H
	or	L
	ret	z		;return with 0000 in HL and z flag

;disk header block address in hl
	ld	E,(hl)
	inc	hl
	ld	D,(hl)
	inc	hl		;DE=.tran
	ld	(cdrmaxa),hl
	inc	hl
	inc	hl		;.cdrmax
	ld	(curtrka),hl
	inc	hl
	inc	hl		;HL=.currec
	ld	(curreca),hl
	inc	hl
	inc	hl		;HL=.buffa

;DE still contains .tran

	ex	de,hl
	ld	(tranv),hl	;.tran vector
	ld	hl,buffa	;DE= source for move, HL=dest
	ld	C,addlist
	call	move		;addlist filled

;now fill the disk parameter block

	ld	de,(dpbaddr)	;DE is source
	ld	hl,sectpt	;HL is destination
	ld	C,dpblist
	call	move		;data filled

;now set single/double map mode

	ld	hl,(maxall)	;largest allocation number
	ld	A,H		;00 indicates < 255
	ld	hl,single
	ld	(hl),true	;assume a=00
	or	A
	jr	z,retselect

;high order of maxall not zero, use double dm

	ld	(hl),false

retselect:

	scf
	ret			;select disk function ok
subttl	HOME - move to track 0, sector 0

home:

;move to home position, then offset to start of dir

	call	homef		;move to track 00, sector 00 reference

;lxi h,offset ;mov c,m ;inx h ;mov b,m ;call settrkf	;
;first directory position selected

	xor	A		;constant zero to accumulator
	ld	hl,(curtrka)
	ld	(hl),A
	inc	hl
	ld	(hl),A		;curtrk=0000
	ld	hl,(curreca)
	ld	(hl),A
	inc	hl
	ld	(hl),A		;currec=0000

;curtrk, currec both set to 0000

	ret
subttl	RDBUFF & WRBUFF - read & write disk buffers

rdbuff:
;read buffer and check condition
	ld	a,1
	call	readf		;current drive, track, sector, dma
	jr	diocomp		;check for i/o errors

wrbuff:
;write buffer and check condition
;write type (wrtype) is in register C
;wrtype = 0 => normal write operation
;wrtype = 1 => directory write operation
;wrtype = 2 => start of new block

	call	writef		;current drive, track, sector, dma

diocomp:	;check for disk errors

	or	A
	ret	z

	ld	C,A
	jp	GOERR
subttl	SEEK$DIR - seek the record containing the current dir entry

seek$dir:

	ld	hl,(dcnt)	;directory counter to HL
	ld	C,dskshf
	call	hlrotr		;value to HL
	ld	(arecord),hl	;ready for seek
	;jmp	seek
	;ret
subttl	SEEK - seek the track given by actual record

seek:
;seek the track given by arecord (actual record)
;load the registers from memory

	ld	hl,arecord
	ld	c,(hl)		;arecord
	inc	hl
	ld	b,(hl)
	ld	hl,(curreca)
	ld	e,(hl)		;currec
	inc	hl
	ld	d,(hl)
	ld	hl,(curtrka)
	ld	A,(hl)		;curtrk
	inc	hl
	ld	h,(hl)
	ld	l,A

;loop while arecord < currec
seek0:

	ld	A,c
	sub	e
	ld	A,b
	sbc	a,d
	jr	nc,seek1	;skip if arecord >= currec

;currec = currec - sectpt

	push	hl
	ld	hl,(sectpt)
	ld	A,e
	sub	L
	ld	e,A
	ld	A,d
	sbc	a,H
	ld	d,A
	pop	hl

;curtrk = curtrk - 1

	dec	hl
	jr	seek0		;for another try

seek1:
;look while arecord >= (t:=currec + sectpt)

	push	hl
	ld	hl,(sectpt)
	add	hl,de		;HL = currec+sectpt
	jr	c,seek2		;can be > FFFFH

	ld	A,c
	sub	l
	ld	A,b
	sbc	a,h
	jr	c,seek2		;skip if t > arecord

;currec = t

	ex	de,hl

;curtrk = curtrk + 1

	pop	hl
	inc	hl
	jr	seek1		;for another try

seek2:	pop	hl
;arrive here with updated values in each register

	push	bc
	push	de
	push	hl		;to stack for later

;stack contains (lowest) BC=arecord, DE=currec, HL=curtrk

	ex	de,hl
	ld	hl,(offset)
	add	hl,de		;HL = curtrk+offset
	ld	B,H
	ld	C,L
	call	settrkf		;track set up

;note that BC - curtrk is difference to move in bios

	pop	de		;recall curtrk
	ld	hl,(curtrka)
	ld	(hl),E
	inc	hl
	ld	(hl),D		;curtrk updated

;now compute sector as arecord-currec

	pop	de		;recall currec
	ld	hl,(curreca)
	ld	(hl),e
	inc	hl
	ld	(hl),d
	pop	bc		;BC=arecord, DE=currec
	ld	A,c
	sub	e
	ld	c,A
	ld	A,b
	sbc	a,d
	ld	b,A
	ld	hl,(tranv)
	ex	de,hl		;BC=sector#, DE=.tran
	call	sectran		;HL = tran(sector)
	ld	C,L
	ld	B,H		;BC = tran(sector)
	jp	setsecf		;sector selected
	;ret
subttl	FCB constants

;;	file control block (fcb) constants

empty	equ	0E5h	;empty directory entry
lstrec	equ	127	;last record# in extent
recsiz	equ	128	;record size
fcblen	equ	32	;file control block size
dirrec	equ	recsiz/fcblen	;directory elts / record
dskshf	equ	2	;log2(dirrec)
dskmsk	equ	dirrec-1
fcbshf	equ	5	;log2(fcblen)


extnum	equ	12	;extent number field
maxext	equ	31	;largest extent number
ubytes	equ	13	;unfilled bytes field
modnum	equ	14	;data module number
maxmod	equ	15	;largest module number
fwfmsk	equ	80h	;file write flag is high order modnum
namlen	equ	15	;name length
reccnt	equ	15	;record count field
dskmap	equ	16	;disk map field
lstfcb	equ	fcblen-1
nxtrec	equ	fcblen
ranrec	equ	nxtrec+1	;random record field (2 bytes)

;
;	reserved file indicators
;

rofile	equ	9	;high order of first type char
invis	equ	10	;invisible file in dir command
;	equ	11	;reserved
subttl	Utility functions for file access
dm$position:
;compute disk map position for vrecord to HL

	ld	hl,blkshf
	ld	C,(hl)		;shift count to C
	ld	a,(vrecord)	;current virtual record to A

dmpos0:

	or	A
	rra
	dec	C
	jr	nz,dmpos0

;A = shr(vrecord,blkshf) = vrecord/2**(sect/block)

	ld	B,A		;save it for later addition
	ld	A,8
	sub	(hl)		;8-blkshf to accumulator
	ld	C,A		;extent shift count in register c
	ld	a,(extval)	;extent value ani extmsk

dmpos1:
;blkshf = 3,4,5,6,7, C=5,4,3,2,1
;shift is 4,3,2,1,0

	dec	C
	jr	z,dmpos2

	or	A
	rla
	jr	dmpos1

dmpos2:
;arrive here with A = shl(ext and extmsk,7-blkshf)

	add	a,B		;add the previous shr(vrecord,blkshf) value

;A is one of the following values, depending upon alloc
;bks blkshf
;1k   3     v/8 + extval * 16
;2k   4     v/16+ extval * 8
;4k   5     v/32+ extval * 4
;8k   6     v/64+ extval * 2
;16k  7     v/128+extval * 1

	ret			;with dm$position in A
subttl	GETDM - return disk map value from position given by BC

getdm:

	ld	hl,(info)	;base address of file control block
	ld	de,dskmap
	add	hl,de		;HL =.diskmap
	add	hl,bc		;index by a single byte value
	ld	a,(single)	;single byte/map entry?
	or	A
	jr	z,getdmd	;get disk map single byte

	ld	L,(hl)
	ld	H,0
	ret			;with HL=00bb

getdmd:

	add	hl,bc		;HL=.fcb(dm+i*2)
				;double precision value returned
	ld	E,(hl)
	inc	hl
	ld	D,(hl)
	ex	de,hl
	ret
subttl	INDEX - compute disk block number from current FCB

index:
	call	dm$position	;0...15 in register A
	ld	C,A
	ld	B,0
	call	getdm		;value to HL
	ld	(arecord),hl
	ld	A,L
	or	H
	ret
subttl	ATRAN - compute actual record address, assuming index called

atran:
	ld	a,(blkshf)	;shift count to reg A
	ld	hl,(arecord)

atran0:

	add	hl,hl
	dec	A
	jr	nz,atran0	;shl(arecord,blkshf)

	ld	(arecord1),hl	;save shifted block #
	ld	a,(blkmsk)
	ld	C,A		;mask value to C
	ld	a,(vrecord)
	and	C		;masked value in A
	or	L
	ld	L,A		;to HL
	ld	(arecord),hl	;arecord=HL or (vrecord and blkmsk)
	ret
subttl	GETEXTA - get current extent field address

getexta:
	ld	hl,(info)
	ld	de,extnum
	add	hl,de		;HL=.fcb(extnum)
	ret
subttl	GETFCBA - compute RECCNT and NXTREC addresses for GET/SETFCB

getfcba:
	ld	hl,(info)
	ld	de,reccnt
	add	hl,de
	ex	de,hl		;DE=.fcb(reccnt)
	ld	hl,nxtrec-reccnt
	add	hl,de		;HL=.fcb(nxtrec)
	ret
subttl	GETFCB - set variables from currently addressed FCB

getfcb:
	call	getfcba		;addresses in DE, HL
	ld	A,(hl)
	ld	(vrecord),a	;vrecord=fcb(nxtrec)
	ex	de,hl
	ld	A,(hl)
	ld	(rcount),a	;rcount=fcb(reccnt)
	call	getexta		;HL=.fcb(extnum)
	ld	a,(extmsk)	;extent mask to a
	and	(hl)		;fcb(extnum) and extmsk
	ld	(extval),a
	ret
subttl	SETFCB - place values back into current FCB

setfcb:
	call	getfcba		;addresses to DE, HL
	ld	a,(VRECORD)
	ld	(hl),A
	ld	a,(FX)
	cp	22
	jr	nc,setfcb_1

	inc	(hl)

setfcb_1:

	ex	de,hl
	ld	a,(rcount)
	ld	(hl),A		;fcb(reccnt)=rcount
	ret
subttl	HLROTR -  HL rotated right by amount C

hlrotr:
	inc	C		;in case zero
hlrotr0:
	dec	C
	ret	z		;return when zero

	srl	h		;SCC - operation performed was actually a
	rr	l		;'shift right logical' of HL
	jr	hlrotr0
subttl	HLROTL - HL rotated left by amount C

hlrotl:
	inc	C		;may be zero
hlrotl0:
	dec	C
	ret	z		;return if zero

	add	hl,hl
	jr	hlrotl0
subttl

SCAN$DRIVE:

	ld	hl,(DLOG)

SD$0:

	ld	A,16

SD$1:

	dec	A
	add	hl,hl
	jr	nc,SD$4

	push	af
	push	hl
	ld	E,A
	ld	a,(SCAN$FLAG)
	inc	A
	jr	z,SD$2

	call	TMPSELECT
	or	1
	call	COPY$ALV
	call	SET$DIR$BLKS
	jr	SD$3

SD$2:

	ld	C,E
	call	?discd

SD$3:

	pop	hl
	pop	af

SD$4:
	or	A
	jr	nz,SD$1

	ret

SET$DLOG:

	ld	de,DLOG

set$cdisk:

	ld	a,(CURDSK)

SET$CDISK1:

	ld	C,A		;ready parameter for shift
	ld	hl,1		;number to shift
	call	hlrotl		;HL = mask to integrate
	ld	a,(de)
	or	L
	ld	(de),a
	inc	de
	ld	a,(de)
	or	H
	ld	(de),a
	ret

nowrite:
;return true if dir checksum difference occurred

	ld	hl,(rodsk)

TEST$VECTOR:

	ld	a,(curdsk)
	ld	C,A
	call	hlrotr
	ld	A,L
	and	1b
	ret			;non zero if nowrite

TST$LOG$FXS:

	ld	hl,LOG$FXS

TST$LOG0:

	ld	a,(FX)
	ld	B,A

TST$LOG1:

	ld	A,(hl)
	cp	B
	ret	z

	inc	hl
	or	A
	jr	nz,TST$LOG1

	inc	A
	ret

TST$RELOG:

	ld	hl,RELOG
	ld	A,(hl)
	or	A
	ret	z

	ld	(hl),0
	call	CURSELECT
	ld	hl,0
	ld	(DCNT),hl
	xor	A
	ld	(DPTR),a
	ret

CHK$EXIT$FXS:

	ld	hl,GOBACK
	push	hl
	ld	hl,RW$FXS
	call	TST$LOG0
	jr	z,CHK$MEDIA2

	ld	hl,SC$FXS
	call	TST$LOG0
	jp	z,LRET$EQ$FF

	pop	hl
	ret

SET$LSN:

	ld	hl,(LSN$ADD)
	ld	C,(hl)
	call	GETEXTA
	inc	hl
	ld	(hl),C
	ret

SET$RLOG:

	ld	hl,(OLOG)
	call	TEST$VECTOR
	ret	z

	ld	de,RLOG
	jr	SET$CDISK

CHECK$FCB:

	call	GETEXTA
	inc	hl
	ld	A,(hl)
	ld	hl,(LSN$ADD)
	cp	(hl)
	call	nz,CHK$MEDIA1
	call	GETMODNUM
	and	40H
	ret	z

	ld	hl,(INFO)
	ld	(hl),0
	ret

CHK$MEDIA1:

	ld	hl,(RLOG)
	call	TEST$VECTOR
	ret	z

	pop	hl
	pop	hl

CHK$MEDIA2:

	ld	A,10
	jp	STA$RET

set$ro:
;set current disk to read only

	ld	de,RODSK
	ld	a,(SELDSK)
	call	SET$CDISK1

;high water mark in directory goes to max

	ld	hl,(dirmax)
	inc	hl
	ex	de,hl		;DE = directory max
	ld	hl,(cdrmaxa)	;HL = .cdrmax
	ld	(hl),E
	inc	hl
	ld	(hl),D		;cdrmax = dirmax
	ret

check$rodir:
;check current directory element for read/only status

	call	getdptra	;address of element

check$rofile:
;check current buff(dptr) or fcb(0) for r/o status

	ld	de,rofile
	add	hl,de		;offset to ro bit
	ld	A,(hl)
	rla
	ret	nc		;return if not set
	jp	rof$error

check$write:
;check for write protected disk

	call	nowrite
	ret	z		;ok to write if not rodsk

	jp	rod$error	;read only disk error

getdptra:
;compute the address of a directory element at
;positon dptr in the buffer

	ld	hl,(buffa)
	ld	a,(dptr)

addh:
;HL = HL + A

	add	a,L
	ld	L,A
	ret	nc

;overflow to H

	inc	H
	ret

getmodnum:
;compute the address of the module number
;bring module number to accumulator
;(high order bit is fwf (file write flag)

	ld	hl,(info)
	ld	de,modnum
	add	hl,de		;HL=.fcb(modnum)
	ld	A,(hl)
	ret			;A=fcb(modnum)

clrmodnum:
;clear the module number field for user open/make

	call	getmodnum
	ld	(hl),0		;fcb(modnum)=0
	ret

setfwf:

	call	getmodnum	;HL=.fcb(modnum), A=fcb(modnum)

;set fwf (file write flag) to "1"

	or	fwfmsk
	ld	(hl),A	;fcb(modnum)=fcb(modnum) or 80h

;also returns non zero in accumulator

	ret

compcdr:
;return cy if cdrmax > dcnt

	ld	de,(dcnt)	;DE = directory counter
	ld	hl,(cdrmaxa)	;HL=.cdrmax
	ld	A,E
	sub	(hl)	;low(dcnt) - low(cdrmax)
	inc	hl	;HL = .cdrmax+1
	ld	A,D
	sbc	a,(hl)	;hig(dcnt) - hig(cdrmax)

;condition dcnt - cdrmax  produces cy if cdrmax>dcnt

	ret

setcdr:
;if not (cdrmax > dcnt) then cdrmax = dcnt+1

	call	compcdr
	ret	c		;return if cdrmax > dcnt

;otherwise, HL = .cdrmax+1, DE = dcnt

	inc	de
	ld	(hl),D
	dec	hl
	ld	(hl),E
	ret

subdh:
;compute HL = DE - HL

	ld	A,E
	sub	L
	ld	L,A
	ld	A,D
	sbc	a,H
	ld	H,A
	ret

newchecksum:

	ld	C,0FEH		;drop through to compute new checksum

checksum:
;compute current checksum record and update the
;directory element if C=true, or check for = if not
;ARECORD < chksiz?

	ld	de,(ARECORD)
	ld	hl,(chksiz)
	call	subdh		;DE-HL
	ret	nc		;skip checksum if past checksum vector size

;ARECORD < chksiz, so continue

	push	bc		;save init flag

;COMPUTE CHECKSUM FOR CURRENT DIRECTORY BUFFER

	ld	C,RECSIZ	;SIZE OF DIRECTORY BUFFER
	ld	hl,(BUFFA)	;CURRENT DIRECTORY BUFFER
	xor	A		;CLEAR CHECKSUM VALUE

COMPUTECS0:

	add	a,(hl)
	inc	hl
	dec	C		;CS=CS+BUFF(RECSIZ-C)
	jr	nz,COMPUTECS0

	ld	de,(checka)	;address of check sum vector
	ld	hl,(ARECORD)
	add	hl,de		;HL = .check(ARECORD)
	pop	bc		;recall true=0ffh or false=00 to C
	inc	C		;0ffh produces zero flag
	jr	z,initial$cs
	inc	C
	jr	z,UPDATE$CS

;not initializing, compare

	cp	(hl)		;compute$cs=check(ARECORD)?
	ret	z		;no message if ok
	call	NOWRITE
	ret	nz
	ld	A,0FFH
	ld	(RELOG),a
	call	set$rlog

RESET$DRIVE:

	call	set$dlog
	jp	RESET37X

initial$cs:

	cp	(hl)
	ld	(hl),A
	ret	z
	ld	hl,(LSN$ADD)
	ld	A,1
	or	(hl)

UPDATE$CS:
;initializing the checksum

	ld	(hl),A
	ret

wrdir:
;write the current directory entry, set checksum

	call	newchecksum	;initialize entry
	call	setdir		;directory dma
	ld	C,1		;indicates a write directory operation
	call	wrbuff		;write the buffer
	jr	setdata		;to data dma address
	;ret

rd$dir:
;read a directory entry into the directory buffer

	call	seek$dir
	call	setdir		;directory dma
	call	rdbuff		;directory record loaded
	; jmp setdata to data dma address
	;ret

setdata:
;set data dma address

	ld	hl,dmaad
	jr	setdma		;to complete the call

setdir:
;set directory dma address

	ld	hl,buffa	;jmp setdma to complete call

setdma:
;HL=.dma address to set (i.e., buffa or dmaad)

	ld	C,(hl)
	inc	hl
	ld	B,(hl)		;parameter ready
	jp	setdmaf

dir$to$user:
;copy the directory entry to the user buffer
;after call to search or searchn by user code

	ld	de,(buffa)		;source is directory buffer
	ld	hl,(dmaad)		;destination is user dma address
	ld	C,recsiz		;copy entire record
	call	MOVE
	ld	hl,LRET
	ld	A,(hl)
	inc	A
	ret	z
	ld	a,(DCNT)
	and	DSKMSK
	ld	(hl),A
	ret

end$of$dir:
;return zero flag if at end of directory, non zero
;if not at end (end of dir if dcnt = 0ffffh)

	ld	hl,(DCNT)
	ld	A,L
	and	H
	inc	A
	ret

set$end$dir:
;set dcnt to the end of the directory

	ld	hl,enddir
	ld	(dcnt),hl
	ret

read$dir:
;read next directory entry, with C=true if initializing

	ld	de,(dirmax)	;in preparation for subtract
	ld	hl,(dcnt)
	inc	hl
	ld	(dcnt),hl	;dcnt=dcnt+1

;continue while dirmax >= dcnt (dirmax-dcnt no cy)

	call	subdh		;DE-HL
	jr	nc,read$dir0

;yes, set dcnt to end of directory

	jr	set$end$dir	
	;ret

read$dir0:
;not at end of directory, seek next element
;initialization flag is in C

	ld	a,(dcnt)
	and	dskmsk		;low(dcnt) and dskmsk
	ld	B,fcbshf	;to multiply by fcb size

read$dir1:

	add	a,A
	dec	B
	jr	nz,read$dir1

;A = (low(dcnt) and dskmsk) shl fcbshf

	ld	(dptr),a	;ready for next dir operation
	or	A
	ret	nz		;return if not a new record
	push	bc		;save initialization flag C
	call	rd$dir		;read the directory record
	pop	bc		;recall initialization flag
	call	CHECKSUM
	ld	a,(RELOG)
	or	A
	ret	z
	call	CHK$EXIT$FXS
	call	TST$RELOG
	jr	RD$DIR
	;ret

getallocbit:
;given allocation vector position BC, return with byte
;containing BC shifted so that the least significant
;bit is in the low order accumulator position.  HL is
;the address of the byte for possible replacement in
;memory upon return, and D contains the number of shifts
;required to place the returned value back into position

	ld	A,C
	and	111b
	inc	A
	ld	E,A
	ld	D,A

;d and e both contain the number of bit positions to shift

	ld	H,B
	ld	L,C
	ld	C,3
	call	HLROTR
	ld	B,H
	ld	C,L

	ld	hl,(alloca)	;base address of allocation vector
	add	hl,bc
	ld	A,(hl)		;byte to A, hl = .alloc(BC shr 3)

;now move the bit to the low order position of A

rotl:	rlca
	dec	E
	jr	nz,rotl
	ret

set$alloc$bit:
;BC is the bit position of ALLOC to set or reset.  The
;value of the bit is in register E.

	push	de
	call	getallocbit	;shifted val A, count in D
	and	11111110b	;mask low bit to zero (may be set)
	pop	bc
	or	C		;low bit of C is masked into A
	;jmp rotr 		;to rotate back into proper position
	;ret

rotr:
;byte value from ALLOC is in register A, with shift count
;in register C (to place bit back into position), and
;target ALLOC position in registers HL, rotate and replace

	rrca
	dec	D
	jr	nz,rotr		;back into position
	ld	(hl),A		;back to ALLOC
	ret

COPY$ALV:
;IF Z FLAG SET, COPY 1ST ALV TO 2ND ALV
;OTHERWISE, COPY 2ND ALV TO 1ST ALV

	push	af
	call	GET$NALBS
	ld	B,H
	ld	C,L
	ld	hl,(ALLOCA)
	ld	D,H
	ld	E,L
	add	hl,bc
	pop	af
	jp	z,MOVEX
	ex	de,hl
	jp	MOVEX

SCANDM$AB:

	push	bc
 	call	SCANDM$A
 	pop	bc
	;JMP SCANDM$B

SCANDM$B:
;SET/RESET 2ND ALV

	push	bc
	call	GET$NALBS
	ex	de,hl
	ld	hl,(ALLOCA)
	pop	bc
	push	hl
	add	hl,de
	ld	(ALLOCA),hl
	call	SCANDM$A
	pop	hl
	ld	(ALLOCA),hl
	ret

SCANDM$A:
;SET/RESET 1ST ALLOCATION VECTOR
;scan the disk map addressed by dptr for non-zero
;entries, the allocation vector entry corresponding
;to a non-zero entry is set to the value of C (0,1)

	call	getdptra	;HL = buffa + dptr

;HL addresses the beginning of the directory entry

	ld	de,dskmap
	add	hl,de		;hl now addresses the disk map
	push	bc		;save the 0/1 bit to set
	ld	C,fcblen-dskmap+1	;size of single byte disk map + 1

scandm0:
;loop once for each disk map entry

	pop	de		;recall bit parity
	dec	C
	ret	z		;all done scanning?

				;no, get next entry for scan
	push	de		;replace bit parity
	ld	a,(single)
	or	A
	jr	z,scandm1

;single byte scan operation

	push	bc		;save counter
	push	hl		;save map address
	ld	C,(hl)
	ld	B,0		;BC=block#
	jr	scandm2

scandm1:
;double byte scan operation

	dec	C		;count for double byte
	push	bc		;save counter
	ld	C,(hl)
	inc	hl
	ld	B,(hl)		;BC=block#
	push	hl		;save map address

scandm2:
;arrive here with BC=block#, E=0/1

	ld	A,C
	or	B		;skip if = 0000
	jr	z,scanm3
	ld	hl,(maxall)	;check invalid index
	ld	A,L
	sub	C
	ld	A,H
	sbc	a,B		;maxall - block#
	call	nc,set$alloc$bit	

;bit set to 0/1
scanm3:	

	pop	hl
	inc	hl		;to next bit position
	pop	bc		;recall counter
	jr	scandm0		;for another item

GET$NALBS:	;GET # OF ALLOCATION VECTOR BYTES

	ld	hl,(MAXALL)
	ld	C,3
	call	HLROTR
	inc	hl
	ret

SET$DIR$BLKS:

	ld	de,(DIRBLK)
	ld	hl,(ALLOCA)
	ld	A,(hl)
	or	E
	ld	(hl),A
	inc	hl
	ld	A,(hl)
	or	D
	ld	(hl),A
	ret

initialize:
;initialize the current disk
;lret = false ;set to true if $ file exists
;compute the length of the allocation vector
;number of bytes in alloc vector is (maxall/8)+1

	call	GET$NALBS
	ld	B,H
	ld	C,L		;count down BC til zero
	ld	hl,(alloca)	;base of allocation vector

;fill the allocation vector with zeros
initial0:

	ld	(hl),0
	inc	hl		;alloc(i)=0
	dec	bc		;count length down
	ld	A,B
	or	C
	jr	nz,initial0

;set the reserved space for the directory

	call	SET$DIR$BLKS

;allocation vector initialized, home disk

	call	home

;cdrmax = 3 (scans at least one directory record)

	ld	hl,(cdrmaxa)
	ld	(hl),3
	inc	hl
	ld	(hl),0

;cdrmax = 0000

	call	set$end$dir	;dcnt = enddir

;read directory entries and check for allocated storage
initial2:

	ld	C,true
	call	read$dir
	call	end$of$dir
	jp	z,COPY$ALV	;return if end of directory

;not end of directory, valid entry?

	call	getdptra	;HL = buffa + dptr
	ld	A,0F0H
	and	(hl)
	jr	nz,INITIAL2

;now scan the disk map for allocated blocks

	ld	C,1		;set to allocated
	call	SCANDM$A
	call	setcdr		;set cdrmax to dcnt
	jr	initial2	;for another entry

copy$dirloc:
;copy directory location to lret following
;delete, rename, ... ops

	ld	a,(dirloc)
	jp	sta$ret	
	;ret

compext:
;compare extent# in A with that in C, return nonzero
;if they do not match

	push	bc		;save C's original value
	push	af
	ld	a,(extmsk)
	cpl
	ld	B,A

;B has negated form of extent mask

	ld	A,C
	and	B
	ld	C,A		;low bits removed from C
	pop	af
	and	B		;low bits removed from A
	sub	C
	and	maxext		;set flags
	pop	bc		;restore original values
	ret

SEARCH$EXTNUM:

	ld	C,EXTNUM
	jr	SEARCH

SEARCH$NAMLEN:

	ld	C,NAMLEN

search:
;search for directory element of length C at info

	ld	A,0ffh
	ld	(dirloc),a	;changed if actually found
	ld	hl,searchl
	ld	(hl),C		;searchl = C
	ld	hl,(info)
	ld	(searcha),hl	;searcha = info
	call	set$end$dir	;dcnt = enddir
	call	home		;to start at the beginning
;(drop through to searchn)	

searchn:
;search for the next directory element, assuming
;a previous call on search which sets searcha and
;searchl

	ld	C,false
	call	read$dir	;read next dir element
	call	end$of$dir
	jr	z,search$fin	;skip to end if so


;not end of directory, scan for match

	ld	hl,(searcha)	
	ex	de,hl		;DE=beginning of user fcb 
	ld	a,(de)		;first character
	cp	empty		;keep scanning if empty
	jr	z,searchnext

;not empty, may be end of logical directory

	push	de		;save search address
	call	compcdr		;past logical end?
	pop	de		;recall address
	jr	nc,search$fin	;artificial stop

searchnext:

	xor	A
	ld	(USER0$SEARCH),a
	call	getdptra	;HL = buffa+dptr
	ld	a,(searchl)
	ld	C,A		;length of search to c
	ld	B,0		;b counts up, c counts down

searchloop:

	ld	A,C
	or	A
	jr	z,endsearch

	ld	a,(de)
	cp	'?'
	jr	z,searchok	;? matches all

;scan next character if not ubytes

	ld	A,B
	cp	ubytes
	jr	z,searchok

;not the ubytes field, extent field?

	cp	extnum		;may be extent field
	ld	a,(de)		;fcb character
	jr	z,searchext	;skip to search extent

	jr	c,skipsys	;skip test for sys flag	SCC	23 Apr 84

	sub	(hl)		;			SCC	23 Apr 84
	and	3Fh		;			SCC	23 Apr 84
	jr	z,searchok	;			SCC	23 Apr 84

	ld	a,(de)		;get FCB char again	SCC	23 Apr 84
skipsys:			;			SCC	23 Apr 84

	sub	(hl)
	and	7Fh		;mask-out flags/extent modulus
	jr	z,SEARCHOK

	ld	A,(hl)
	or	b
	jr	nz,SEARCHN

	ld	a,(FX)
	cp	15
	jr	nz,SEARCHN

	ld	a,(USRCODE)
	or	A
	jr	z,SEARCHN

	ld	A,0FFH
	ld	(USER0$SEARCH),a
	jr	SEARCHOK

searchext:
;A has fcb character
;attempt an extent # match

	push	bc		;save counters
	ld	C,(hl)		;directory character to c
	call	compext		;compare user/dir char
	pop	bc		;recall counters
	jr	nz,searchn	;skip if no match

searchok:
;current character matches

	inc	de
	inc	hl
	inc	B
	dec	C
	jr	searchloop

endsearch:

	xor	A
	ld	(DIRLOC),a
	ld	(LRET),a
	ld	hl,USER0$SEARCH
	inc	(hl)
	ret	nz
	ld	hl,(DCNT)
	ld	(SDCNT),hl
	jp	SEARCHN

search$fin:
;end of directory, or empty name

	call	set$end$dir	;may be artifical end

LRET$EQ$FF:

	ld	A,255
	ld	B,A
	inc	B
	jp	sta$ret

delete:
;delete the currently addressed file

	call	RESELECT
	call	check$write	;write protected?
	call	SEARCH$EXTNUM	;search through file type
	ret	z

DELETE00:

	jr	z,DELETE1
	call	CHECK$RODIR
	ld	hl,(INFO)
	call	CHK$WILD
	jr	nz,DELETE11
	call	SEARCHN
	jr	DELETE00

DELETE1:

	call	SEARCH$EXTNUM

DELETE10:

	jp	z,COPY$DIRLOC

DELETE11:

	call	GETDPTRA
	ld	(hl),EMPTY
	ld	C,0
	call	SCANDM$AB
	call	SET$DIR$BLKS
	call	DELETE$SUB
	call	WRDIR
	call	SEARCHN
	jr	DELETE10

CHK$WILD:

	ld	C,11

CHK$WILD1:

	inc	hl
	ld	A,3FH
	sub	(hl)
	and	7FH
	ret	z
	dec	C
	jr	nz,CHK$WILD1
	or	A
	ret

get$block:
;given allocation vector position BC, find the zero bit
;closest to this position by searching left and right.
;if found, set the bit to one and return the bit position
;in hl.  if not found (i.e., we pass 0 on the left, or
;maxall on the right), return 0000 in hl

	ld	D,B
	ld	E,C		;copy of starting position to de

lefttst:

	ld	A,C
	or	B
	jr	z,righttst	;skip if left=0000

;left not at position zero, bit zero?

	dec	bc
	push	de
	push	bc		;left,right pushed
	call	getallocbit
	rra
	jr	nc,retblock	;return block number if zero

;bit is one, so try the right

	pop	bc
	pop	de		;left, right restored

righttst:

	ld	hl,(maxall)	;value of maximum allocation#
	ld	A,E
	sub	L
	ld	A,D
	sbc	a,H		;right=maxall?
	jr	nc,retblock0	;return block 0000 if so
	inc	de
	push	bc
	push	de		;left, right pushed
	ld	B,D
	ld	C,E		;ready right for call
	call	getallocbit
	rra
	jr	nc,retblock	;return block number if zero
	pop	de
	pop	bc		;restore left and right pointers
	jr	lefttst		;for another attempt

retblock:

	rla
	inc	A		;bit back into position and set to 1

;d contains the number of shifts required to reposition

	call	rotr		;move bit back to position and store
	pop	hl
	pop	de		;HL returned value, DE discarded
	ret

retblock0:
;cannot find an available bit, return 0000

	ld	A,C	
	or	B
	jr	nz,lefttst	;also at beginning
	ld	hl,0000h
	ret

copy$fcb:
;copy the entire file control block

	ld	C,0
	ld	E,fcblen	;start at 0, to fcblen-1
	;jmp copy$dir

copy$dir:
;copy fcb information starting at C for E bytes
;into the currently addressed directory entry

	push	de		;save length for later
	ld	B,0		;double index to BC
	ld	hl,(info)	;HL = source for data
	add	hl,bc
	push	hl
	call	TEST$SUB
	ld	C,0FFH
	call	z,SET$SUB$FLAG
	pop	de		;DE=.fcb(C), source for copy
	call	getdptra	;HL=.buff(dptr), destination
	pop	bc		;DE=source, HL=dest, C=length
	call	move		;data moved

seek$copy:
;enter from close to seek and copy current element

	call	seek$dir	;to the directory element
	jp	wrdir		;write the directory element
	;ret

TEST$SUB:

	inc	hl
	ld	de,SUB$FCB
	ld	C,11

TEST$SUB1:

	ld	a,(de)
	cp	(hl)
	ret	nz
	inc	de
	inc	hl
	dec	C
	jr	nz,TEST$SUB1
	xor	A
	ret


SUB$FCB:	defb	'$$$     SUB'


DELETE$SUB:

	call	GETDPTRA
	call	TEST$SUB
	ld	C,0
	ret	nz
	;JMP  SET$SUB$FLAG

SET$SUB$FLAG:

	ld	a,(CURDSK)
	or	A
	ret	nz
	ld	hl,SUB$FLAG
	ld	(hl),C
	ret

rename:
;rename the file described by the first half of
;the currently addressed file control block. the
;new name is contained in the last half of the
;currently addressed file conrol block.  the file
;name and type are changed, but the reel number
;is ignored.  the user number is identical

	call	check$write	;may be write protected

;search up to the extent field

	call	RESELECT
	call	SEARCH$EXTNUM

;copy position 0

	ld	hl,(info)
	ld	A,(hl)		;HL=.fcb(0), A=fcb(0)
	ld	de,dskmap
	add	hl,de		;HL=.fcb(dskmap)
	ld	(hl),A		;fcb(dskmap)=fcb(0)

;assume the same disk drive for new named file
rename0:

	jð	z,COPY$DIRLOÃ	;stoð aô enä oæ dir
	call	DELETE$SUB

;not end of directory, rename next element

	call	check$rodir	;may be read-only file
	ld	C,dskmap
	ld	E,extnum
	call	copy$dir

;element renamed, move to next

	call	searchn
	jr	rename0

indicators:
;set file indicators for current fcb

	call	RESELECT
	call	SEARCH$EXTNUM	;through file type

indic0:

	jp	z,COPY$DIRLOC	;stop at end of dir

;not end of directory, continue to change

	ld	C,0
	ld	E,extnum	;copy name
	call	copy$dir
	call	searchn
	jr	indic0

open:
;search for the directory entry, copy to fcb

	call	SEARCH$NAMLEN

OPEN1:

	ret	z	;return with lret=255 if end

;not end of directory, copy fcb information
open$copy:
;(referenced below to copy fcb info)

	call	getexta
	ld	A,(hl)
	push	af
 	push	hl		;save extent#
	call	getdptra
	ex	de,hl		;DE = .buff(dptr)
	ld	hl,(info)	;HL=.fcb(0)
	ld	C,nxtrec	;length of move operation
	push	de		;save .buff(dptr)
	call	move		;from .buff(dptr) to .fcb(0)

;note that entire fcb is copied, including indicators

	call	setfwf		;sets file write flag
	pop	de
	ld	hl,extnum
	add	hl,de		;HL=.buff(dptr+extnum)
	ld	C,(hl)		;C = directory extent number
	ld	hl,reccnt
	add	hl,de		;HL=.buff(dptr+reccnt)
	ld	B,(hl)		;B holds directory record count
	pop	hl
	pop	af
	ld	(hl),A		;restore extent number

;HL = .user extent#, B = dir rec cnt, C = dir extent#
;if user ext < dir ext then user := 128 records
;if user ext = dir ext then user := dir records
;if user ext > dir ext then user := 0 records

	ld	A,C
	cp	(hl)
	ld	A,B		;ready dir reccnt
	jr	z,open$rcnt	;if same, user gets dir reccnt
	ld	A,0
	jr	c,open$rcnt	;user is larger
	ld	A,128		;directory is larger

open$rcnt:		;A has record count to fill

	ld	hl,(info)
	ld	de,reccnt
	add	hl,de
	ld	(hl),A
	ret
 
mergezero:
;HL = .fcb1(i), DE = .fcb2(i),
;if fcb1(i) = 0 then fcb1(i) := fcb2(i)

	ld	A,(hl)
	inc	hl
	or	(hl)
	dec	hl
	ret	nz		;return if = 0000
	ld	a,(de)
	ld	(hl),A
	inc	de
	inc	hl		;low byte copied
	ld	a,(de)
	ld	(hl),A
	dec	de
	dec	hl		;back to input form
	ret

close:
;locate the directory element and re-write it

	xor	A
	ld	(lret),a
	ld	(dcnt),a
	ld	(dcnt+1),a	
	call	nowrite
	ret	nz		;skip close if r/o disk

;check file write flag - 0 indicates written
	call	getmodnum	;fcb(modnum) in A
	and	fwfmsk
	ret	nz		;return if bit remains set

	call	SEARCH$NAMLEN
	ret	z		;return if not found

;merge the disk map at info with that at buff(dptr)

	ld	bc,dskmap
	call	getdptra
	add	hl,bc
	ex	de,hl		;DE is .buff(dptr+16)
	ld	hl,(info)
	add	hl,bc		;DE=.buff(dptr+16), HL=.fcb(16)
	ld	C,fcblen-dskmap	;length of single byte dm

merge0:

	ld	a,(single)
	or	A
	jr	z,merged	;skip to double

;this is a single byte map
;if fcb(i) = 0 then fcb(i) = buff(i)
;if buff(i) = 0 then buff(i) = fcb(i)
;if fcb(i) <> buff(i) then error

	ld	A,(hl)
	or	A
	ld	a,(de)
	jr	nz,fcbnzero

;fcb(i) = 0

	ld	(hl),A	;fcb(i) = buff(i)

fcbnzero:

	or	A
	jr	nz,buffnzero

;buff(i) = 0

	ld	A,(hl)
	ld	(de),a		;buff(i)=fcb(i)

buffnzero:

	cp	(hl)
	jr	nz,mergerr	;fcb(i) = buff(i)?
	jr	dmset		;if merge ok

merged:
;this is a double byte merge operation

	call	mergezero	;buff = fcb if buff 0000
	ex	de,hl
	call	mergezero
	ex	de,hl		;fcb = buff if fcb 0000

;they should be identical at this point

	ld	a,(de)
	cp	(hl)
	jr	nz,mergerr	;low same?
	inc	de
	inc	hl		;to high byte
	ld	a,(de)
	cp	(hl)
	jr	nz,mergerr	;high same?

;merge operation ok for this pair

	dec	C		;extra count for double byte

dmset:

	inc	de
	inc	hl		;to next byte position
	dec	C
	jr	nz,merge0	;for more

;end of disk map merge, check record count
;DE = .buff(dptr)+32, HL = .fcb(32)

	ld	bc,-(fcblen-extnum)
	add	hl,bc
	ex	de,hl
	add	hl,bc

;DE = .fcb(extnum), HL = .buff(dptr+extnum)

	ld	a,(de)		;current user extent number

;if fcb(ext) >= buff(fcb) then
;buff(ext) := fcb(ext), buff(rec) := fcb(rec)

	cp	(hl)
	jr	c,endmerge

;fcb extent number >= dir extent number
	
	ld	(hl),A		;buff(ext) = fcb(ext)

;update directory record count field

	ld	bc,reccnt-extnum
	add	hl,bc
	ex	de,hl
	add	hl,bc

;DE=.buff(reccnt), HL=.fcb(reccnt)

	ld	A,(hl)
	ld	(de),a		;buff(reccnt)=fcb(reccnt)

endmerge:

	ld	A,true
	ld	(fcb$copied),a	;mark as copied
	ld	C,1
	call	SCANDM$B
	call	SETFWF
	jp	seek$copy	;ok to "wrdir" here - 1.4 compat
	;ret

mergerr:
;elements did not merge correctly

	ld	hl,lret
	dec	(hl)		;=255 non zero flag set
	ret

make:
;create a new file by creating a directory entry
;then opening the file

	call	check$write	;may be write protected
	ld	hl,(info)
	push	hl		;save fcb address, look for e5
	ld	hl,efcb
	ld	(info),hl	;info = .empty
	ld	C,1
	call	search		;length 1 match on empty entry
	pop	hl		;recall info address
	ld	(info),hl	;in case we return here
	ret	z		;return with error condition 255 if not found
	ex	de,hl		;DE = info address

;clear the remainder of the fcb

	ld	hl,namlen
	add	hl,de		;HL=.fcb(namlen)
	ld	C,fcblen-namlen	;number of bytes to fill
	xor	A		;clear accumulator to 00 for fill

make0:

	ld	(hl),A
	inc	hl
	dec	C
	jr	nz,make0
	ld	hl,ubytes
	add	hl,de		;HL = .fcb(ubytes)
	ld	(hl),A		;fcb(ubytes) = 0
	call	setcdr		;may have extended the directory

;now copy entry to the directory

	call	copy$fcb

;and set the file write flag to "1"

	jp	setfwf
	;ret

open$reel:
;close the current extent, and open the next one
;if possible.  RMF is true if in read mode

	xor	A
	ld	(fcb$copied),a	;set true if actually copied
	call	close		;close current extent

;lret remains at enddir if we cannot open the next ext

	ld	a,(LRET)
	inc	A
	ret	z		;return if end

;increment extent number

	ld	hl,(info)
	ld	bc,extnum
	add	hl,bc		;HL=.fcb(extnum)
	ld	A,(hl)
	inc	A
	and	maxext
	ld	(hl),A		;fcb(extnum)=++1
	jr	z,open$mod	;move to next module if zero

;may be in the same extent group

	ld	B,A
	ld	a,(extmsk)
	and	B

;if result is zero, then not in the same group

	ld	hl,fcb$copied	;true if the fcb was copied to directory
	and	(hl)		;produces a 00 in accumulator if not written
	jr	z,open$reel0	;go to next physical extent

;result is non zero, so we must be in same logical ext

	jr	open$reel1	;to copy fcb information

open$mod:
;extent number overflow, go to next module

	ld	bc,modnum-extnum
	add	hl,bc		;HL=.fcb(modnum)
	inc	(hl)		;fcb(modnum)=++1

;module number incremented, check for overflow

	ld	A,(hl)
	and	maxmod		;mask high order bits
	jr	z,open$r$err	;cannot overflow to zero

;otherwise, ok to continue with new module
open$reel0:

	call	SEARCH$NAMLEN	;next extent found?
	jr	nz,OPEN$REEL1

;end of file encountered

	ld	a,(rmf)
	inc	A		;0ffh becomes 00 if read
	jr	z,open$r$err	;sets lret = 1

;try to extend the current file

	call	make

;cannot be end of directory

	call	end$of$dir
	jr	z,open$r$err	;with lret = 1

	jr	open$reel2

open$reel1:
;not end of file, open

	call	open$copy

open$reel2:

	call	getfcb		;set parameters
	xor	A
	jp	sta$ret		;ret with lret = 0

open$r$err:
;cannot move to next extent of this file

	call	setlret1	;lret = 1
	jp	setfwf		;ensure that it will not be closed
	;ret

seqdiskread:

	call	RESELECTX

diskread:			;(may enter from seqdiskread)

	ld	A,true
	ld	(rmf),a		;read mode flag = true (open$reel)

;read the next record from the current fcb

	call	getfcb		;sets parameters for the read
	ld	a,(vrecord)
	ld	hl,rcount
	cp	(hl)		;vrecord-rcount

;skip if rcount > vrecord

	jr	c,recordok

;not enough records in the extent
;record count must be 128 to continue

	cp	128		;vrecord = 128?
	jr	nz,diskeof	;skip if vrecord<>128

	call	open$reel	;go to next extent if so
	xor	A
	ld	(vrecord),a	;vrecord=00


;now check for open ok

	ld	a,(lret)
	or	A
	jr	nz,diskeof	;stop at eof

recordok:
;arrive with fcb addressing a record to read

	call	index

;error 2 if reading unwritten data
;(returns 1 to be compatible with 1.4)

	jr	z,diskeof

;record has been allocated, read it

	call	atran		;arecord now a disk address
	call	seek		;to proper track,sector
	call	rdbuff		;to dma address
	jp	setfcb		;replace parameter
	;ret

diskeof:

	jp	setlret1	;lret = 1
	;ret

seqdiskwrite:

	call	RESELECTX

diskwrite:			;(may enter here from seqdiskwrite above)

	ld	A,false
	ld	(rmf),a		;read mode flag

;write record to currently selected file

	call	check$write	;in case write protected
	ld	hl,(info)	;HL = .fcb(0)
	call	check$rofile	;may be a read-only file
	call	GETMODNUM
	and	40H
	jp	nz,ROF$ERROR
	call	getfcb		;to set local parameters
	ld	a,(vrecord)
	cp	lstrec+1	;vrecord-128

;skip if vrecord > lstrec
;vrecord = 128, cannot open next extent
	jp	nc,setlret1	;lret=1		

diskwr0:
;can write the next record, so continue

	call	index
	ld	C,0		;marked as normal write operation for wrbuff
	jr	nz,diskwr1

;not allocated
;the argument to getblock is the starting
;position for the disk search, and should be
;the last allocated block for this file, or
;the value 0 if no space has been allocated

	call	dm$position
	ld	(dminx),a	;save for later
	ld	bc,0000h	;may use block zero
	or	A
	jr	z,nopblock	;skip if no previous block

;previous block exists at A

	ld	C,A
	dec	bc		;previous block # in BC
	call	getdm		;previous block # to HL
	ld	B,H
	ld	C,L		;BC=prev block#

nopblock:
;BC = 0000, or previous block #

	call	get$block	;block # to HL

;arrive here with block# or zero

	ld	A,L
	or	H
	jr	nz,blockok

;cannot find a block to allocate

	ld	A,2
	jp	sta$ret		;lret=2

blockok:
;allocated block number is in HL

	ld	(arecord),hl
	ex	de,hl		;block number to DE
	ld	hl,(info)
	ld	bc,dskmap
	add	hl,bc		;HL=.fcb(dskmap)
	ld	a,(single)
	or	A		;set flags for single byte dm
	ld	a,(dminx)	;recall dm index
	jr	z,allocwd	;skip if allocating word

;allocating a byte value

	call	addh
	ld	(hl),E		;single byte alloc
	jr	diskwru		;to continue

allocwd:
;allocate a word value

	ld	C,A
	ld	B,0		;double(dminx)
	add	hl,bc
	add	hl,bc		;HL=.fcb(dminx*2)
	ld	(hl),E
	inc	hl
	ld	(hl),D		;double wd

diskwru:
;disk write to previously unallocated block

	ld	C,2		;marked as unallocated write

diskwr1:
;continue the write operation if no allocation error
;C = 0 if normal write, 2 if to prev unalloc block

	ld	a,(lret)
	or	A
	ret	nz		;stop if non zero returned value
	push	bc		;save write flag
	call	atran		;arecord set
	ld	a,(FX)
	cp	40
	jr	nz,diskwr11	
	pop	bc
	push	bc
	ld	A,C
	dec	A
	dec	A	
	jr	nz,diskwr11	;old allocation
	push	hl		;arecord in hl ret from atran
	ld	hl,(buffa)
	ld	D,A		;zero buffa & fill

fill0:	

	ld	(hl),A
	inc	hl
	inc	D
	jp	p,fill0	
	call	setdir
	ld	hl,(arecord1)	
	ld	C,2	

fill1:	

	ld	(arecord),hl
	push	bc
	call	seek
	pop	bc	
	call	wrbuff		;write fill record	
	ld	hl,(arecord)

;restore last record

	ld	C,0		;change  allocate flag
	ld	a,(blkmsk)
	ld	B,A
	and	L
	cp	B
	inc	hl	
	jr	nz,fill1	;cont until cluster is zeroed
	pop	hl
	ld	(arecord),hl
	call	setdata
	call	SEEK
	jr	DISKWR12

diskwr11:

	call	seek		;to proper file position
	pop	bc
	push	bc		;restore/save write flag (C=2 if new block)
	ld	a,(ARECORD)
	ld	hl,BLKMSK
	and	(hl)
	jr	z,DISKWR13

DISKWR12:

	ld	C,0

DISKWR13:

	call	wrbuff		;written to disk
	pop	bc		;C = 2 if a new block was allocated, 0 if not

;increment record count if rcount<=vrecord

	ld	a,(vrecord)
	ld	hl,rcount
	cp	(hl)		;vrecord-rcount
	jr	c,diskwr2

;rcount <= vrecord

	ld	(hl),A
	inc	(hl)		;rcount = vrecord+1
	ld	C,2		;mark as record count incremented

diskwr2:
;A has vrecord, C=2 if new block or new record#

	dec	C
	dec	C
	jr	nz,noupdate
	push	af		;save vrecord value
	call	getmodnum	;HL=.fcb(modnum), A=fcb(modnum)

;reset the file write flag to mark as written fcb

	and	(not fwfmsk) and 0ffh	;bit reset
	ld	(hl),A		;fcb(modnum) = fcb(modnum) and 7fh
	pop	af		;restore vrecord

noupdate:
;check for end of extent, if found attempt to open
;next extent in preparation for next write

	cp	lstrec		;vrecord=lstrec?
	jr	nz,diskwr3	;skip if not

;may be random access write, if so we are done
;change next

	ld	a,(FX)
	cp	22
	jr	nc,DISKWR3	;skip next extent open op

;update current fcb before going to next extent

	call	setfcb
	call	open$reel	;rmf=false

;vrecord remains at lstrec causing eof if
;no more directory space is available

	ld	hl,lret
	ld	A,(hl)
	or	A
	jr	nz,nospace

;space available, set vrecord=255
	dec	A
	ld	(vrecord),a	;goes to 00 next time

nospace:

	ld	(hl),0		;lret = 00 for returned value

diskwr3:

	jp	setfcb		;replace parameters
	;ret

rseek:
;random access seek operation, C=0ffh if read mode
;fcb is assumed to address an active file control block
;(MODNUM HAS BEEN SET TO 1010$0000b IF PREVIOUS BAD SEEK)

	push	bc		;save r/w flag
	ld	de,(info)	;DE will hold base of fcb
	ld	hl,ranrec
	add	hl,de		;HL=.fcb(ranrec)
	ld	A,(hl)
	and	7fh
	push	af		;record number
	ld	A,(hl)
	rla			;cy=lsb of extent#
	inc	hl
	ld	A,(hl)
	rla
	and	11111b		;A=ext#
	ld	C,A		;C holds extent number, record stacked
	ld	A,(hl)
	rra
	rra
	rra
	rra
	and	1111b		;mod#
	ld	B,A		;B holds module#, C holds ext#
	pop	af		;recall sought record #

;check to insure that high byte of ran rec = 00

	inc	hl
	ld	L,(hl)		;l=high byte (must be 00)
	inc	L
	dec	L
	ld	L,6		;zero flag, l=6

;produce error 6, seek past physical eod

	jr	nz,seekerr

;otherwise, high byte = 0, A = sought record

	ld	hl,nxtrec
	add	hl,de		;HL = .fcb(nxtrec)
	ld	(hl),A		;sought rec# stored away

;arrive here with B=mod#, C=ext#, DE=.fcb, rec stored
;the r/w flag is still stacked.  compare fcb values

	ld	hl,extnum
	add	hl,de
	ld	A,C		;A=seek ext#
	sub	(hl)
	jr	nz,ranclose	;tests for = extents

;extents match, check mod#

	ld	hl,modnum
	add	hl,de
	ld	A,B		;B=seek mod#

;could be overflow at eof, producing module#
;of 90H or 10H, so compare all but fwf

	sub	(hl)
	and	3FH
	jr	z,seekok	;same?

ranclose:

	push	bc
	push	de		;save seek mod#,ext#, .fcb
	call	close		;current extent closed
	pop	de
	pop	bc		;recall parameters and fill
	ld	L,3		;cannot close error #3
	ld	a,(lret)
	inc	A
	jr	z,badseek
	ld	hl,extnum
	add	hl,de
	ld	(hl),C		;fcb(extnum)=ext#
	ld	hl,modnum
	add	hl,de
	ld	(hl),B		;fcb(modnum)=mod#
	call	open		;is the file present?
	ld	a,(lret)
	inc	A
	jr	nz,seekok	;open successful?

;cannot open the file, read mode?

	pop	bc		;r/w flag to c (=0ffh if read)
	push	bc		;everyone expects this item stacked
	ld	L,4		;seek to unwritten extent #4
	inc	C		;becomes 00 if read operation
	jr	z,badseek	;skip to error if read operation

;write operation, make new extent

	call	make
	ld	L,5		;cannot create new extent #5
	ld	a,(lret)
	inc	A
	jr	z,badseek	;no dir space

;file make operation successful
seekok:

	pop	bc		;discard r/w flag
	xor	A
	jp	sta$ret		;with zero set

badseek:
;fcb no longer contains a valid fcb, mark
;with 1010$00000B IN MODNUM FIELD so that it
;appears as overflow with file write flag set

	push	hl		;save error flag
	call	getmodnum	;HL = .modnum
	ld	(hl),10100000B
	pop	hl		;and drop through

seekerr:

	pop	bc		;discard r/w flag
	ld	A,L
	ld	(lret),a	;lret=#, nonzero

;setfwf returns non-zero accumulator for err

	jp	setfwf		;flag set, so subsequent close ok
	;ret

randiskread:
;random disk read operation

	call	RESELECTX
	ld	C,true		;marked as read operation
	call	rseek
	call	z,diskread	;if seek successful
	ret

randiskwrite:
;random disk write operation

	call	RESELECTX
	ld	C,false		;marked as write operation
	call	rseek
	call	z,diskwrite	;if seek successful
	ret

compute$rr:
;compute random record position for getfilesize/setrandom

	ex	de,hl
	add	hl,de

;DE=.buf(dptr) or .fcb(0), HL = .f(nxtrec/reccnt)

	ld	C,(hl)
	ld	B,0		;BC = 0000 0000 ?rrr rrrr
	ld	hl,extnum
	add	hl,de
	ld	A,(hl)
	rrca
	and	80h		;A=e000 0000
	add	a,C
	ld	C,A
	ld	A,0
	adc	a,B
	ld	B,A

;BC = 0000 000? errrr rrrr

 	ld	A,(hl)
 	rrca
	and	0fh
	add	a,B
	ld	B,A

;BC = 000? eeee errrr rrrr

	ld	hl,modnum
	add	hl,de
	ld	A,(hl)		;A=XXX? mmmm
	add	a,A
	add	a,A
	add	a,A
	add	a,A		;cy=? A=mmmm 0000
	push	af
	add	a,B
	ld	B,A

;cy=?, BC = mmmm eeee errr rrrr

	push	af		;possible second carry
	pop	hl		;cy = lsb of L
	ld	A,L		;cy = lsb of A
	pop	hl		;cy = lsb of L
	or	L		;cy/cy = lsb of A
	and	1		;A = 0000 000? possible carry-out
	ret

getfilesize:
;compute logical file size for current fcb
;zero the receiving ranrec field

	call	RESELECT
	ld	hl,(info)
	ld	de,ranrec
	add	hl,de
	push	hl		;save position
	ld	(hl),D
	inc	hl
	ld	(hl),D
	inc	hl
	ld	(hl),D		;=00 00 00
	call	SEARCH$EXTNUM

getsize:

	jr	z,SETSIZE

;current fcb addressed by dptr

	call	getdptra
	ld	de,reccnt	;ready for compute size
	call	compute$rr

;A=0000 000? BC = mmmm eeee errr rrrr
;compare with memory, larger?

	pop	hl
	push	hl		;recall, replace .fcb(ranrec)
	ld	E,A		;save cy
	ld	A,C
	sub	(hl)
	inc	hl		;ls byte
	ld	A,B
	sbc	a,(hl)
	inc	hl		;middle byte
	ld	A,E
	sbc	a,(hl)		;carry if .fcb(ranrec) > directory
	jr	c,getnextsize	;for another try

;fcb is less or equal, fill from directory

	ld	(hl),E
	dec	hl
	ld	(hl),B
	dec	hl
	ld	(hl),C

getnextsize:

	call	searchn
	jr	getsize

setsize:

	pop	hl		;discard .fcb(ranrec)
	ret

setrandom:
;set random record from the current file control block

	ld	hl,(info)
	ld	de,nxtrec	;ready params for computesize
	call	compute$rr	;DE=info, A=cy, BC=mmmm eeee errr rrrr
	ld	hl,ranrec
	add	hl,de		;HL = .fcb(ranrec)
	ld	(hl),C
	inc	hl
	ld	(hl),B
	inc	hl
	ld	(hl),A		;to ranrec
	ret

TMPSELECT:

	ld	hl,SELDSK
	ld	(hl),E

CURSELECT:

	ld	a,(SELDSK)
	ld	hl,CURDSK
	cp	(hl)
	jr	nz,SELECT
	cp	0FFH
	ret	nz

select:
;select disk info for subsequent input or output ops

	ld	(hl),A
	ld	D,A
	ld	hl,(DLOG)
	call	TEST$VECTOR
	ld	E,A
	push	de
	call	selectdisk
	pop	hl		;recall dlog vector
	jp	nc,SEL$ERROR	;returns with CARRY SET if select ok

;is the disk logged in?

	dec	L
	ret	z		;return if bit is set

;disk not logged in, set bit and initialize

	call	INITIALIZE
	ld	hl,(LSN$ADD)
	ld	A,(hl)
	and	1
	push	af
	add	a,(hl)
	ld	(hl),A
	pop	af
	call	nz,SET$RLOG
	jp	SET$DLOG
	;ret

RESELECTX:

	ld	hl,CHECK$FCB
	push	hl

reselect:
;check current fcb to see if reselection necessary

	ld	A,true
	ld	(resel),a	;mark possible reselect
	ld	hl,(info)
	ld	A,(hl)		;drive select code
	ld	(FCBDSK),a
	and	11111b		;non zero is auto drive select
	dec	A		;drive code normalized to 0..30, or 255
	jp	m,noselect	;			SCC	22 Apr 84

	ld	(SELDSK),a

noselect:

	call	CURSELECT

;set user code

	ld	a,(usrcode)	;0...15
	ld	hl,(info)
	ld	(hl),A

NOSELECT0:

	call	TST$LOG$FXS
	ret	nz
	call	FUNC48

NOSELECT1:

	ld	C,0FFH
	jp	?discd

subttl	Individual Function Handlers
func12:
;return version number

	ld	A,dvers
	jp	sta$ret		;lret = dvers (high = 00)
	;ret 			;jmp goback
page
func13:
;reset disk system - initialize to disk 0

	ld	hl,0
	ld	(rodsk),hl
	ld	(dlog),hl
	xor	A
	ld	(ACTDSK),a
	dec	A
	ld	(CURDSK),a

;note that usrcode remains unchanged

	ld	hl,tbuff
	ld	(dmaad),hl	;dmaad = tbuff
	call	SETDATA		;to data dma address
	jr	NOSELECT1
	;ret 			;jmp goback
page
FUNC14:

	call	TMPSELECT
	ld	a,(SELDSK)
	ld	(ACTDSK),a
	ret

page
func15:
;open file

	call	clrmodnum	;clear the module number
	call	reselect
	call	OPEN
	call	OPENX
	ld	a,(DIRLOC)
	inc	A
	ret	z
	ld	hl,(SDCNT)
	ld	A,L
	and	0FCH
	ld	L,A
	dec	hl
	ld	(DCNT),hl
	ld	hl,(INFO)
	ld	(hl),0
	call	SEARCHN
	call	OPEN1
	call	OPENX
	ret

OPENX:

	call	END$OF$DIR
	ret	z
	pop	hl
	ld	a,(USRCODE)
	ld	hl,(INFO)
	cp	(hl)
	jr	z,OPENX1

	ld	de,10		;test file attribute t2'
	add	hl,de		;for sys | dir status
	ld	A,(hl)
	and	80H
	jr	nz,openx2	;system, allow open	SCC	22 Apr 84

	inc	hl		;bump ptr to ext field	SCC	22 Apr 84
	inc	hl		;			SCC	22 Apr 84
	ld	(hl),a		;zero remainder of FCB	SCC	22 Apr 84
	ld	d,h		;			SCC	22 Apr 84
	ld	e,l		;			SCC	22 Apr 84
	inc	de		;			SCC	22 Apr 84
	ld	bc,19		;			SCC	22 Apr 84
	ldir			;			SCC	22 Apr 84
	jp	lret$eq$FF	;flag open failure	SCC	22 Apr 84

openx2:				;			SCC	22 Apr 84
	ld	de,4
	add	hl,de
	ld	A,(hl)
	or	40H
	ld	(hl),A

OPENX1:

	ld	de,OLOG
	call	SET$CDISK
	jp	SET$LSN
	;ret 			;jmp goback
page
func16:
;close file

	call	reselect
	call	CLOSE
	jp	SET$LSN
	;ret ;jmp goback
page
func17:
;search for first occurence of a file

	ex	de,hl
	xor	A

CSEARCH:

	push	af
	ld	A,(hl)
	cp	'?'
	jr	nz,CSEARCH1
	call	CURSELECT
	call	NOSELECT0
	ld	C,0
	jr	CSEARCH2

CSEARCH1:

	call	GETEXTA
	ld	A,(hl)
	cp	'?'
	call	nz,CLRMODNUM
	call	RESELECT
	ld	C,NAMLEN

CSEARCH2:

	pop	af
	ld	hl,DIR$TO$USER
	push	hl
	jp	z,SEARCH
	jp	SEARCHN
page

func18:
;search for next occurence of a file

	ld	hl,(SEARCHA)
	ld	(INFO),hl
	or	1
	jr	CSEARCH
page
FUNC19	equ	DELETE
;delete a file

FUNC20	equ	SEQDISKREAD
;read a file

FUNC21	equ	SEQDISKWRITE
;write a file
page

func22:
;make a file

	call	clrmodnum
 	call	reselect
	call	MAKE
	jr	OPENX1
	;ret ;jmp goback

FUNC23	equ	RENAME
;rename a file

func24:
;return the login vector

	ld	hl,(dlog)
	jr	sthl$ret
	;ret ;jmp goback

func25:
;return selected disk number

	ld	a,(SELDSK)
	jp	sta$ret
	;ret ;jmp goback

func26:
;set the subsequent dma address to info

	ex	de,hl		;was lhld info
	ld	(dmaad),hl	;dmaad = info
	jp	setdata		;to data dma address
	;ret ;jmp goback

func27:
;return the login vector address

	call	CURSELECT
	ld	hl,(alloca)
	jr	sthl$ret
	;ret 			;jmp goback

func28	equ	set$ro
;write protect current disk

	;ret 			;jmp goback

func29:
;return r/o bit vector

	ld	hl,(rodsk)
	jr	sthl$ret
	;ret 			;jmp goback

FUNC30	equ	INDICATORS
;set file indicators

func31:
;return address of disk parameter block

	call	CURSELECT
	ld	hl,(dpbaddr)

sthl$ret:

	ld	(aret),hl
	ret	;jmp goback

func32:
;GET/SET USER CODE

	ld	hl,USRCODE

;DOES REG E = FFH?

	ld	A,E
	inc	A
	ld	A,(hl)
	jp	z,STA$RET	; YES - RETURN USER

;SET USER NUMBER

	ld	A,E
	and	0FH
	ld	(hl),A
	ret

FUNC33	equ	RANDISKREAD
;random disk read operation

FUNC34	equ	RANDISKWRITE
;random disk write operation

FUNC35	equ	GETFILESIZE
;return file size (0-65536)

func36	equ	setrandom
;set random record
	;ret 			;jmp goback

func37:
;drive reset

	ex	de,hl

RESET37X:

	push	hl
	ld	A,L
	cpl
	ld	E,A
	ld	A,H
	cpl
	ld	hl,(dlog)
	and	H
	ld	D,A
	ld	A,L
	and	E
	ld	E,A
	ld	hl,(rodsk)
	ex	de,hl	
	ld	(dlog),hl
	ld	A,L
	and	E
	ld	L,A
	ld	A,H
	and	D
	ld	H,A
	ld	(rodsk),hl
	ld	A,0FFH
	ld	(CURDSK),a
	ld	(SCAN$FLAG),a
	pop	hl
	jp	SD$0

func38	equ	func$ret

func39	equ	func$ret

FUNC40	equ	RANDISKWRITE

FUNC45:
;SET BDOS ERROR MODE

	ld	A,E
	ld	(ERRMODE),a
	ret

FUNC48:
;FLUSH BUFFERS

	call	?flush
	jp	DIOCOMP
subttl BDOS call termination

GOBACK0:

	ld	hl,0FFFFH
	ld	(ARET),hl

goback:
;arrive here at end of processing to return to user

	ld	a,(resel)
	or	A
	jr	z,retmon

;reselection may have taken place

	ld	hl,(INFO)
	ld	a,(FCBDSK)
	ld	(hl),A

;return from the disk monitor
retmon:

	ld	sp,(entsp)	;user stack restored
	ld	hl,(aret)
	ld	A,L
	ld	B,H		;BA = HL = aret
	ret
subttl Initialized Data Areas

efcb:

	defb	empty		;0e5=available dir entry

LOG$FXS:

	defb	15,16,17,19,22,23,30,35,0

RW$FXS:

	defb	20,21,33,34,40,0

SC$FXS:

	defb	16,18
subttl	CPMERR - console error message routine
;*****************************************************************
;*****************************************************************
;**                                                             **
;**                   S I M P L E   C P / M                     **
;**                                                             **
;**         S t a n d a r d   E r r o r   R o u t i n e         **
;**                                                             **
;*****************************************************************
;*****************************************************************

cpmerr:	; c = error #

	ld	B,0
	dec	C
	ld	hl,errtbl
	add	hl,bc
	add	hl,bc
	ld	E,(hl)
	inc	hl
	ld	D,(hl)

;stack message address, advance to new line
	push	de
	call	crlf

;print error prefix
	ld	bc,dskmsg
	call	print

;identify drive
	ld	a,(seldsk)
	add	a,'A'
	ld	C,A
	call	conout

;print colon and space
	ld	bc,colon
	call	print

;print error message tail
	pop	bc
	jp	print

errtbl:	defw	permsg,rodmsg,rofmsg,selmsg

dskmsg:	defb	'CP/M Error On $'
colon:	defb	': $'
permsg:	defb	'Disk I/O$'
selmsg:	defb	'Invalid Drive$'
rofmsg:	defb	'Read/Only File$'
rodmsg:	defb	'Read/Only Disk$'

;------------------------------------------------------------------------------

subttl	Data Segment

	if	standard
	dseg
	endif

	public	DLOG
	public	RODSK
	public	FX
	public	ERRMODE
	public	LISTCP
	public	KBCHAR
	public	SUB$FLAG
	public	INFO
	public	ARET
	public	ARECORD
	public	SELDSK
	public	CONMODE
	public	out$delim

BDOS$DT	equ	$

	if	data_low
?bdos	equ	$+6
	defs	front_size
	endif

dlog:	defs	WORD	;logged-in disks
rodsk:	defs	WORD	;read only disk vector
dmaad:	defs	WORD	;initial dma address

;
;	curtrka - alloca are set upon disk select
;	(data must be adjacent, do not insert variables)
;	(address of translate vector, not used)
;

cdrmaxa:	defs	word	;pointer to cur dir max value
curtrka:	defs	word	;current track address
curreca:	defs	word	;current record address
buffa:		defs	word	;pointer to directory dma address
dpbaddr:	defs	word	;current disk parameter block address
checka:		defs	word	;current checksum vector address
alloca:		defs	word	;current allocation vector address

addlist	equ	$-buffa	;address list size

;
;	sectpt - offset obtained from disk parm block at dpbaddr
;	(data must be adjacent, do not insert variables)
;

sectpt:		defs	word	;sectors per track
blkshf:		defs	byte	;block shift factor
blkmsk:		defs	byte	;block mask
extmsk:		defs	byte	;extent mask
maxall:		defs	word	;maximum allocation number
dirmax:		defs	word	;largest directory number
dirblk:		defs	word	;reserved allocation bits for directory
chksiz:		defs	word	;size of checksum vector
offset:		defs	word	;offset tracks at beginning

dpblist		equ	$-sectpt	;size of area

;
;	local variables
;

tranv:		defs	word	;address of translate vector
fcb$copied:	defs	byte	;set true if copy$fcb called
rmf:		defs	byte	;read mode flag for open$reel
dirloc:		defs	byte	;directory flag in rename, etc.
dminx:		defs	byte	;local for diskwrite
searchl:	defs	byte	;search length
searcha:	defs	word	;search address
single:		defs	byte	;set true if single byte allocation map
fcbdsk:		defs	byte	;disk named in fcb
rcount:		defs	byte	;record count in current fcb
extval:		defs	byte	;extent number and extmsk
vrecord:	defs	BYTE	;current virtual record
arecord:	defs	word	;current actual record
arecord1:	defs	word	;current actual block# * blkmsk

;
;	local variables for directory access
;

dptr:		defs	byte	;directory pointer 0,1,2,3
dcnt:		defs	word	;directory counter 0,1,...,dirmax

entsp:		defs	word	;entry stack pointer
		defs	ssize*2	;stack size

lstack:

info:		defs	word	;information address
aret:		defs	word	;address value to return
lret		equ	aret	;low(aret)

; RESEL & RELOG ARE INITIALIZED AS A PAIR AT BDOS ENTRY

resel:		defs	byte	;reselection flag
RELOG:		defs	BYTE	;RELOG DRIVE SWITCH
save$pos:	defs	BYTE	;SAVE BEGINNING FUNCTION 10 BUFF POS
LSN$ADD:	defs	WORD	;LOGIN SEQ # ADDRESS
LSN$NS:		defs	16	;LOGIN SEQUENCE #S (1 PER DRIVE)
SDCNT:		defs	WORD	;SAVE USER 0 DCNT FOR OPEN FX

USER0$SEARCH:	defs	WORD	;USER 0 SEARCH FLAG FOR OPEN FX
CURDSK:		defs	BYTE	;CURRENT DISK NUMBER
ACTDSK:		defs	BYTE	;ACTUAL SELECTED DISK NUMBER
SELDSK:		defs	BYTE	;CURRENTLY SELECTED DISK NUMBER

OUT$DELIM:	defs	BYTE	;FUNCTION 9 (PRINT) DELIMITER

; The following two variables are set to zero by the
; CCP prior to passing control to a loaded transient program.

OLOG:		defs	WORD	;FILE OPEN DRIVE VECTOR
RLOG:		defs	WORD	;MEDIA CHANGE DRIVE VECTOR
subttl	Reinitialized data

; The following variables are initialized to zero by the BDOS
; warm start initialization routine (WS$INIT) and the
; cold start initialization routine (CS$INIT)

column:		defs	byte	;column position
usrcode:	defs	byte	;current user number
kbchar:		defs	byte	;initial key char = 00
compcol:	defs	byte	;true if computing column position
strtcol:	defs	byte	;starting column position after read

FX:		defs	BYTE	;CURRENT BDOS FUNCTION NUMBER
ERRMODE:	defs	BYTE	;BDOS ERROR MODE (NORMAL,RETURN,RET & DISPLAY)
SCAN$FLAG:	defs	BYTE	;SCAN$DRIVE FLAG (FF=DRIVE RESET,0=WS$INIT)
CONMODE:	defs	BYTE	;CONSOLE MODE (BIT 2 = RAW OUTPUT, BIT 4 = ESD)

WS$INIT$SIZE	equ	$-COLUMN





;
; The following variables are initialized to zero by the BDOS
; cold start initialization routine (CS$INIT)
;

listcp:		defs	byte	;listing toggle
FX10FLG:	defs	BYTE	;CCP FUNCTION 10 (^C) FLAG
SUB$FLAG:	defs	BYTE	;SUBMIT FLAG ($$$.SUB FILE ON A)
SUBWORK:	defs	20	;CCP SUBMIT WORK AREA

CS$INIT$SIZE	equ	$-COLUMN

	cseg		;this code makes the code segment begin on a
			; page boundry
LAST:
	defb	0

	org	(((LAST-BASE)+255) AND 0FF00H) - 1
	defb	0
subttl BIOS access constants

BIOS	equ	$

bootf	equ	bios+3*0	;cold boot function
wbootf	equ	bios+3*1	;warm boot function
constf	equ	bios+3*2	;console status function
coninf	equ	bios+3*3	;console input function
conoutf	equ	bios+3*4	;console output function
listf	equ	bios+3*5	;list output function
punchf	equ	bios+3*6	;punch output function
readerf	equ	bios+3*7	;reader input function
homef	equ	bios+3*8	;disk home function
seldskf	equ	bios+3*9	;select disk function
settrkf	equ	bios+3*10	;set track function
setsecf	equ	bios+3*11	;set sector function
setdmaf	equ	bios+3*12	;set dma function
readf	equ	bios+3*13	;read disk function
writef	equ	bios+3*14	;write disk function
liststf	equ	bios+3*15	;list status function
sectran	equ	bios+3*16	;sector translate

	end	
