
;
; CCC.ASM (C.CCC)  v1.45				11/22/81
;
; NOTE: If you are running under MP/M II, be sure to set the MPM2
; equate to 1.
;
; This is the BDS C run-time package. Normally, it resides at
;    the start of the TPA (at address BASE+100h, where BASE is either
;    0000h or 4200h depending on CP/M implementation.) The code
;    generated by the compiler ALWAYS sits immediately after the end of
;    this run-time package code.
;
; Equate statements in CAPITAL letters may be customized by the
;    user in order to change a) the origin of the run-time package,
;    and b) the origin of the run-time RAM area. If you will be
;    generating code to run in a non-CP/M environment, set the CPM
;    equate to zero and make sure to set the ORIGIN, RAM and
;    EXITAD equates to fit your custom run-time configuration.
;
; The "lxi sp,0" instruction at the start is replaced by the sequence:
;
;		lhld base+6
;		sphl
;
;    by CLINK at link time, unless the -t option is used with CLINK,
;    in which case the "lxi sp" remains there and the value used to
;    initialize the SP is the argument given to the "-t" option.
;

	page 76
	title 'BDS C Run-Time Module (c.ccc)  v1.45   11/22/81'


CPM:	EQU 1		;True if to be run under CP/M or MP/M

MPM2:	EQU 0		;True ONLY if running under MP/M II

DMAVIO:	EQU 1		;True if using DMA video library routines and
			;need parameters initialized
	IF CPM
base:	equ  0		;start of ram in system (either 0 or 4200h for CP/M)
bdos:	equ base+5	;rest of these used by CP/M-based configurations.
tpa:	equ base+100h
nfcbs:	equ 8		;maximum # of files open at one time
tbuff:	equ base+80h
origin:	equ tpa
exitad:	equ base	;warm boot location
	ENDIF

	IF NOT CPM	;fill in the appropriate values...
ORIGIN:	EQU NEWBASE	;Address at which programs are to run
RAM:	EQU WHATEVER	;Read-write memory area for non-CP/M configurations
			;  (defaults to immediately after C.CCC under CP/M)
EXITAD:	EQU WHENDONE	;where to go when done executing
	ENDIF

;
; The location of the jump vectors and utility routines must remain
; constant relative to the beginning of this run-time module.
;
; Do NOT change ANYTHING between here and the start of the
; "init" routine!!!!!!!!
;

	org origin
	lxi sp,0	;this is changed by CLINK to lhld base+5h
	nop		;this first is usually turned into sphl by CLINK

	nop! nop	;Simple initialization or patches may be
	nop! nop! nop	;inserted here, but better to do all that
	nop! nop! nop	;in the "init" routine

	call init	;do ARGC & ARGV processing, plus misc. initializations
	call main	;go crunch!!!!
	jmp vexit	;close open files and reboot

extrns:	ds 2		;set by CLINK to external data base address
cccsiz:	dw main-origin	;size of this code (for use by CLINK)
codend:	ds 2		;set by CLINK to (last addr of code + 1)
freram:	ds 2		;set by CLINK to (last addr of externals + 1)

;
; Jump vectors to some file i/o utility routines:
;

error:	jmp verror	;loads -1 into HL and returns
exit:	jmp vexit	;close all open files and reboot

	IF CPM
close:	jmp vclose	;close a file
setfcb:	jmp vsetfcb	;set up fcb at HL given filename at DE
fgfd:	jmp vfgfd	;return C set if file fd in A not open
fgfcb:	jmp vfgfcb	;compute address of internal fcb for fd in A
	ENDIF

	IF NOT CPM	;if not under CP/M, file I/O routines
close:	jmp verror	;are not used.
setfcb:	jmp verror
fgfd:	jmp verror
fgfcb:	jmp verror
	ENDIF

	ds 16		;reserved

	IF CPM
setfcb3: mov m,a	;this is a patch from the "vsetfcb" routine,
	 inx h		;which causes the random record bytes of the
	 mov m,a	;fcb being initialized to be zeroed. (Former
	 inx h		;versions had a "ds 30" above, so this keeps
	 mov m,a	;all the addresses consistent between this 
	 pop d		;and earlier 1.4's)
	 pop b
	 ret

patchnm: call setnm	;another patch from "vsetfcb"
	 jmp setnm3
	ENDIF

	IF NOT CPM
	 ds 14		;keep addresses the same for non-CP/M implementations
	ENDIF

;
; The following routines fetch a variable value from either
; the local stack frame or the external area, given the relative
; offset of the datum required immediately following the call;
; for the "long displacement" routines, the offset must be 16 bits,
; for the "short displacement" routines, the offset must be 8 bits.
;

;
; long-displacement, double-byte external indirection:
;
;	format: 	call ldei		; get 16-bit value in HL
;			dw offset_from_extrns	; >= 256
;

ldei:	pop h		;get address of offset
	mov e,m		;put offset in DE
	inx h
	mov d,m
	inx h 		
	push h		;save return address
	lhld extrns	;add offset to external area base
	dad d
	mov a,m		;and get the value into HL
	inx h
	mov h,m
	mov l,a
	ret

;
; short-displacement, double-byte external indirection:
;
;	format:		call sdei		; get 16-bit value in L
;			db offset_from_extrns	; < 256
;

sdei:	pop h
	mov e,m
	inx h
	push h
	mvi d,0
	lhld extrns
	dad d
	mov a,m
	inx h
	mov h,m
	mov l,a
	ret

;
; long-displacement, single-byte external indirection:
;
;	format:		call lsei		; get 8-bit value in L
;			dw offset_from_extrns	; >= 256
;

lsei:	pop h
	mov e,m
	inx h
	mov d,m
	inx h
	push h
	lhld extrns
	dad d
	mov l,m
	ret

;
; short-displacement, single-byte external indirection:
;
;	format:		call ssei		; get 8-bit value in L
;			db offset_from_externs	; < 256
;

ssei:	pop h
	mov e,m	
	inx h
	push h
	mvi d,0
	lhld extrns
	dad d
	mov l,m
	ret

;
; long-displacement, double-byte local indirection:
;
;	format:		call ldli		; get 16-bit value in HL
;			dw offset_from_BC	; >= 256
;

ldli:	pop h
	mov e,m
	inx h
	mov d,m
	inx h
	push h
	xchg
	dad b
	mov a,m
	inx h
	mov h,m
	mov l,a
	ret

;
; short-displacement, double-byte local indirection:
;
;	format:		call sdli		; get 16-bit value in HL
;			db offset_from_BC	; < 256
;

sdli:	pop h
	mov e,m
	inx h
	push h
	xchg
	mvi h,0
	dad b
	mov a,m
	inx h
	mov h,m
	mov l,a
	ret

;
; Flag conversion routines:
;

pzinh:	lxi h,1		;return HL = true if Z set
	rz
	dcx h
	ret

pnzinh:	lxi h,0		;return HL = false if Z set
	rz
	inx h
	ret

pcinh:	lxi h,1		;return HL = true if C set
	rc
	dcx h
	ret

pncinh:	lxi h,0		;return HL = false if C set
	rc
	inx h
	ret

ppinh:	lxi h,1		;return HL = true if P (plus) flag set
	rp
	dcx h
	ret

pminh:	lxi h,1		;return HL = true if M (minus) flag set
	rm
	dcx h
	ret

pzind:	lxi d,1		;return DE = true if Z set
	rz
	dcx d
	ret

pnzind:	lxi d,0		;return DE = false if Z set
	rz
	inx d
	ret

pcind:	lxi d,1		;return DE = true if C set
	rc
	dcx d
	ret

pncind:	lxi d,0		;return DE = false if C set
	rc
	inx d
	ret

ppind:	lxi d,1		;return DE = true if P (plus) flag set
	rp
	dcx d
	ret

pmind:	lxi d,1		;return DE = true if M (minus) flag set
	rm
	dcx d
	ret
	

;	
; Relational operator routines: take args in DE and HL,
; and return a flag bit either set or reset.
;
; ==, >, < :
;

eqwel:	mov a,l		;return Z if HL == DE, else NZ
	cmp e
	rnz		;if L <> E, then HL <> DE
	mov a,h		;else HL == DE only if H == D
	cmp d
	ret

blau:	xchg		;return C if HL < DE, unsigned
albu:	mov a,d		;return C if DE < HL, unsigned
	cmp h
	rnz		;if D <> H, C is set correctly
	mov a,e		;else compare E with L
	cmp l
	ret

bgau:	xchg		;return C if HL > DE, unsigned
agbu:	mov a,h		;return C if DE > HL, unsigned
	cmp d
	rnz		;if H <> D, C is set correctly
	mov a,l		;else compare L with E
	cmp e
	ret

blas:	xchg		;return C if HL < DE, signed
albs:	mov a,h		;return C if DE < HL, signed
	xra d
	jp albu		;if same sign, do unsigned compare
	mov a,d
	ora a
	rp		;else return NC if DE is positive and HL is negative
	stc		;else set carry, since DE is negative and HL is pos.
	ret

bgas:	xchg		;return C if HL > DE, signed
agbs:	mov a,h		;return C if DE > HL, signed
	xra d
	jp agbu		;if same sign, go do unsigned compare
	mov a,h
	ora a
	rp		;else return NC is HL is positive and DE is negative
	stc
	ret		;else return C, since HL is neg and DE is pos


;
; Multiplicative operators: *, /, and %:
;

smod:	mov a,d		;signed MOD routine: return (DE % HL) in HL
	push psw	;save high bit of DE as sign of result
	call tstn	;get absolute value of args
	xchg
 	call tstn
	xchg
	call usmod	;do unsigned mod
	pop psw		;was DE negative?
	ora a		;if not,
	rp		;	all done
	mov a,h		;else make result negative
	cma
	mov h,a
	mov a,l
	cma
	mov l,a
	inx h
	ret

	nop		;maintain address compatibility with some
	nop		; pre-release v1.4's.

usmod:	mov a,h		;unsigned MOD: return (DE % HL) in HL
	ora l
	rz
	push d
	push h
	call usdiv
	pop d
	call usmul
	mov a,h
	cma
	mov h,a
	mov a,l
	cma 
	mov l,a
	inx h
	pop d
	dad d
	ret

smul:	xra a		;signed multiply: return (DE * HL) in HL
	sta tmp
	call tstn
	xchg
	call tstn
	call usmul
smul2:	lda tmp
	rar
	rnc
	mov a,h
	cma
	mov h,a
	mov a,l
	cma
	mov l,a
	inx h
	ret

tstn:	mov a,h
	ora a
	rp
	cma
	mov h,a
	mov a,l
	cma
	mov l,a
	inx h
	lda tmp
	inr a
	sta tmp
	ret

usmul:	push b		;unsigned multiply: return (DE * HL) in HL
	call usm2
	pop b
	ret

usm2:	mov b,h
	mov c,l
	lxi h,0
usm3:	mov a,b
	ora c
	rz
	mov a,b
	rar
	mov b,a
	mov a,c
	rar
	mov c,a
	jnc usm4
	dad d
usm4:	xchg
	dad h
	xchg
	jmp usm3

usdiv:	mov a,h		;unsigned divide: return (DE / HL) in HL
	ora l		;return 0 if HL is 0
	rz
	push b
	call usd1
	mov h,b
	mov l,c
	pop b
	ret


usd1:	mvi b,1
usd2:	mov a,h
	ora a
	jm usd3
	dad h
	inr b
	jmp usd2

usd3:	xchg

usd4:	mov a,b
	lxi b,0
usd5:	push psw
usd6:	call cmphd
	jc usd7
	inx b
	push d
	mov a,d
	cma
	mov d,a
	mov a,e
	cma
	mov e,a
	inx d
	dad d
	pop d
usd7:	xra a
	mov a,d
	rar
	mov d,a
	mov a,e
	rar
	mov e,a
	pop psw
	dcr a
	rz
	push psw
	mov a,c
	ral
	mov c,a
	mov a,b
	ral
	mov b,a
	jmp usd6

sdiv:	xra a		;signed divide: return (DE / HL) in HL
	sta tmp
	call tstn
	xchg
	call tstn
	xchg
	call usdiv
	jmp smul2

cmphd:	mov a,h		;this returns C if HL < DE
	cmp d		; (unsigned compare only used
	rc		;  within C.CCC, not from C)
	rnz
	mov a,l
	cmp e
	ret

;
; Shift operators  << and >>:
;

sderbl:	xchg		;shift DE right by L bits
shlrbe:	inr e		;shift HL right by E bits
shrbe2:	dcr e
	rz
	xra a
	mov a,h
	rar
	mov h,a
	mov a,l	
	rar
	mov l,a
	jmp shrbe2

sdelbl:	xchg		;shift DE left by L bits
shllbe:	inr e		;shift HL left by E bits
shlbe2:	dcr e
	rz
	dad h
	jmp shlbe2


;
; Routines to 2's complement HL and DE:
;

cmh:	mov a,h
	cma
	mov h,a
	mov a,l
	cma
	mov l,a
	inx h
	ret

cmd:	mov a,d
	cma
	mov d,a
	mov a,e
	cma
	mov e,a
	inx d
	ret


;
; The following routines yank a formal parameter value off the stack
; and place it in both HL and A (low byte), assuming the caller
; hasn't done anything to its stack pointer since IT was called.
;
; The mnemonics are "Move Arg #n To HL",
; where arg #1 is the third thing on the stack (where the first
; and second things are, respectively, the return address of the
; routine making the call to here, and the previous return
; address to the routine which actually pushed the args on the
; stack.) Thus, a call to "ma1toh" would return with the first
; passed parameter in HL and A; "ma2toh" would return the second,
; etc. Note that if the caller has pushed [n] items on the stack
; before calling "ma [x] toh", then the [x-n]th formal parameter
; value will be returned, not the [x]th.
;

ma1toh:	lxi h,4		;get first arg
ma0toh:	dad sp
	mov a,m
	inx h
	mov h,m
	mov l,a
	ret

ma2toh:	lxi h,6		;get 2nd arg
	jmp ma0toh

ma3toh:	lxi h,8		;get 3rd arg
	jmp ma0toh

ma4toh:	lxi h,10	;get 4th arg
	jmp ma0toh

ma5toh:	lxi h,12	;get 5th arg
	jmp ma0toh

ma6toh:	lxi h,14	;get 6th arg
	jmp ma0toh

ma7toh:	lxi h,16	;get 7th arg
	jmp ma0toh

;
; This routine takes the first 7 args on the stack
; and places them contiguously at the "args" ram area.
; This allows a library routine to make one call to arghak
; and henceforth have all it's args available directly
; through lhld's instead of having to hack the stack as it
; grows and shrinks. Note that arghak should be called as the
; VERY FIRST THING a function does, before even pushing BC.
;

arghak:	lxi d,args	;destination for block move in DE
	lxi h,4		;pass over two return address
	dad sp		;source for block move in HL
	push b		;save BC
	mvi b,14	;countdown in B
arghk2:	mov a,m		;copy loop
	stax d
	inx h
	inx d
	dcr b
	jnz arghk2	
	pop b		;restore BC
	ret

;
; UP TO THIS POINT, ABSOLUTELY NO CHANGES SHOULD EVER BE MADE
; TO THIS SOURCE FILE (except for customizing the EQU statements
; at the beginning of the file).
;


;
; This routine is called first to do argc & argv processing (if
; running under CP/M) and some odds and ends initializations:
;

init:	pop h		;store return address
	shld tmp2	; somewhere safe for the time being

	IF CPM
	lxi h,arglst-2	;set the "argv" that the C main program
	ENDIF
	
	IF NOT CPM
	lxi h,0
	ENDIF

	push h		; will get.

			;Initialize storage allocation pointers:
	lhld freram	;get address after end of externals
	shld allocp	;store at allocation pointer (for "sbrk.")
	lxi h,1000	;default safety space between stack and
	shld alocmx	; highest allocatable address in memory 
			; (for use by "sbrk".).

			;Initialize random seed:
	lxi h,59dch	;let's stick something wierd into the
	shld rseed	;first 16 bits of the random-number seed

			;Initialize I/O hack locations:
	mvi a,0dbh	;"in" op, for "in xx; ret" subroutine
	sta iohack
	mvi a,0d3h	;"out" op for "out xx; ret" subroutine
	sta iohack+3
	mvi a,0c9h	;"ret" for above sobroutines
	sta iohack+2	;the port number is filled in by the
	sta iohack+5	;"inp" and "outp" library routines.

			;Initialize DMA video parameters:
	IF DMAVIO	;if we're using DMA video routines,
	lxi h,0cc00h	;set up default values (may be changed
	shld pbase	;to whatever suits). Video board address,
	lxi h,16
	shld xsize	;# of lines,
	lxi h,64
	shld ysize	;# of columns,
	lxi h,1024
	shld psize	;and total # of characters on screen
	ENDIF

	IF CPM		;under CP/M: clear console, process ARGC & ARGV:
	mvi c,11	;interrogate console status to see if there
	call bdos	;  happens to be a stray character there...

	ora a		;(used to be `ani 1'...they tell me this works
	nop		; better for certain bizarre CP/M-"like" systems)

	jz initzz
	mvi c,1		;if input present, clear it
	call bdos

initzz:	lxi h,tbuff	;if arguments given, process them.
	lxi d,comlin	;get ready to copy command line
	mov b,m		;first get length of it from loc. base+80h
	inx h
	mov a,b
	ora a		;if no arguments, don't parse for argv
	jnz initl
	lxi d,1		;set argc to 1 in such a case.
	jmp i5

initl:	mov a,m		;ok, there are arguments. parse...
	stax d		;first copy command line to comlin
	inx h
	inx d
	dcr b
	jnz initl
	xra a		;place zero following line
	stax d

	lxi h,comlin	;now compute pointers to each arg
	lxi d,1		;arg count
	lxi b,arglst	;where pointers will all go
	xra a		;clear "in a string" flag
	sta tmp1
i2:	mov a,m		;between args...
	inx h
	cpi ' '
	jz i2
	ora a
	jz i5		;if null byte, done with list
	cpi '"'
	jnz i2a		;quote?
	sta tmp1	;yes. set "in a string" flag
	jmp i2b	

i2a:	dcx h
i2b:	mov a,l		;ok, HL is a pointer to the start
	stax b		;of an arg string. store it.
	inx b
	mov a,h
	stax b
	inx b
	inx d		;bump arg count
i3:	mov a,m
	inx h		;pass over text of this arg
	ora a		;if at end, all done
	jz i5
	push b		;if tmp1 set, in a string 
	mov b,a		; (so we have to ignore spaces)
	lda tmp1
	ora a
	mov a,b
	pop b
	jz i3a
	cpi '"'		;we are in a string.
	jnz i3		;check for terminating quote
	xra a		;if found, reset "in string" flag
	sta tmp1
	dcx h
	mov m,a		;and stick a zero byte after the string
	inx h		;and go on to next arg
i3a:	cpi ' '		;now find the space between args
	jnz i3
	dcx h		;found it. stick in a zero byte
	mvi m,0
	inx h
	jmp i2		;and go on to next arg

i5:	push d		;all done finding args. Set argc.

	mvi b,nfcbs	;now initialize all the file info
	lxi h,fdt	;(just zero the fd table)
i6:	mvi m,0
	inx h
	dcr b
	jnz i6
	ENDIF

	IF NOT CPM	;if not under CP/M, force ARGC value	
	lxi h,1		; of one.
	push h
	ENDIF

	xra a
	sta ungetl	;clear the push-back byte
	sta lastc	;and last character byte

	lhld tmp2
	pchl		;all done initializing.

;
; General purpose error value return routine:
;

verror:	lxi h,-1	;general error handler...just
	ret		;returns -1 in HL

;
; Here are file I/O handling routines, only needed under CP/M:
;

;
; Close any open files and reboot:
;

vexit:
	IF CPM		;if under CP/M, close all open files
	mvi a,7+nfcbs	;start with largest possible fd
exit1:	push psw	;and scan all fd's for open files
	call vfgfd	;is file whose fd is in A open?
	jc exit2	;if not, go on to next fd
	mov l,a		;else close the associated file
	mvi h,0
	push h
	call vclose
	pop h
exit2:	pop psw
	dcr a		;and go on to next one
	cpi 7
	jnz exit1
	ENDIF

	jmp exitad	;done closing; now reboot CP/M or whatever.

;
; Close the file whose fd is 1st arg:
;

	IF CPM		;here comes a lot of CP/M stuff...
vclose:	call setdma	;library function just jumps here.
	call ma1toh	;get fd in A
	call vfgfd	;see if it is open
	jc verror	;if not, complain
	mov a,m
	ani 4

	IF NOT MPM2	;if not MP/M, and
	jz close2	;the file isn't open for write, don't bother to close
	ENDIF

	IF MPM2		;always close all files under MP/M
	nop
	nop
	nop
	ENDIF

	push h		;save fd table entry addr
	call ma2toh	;move arg1 to A
	push b
	call vfgfcb	;get the appropriate fcb address
	xchg		;put it in DE
	mvi c,16	;get BDOS function # for close
	call bdos	;and do it!
	pop b
	pop h
close2:	mvi m,0		;close logically
	cpi 255		;if 255 comes back, we got problems
	lxi h,0	
	rnz		;return 0 if OK
	dcx h		;return -1 on error
	ret

;
; Determine status of file whose fd is in A...if the file
; is not open, return C flag set, else clear C flag:

vfgfd:	call setdma
	mov d,a
	sui 8
	rc		;if fd < 8, error
	cpi nfcbs
	cmc		;don't allow too big an fd either
	rc
	push d
	mov e,a		;OK, we have a value in range. Now
	mvi d,0		;  see if the file is open or not
	lxi h,fdt
	dad d
	mov a,m
	ani 1		;bit 0 is high if file is open
	stc
	pop d
	mov a,d
	rz		;return C set if not open
	cmc
	ret		;else reset C and return

;
; Set up a CP/M file control block at HL with the file whose
; simple null-terminated name is pointed to by DE:
; Format for filename must be: "[white space][d:]filename.ext"
;

vsetfcb:  call setdma	;set up an fcb at HL for filename at DE
	push b
	call igwsp	;ignore blanks and tabs	
	mvi b,8
	push h
	inx d
	ldax d
	dcx d
	cpi ':'		;default disk byte value is 0
	mvi a,0		; (for currently logged disk)
	jnz setf1
	ldax d		;oh oh...we have a disk designator
	call mapuc	;make it upper case
	sui '@'		;and fudge it a bit
	inx d
	inx d
setf1:	mov m,a
	inx h
	call patchnm	;now set filename and pad with blanks
	ldax d
	cpi '.'		;and if an extension is given,
	jnz setfcb2	
	inx d
setfcb2 mvi b,3		;set the extension and pad with blanks
	call setnm
	xra a		;and zero the appropriate fields of the fcb
	mov m,a
	lxi d,20
	dad d
	mov m,a
	inx h
	jmp setfcb3	;finish up elsewhere to keep addresses consistent
			;with prior releases


;
; This routine copes up to B characters from memory at DE to 
; memory at HL and pads with blanks on the right:
;


setnm:	push b
setnm1:	ldax d
	cpi '*'		;wild card?
	mvi a,'?'	;if so, pad with ? characters
	jz pad2

setnm2:	ldax d
	call legfc	;next char legal filename char?
	jc pad		;if not, go pad for total of B characters
	mov m,a		;else store
	inx h
	inx d
	dcr b
	jnz setnm1	;and go for more if B not yet zero
	pop b
setnm3:	ldax d		;skip rest of filename if B chars already found
	call legfc
	rc
	inx d
	jmp setnm3

pad:	mvi a,' '	;pad with B blanks
pad2:	mov m,a		;pad with B instances of char in A
	inx h
	dcr b
	jnz pad2
	pop b
	ret

;
; Test if char in A is legal character to be in a filename:
;

legfc:	call mapuc
	cpi '.'		; '.' is illegal in a filename or extension
	stc
	rz
	cpi ':'		;so is ':'
	stc 	
	rz
	cpi 7fh		;delete is no good
	stc
	rz
	cpi '!'		;if less than exclamation pt, not legal char
	ret		;else good enough

;
; Map character in A to upper case if it is lower case:
;

mapuc:	cpi 'a'
	rc
	cpi 'z'+1
	rnc
	sui 32		;if lower case, map to upper
	ret

;
; Ignore blanks and tabs at text pointed to by DE:
;

igwsp:	dcx d
igwsp1:	inx d
	ldax d
	cpi ' '
	jz igwsp1
	cpi 9
	jz igwsp1
	ret


;
; This routine does one of two things, depending
; on the value passed in A.
;
; If A is zero, then it finds a free file slot
;  (if possible), else returns C set.
;
; If A is non-zero, then it returns the address
; of the fcb corresponding to an open file whose
; fd happens to be the value in A, or C set if there
; is no file associated with fd.
;

vfgfcb:	push b
	call setdma	
	ora a		;look for free slot?
	mov c,a
	jnz fgfc2	;if not, go away
	mvi b,nfcbs	;yes. do it...
	lxi d,fdt
	lxi h,fcbt
	mvi c,8
fgfc1:	ldax d
	ani 1
	mov a,c
	jnz fgfc1a	;found free slot?
	pop b		;yes. all done.
	ret

fgfc1a:	push d
	lxi d,36	;fcb length to accommodate random I/O
	dad d
	pop d
	inx d
	inr c
	dcr b
	jnz fgfc1
fgfc1b:	stc
	pop b
	ret		;return C if no more free slots

fgfc2:	call vfgfd	;compute fcb address for fd in A:
	jc fgfc1b	;return C if file isn't open

	sui 8
	mov l,a		;put (fd-8) in HL
	mvi h,0
	dad h		;double it
	dad h		;4*a
	mov d,h		;save 4*a in DE
	mov e,l
	dad h		;8*a
	dad h		;16*a
	dad h		;32*a
	dad d		;36*a
	xchg		;put 36*a in DE
	lxi h,fcbt	;add to base of table
	dad d		;result in HL
	mov a,c		;and return original fd in A
	pop b
	ret

setdma:	push d		;just a preventative measure,
	push b		;since the default I/O buffer
	push psw	;tends to magically change
	push h		;around by itself when left
	mvi c,26	;in CP/M's hands !!
	lxi d,tbuff
	call bdos
	pop h
	pop psw
	pop b
	pop d
	ret

	ENDIF		;end of CP/M-related file I/O routines


	IF NOT CPM
main:	equ $		;where main program resides when not under CP/M
			;(under CP/M, the data area comes first)
	ENDIF


;
; Ram area:
;

	IF NOT CPM	;if not under CP/M, use custom ram area address
	org ram
	ENDIF

room:	ds 30		;room for random stuff

pbase:	ds 2		;screen-DMA address
ysize:	ds 2		;screen width
xsize:	ds 2		;screen height
psize:	ds 2		;screen length

rseed:	ds 8		;the random generator seed

args:	ds 14		;"arghak" puts args passed on stack here.

iohack:	ds 6		;room for I/O subroutines for use by "inp"
			;and "outp" library routines

allocp:	ds 2		;pointer to free storage for use by "sbrk" func
alocmx:	ds 2		;highest location to be made available to the
			;storage allocator

tmp:	equ room	;this is misc. garbage space
tmp1:	equ room+1
tmp2:	equ room+2
tmp2a:	equ room+4
ungetl:	equ room+6	;where characters are "ungotten"
lastc:	equ room+7	;last char typed

;
;--------------------------------------------------------------------------
; The following data areas are needed only if running under CP/M:
;
	IF CPM
;
; The fcb table (fcbt): 36 bytes per file control block
;

fcbt:	ds 36*nfcbs	;reserve room for fcb's (extra byte for IMDOS)

;
; The fd table: one byte per file specifying r/w/open as follows:
; 	bit 0 is high if open, low if closed
; 	bit 1 is high if open for read
; 	bit 2 is high if open for write
; (both b1 and b2 may be high)
;

fdt:	ds nfcbs	;one byte per fcb tells if it is active, r/w, etc.

;
; The command line is copied here by init:
;

comlin:	ds 131		;copy of the command line pointed to by entries
			;in arglst

;
; This is where "init" places the array of argument pointers:
;

arglst:	ds 60		;the "argv" paramater points here (well,
			;actually to 2 bytes before arglst). Thus,
			;up to 30 parameters may be passed to "main"
	ENDIF		;(enough for you, Andy?)

;
; End of CP/M-only data area
;---------------------------------------------------------------------------

	IF CPM
main:	equ $		;where "main" program will be loaded under CP/M

	ENDIF

	end

