  
;LOADSYS.ASM   REASSEMBLY OF CPMLDR.COM
;28 MAY 85 RHP

; THIS PROGRAM READS THE FILE 'SYSTEM.FIL' OFF OF THE DISK
; AND RELOCATES THE FILE TO THE OPERATING POSITION.
;
;APPEND A TRUNKATED BIOS THAT NEED ONLY DO CONSOLE WRITE, AND DISK 
; READ FUNCTIONS.
;
;THIS PROGRAM MAY BE RESIDENT ON THE SYSTEM TRACKS OR RUN 
; AS A TRANSIENT PROGRAM.

;BIOS JUMP TABLE
;
BIOSS	EQU	0B00H
;
@BOOT	EQU	BIOSS + 000H	;COLD BOOT
@CONOT	EQU	BIOSS + 00CH	;CONSOLE OUT
@HOME	EQU	BIOSS + 018H	;HOME TO TRACK 0
@SLDSK	EQU	BIOSS + 01BH	;SELECT DISK
@STTRK	EQU	BIOSS + 01EH	;SET TRACK
@STSEC	EQU	BIOSS + 021H	;SET SECTOR
@STDMA	EQU	BIOSS + 024H	;SET DMA ADDRESS
@READ	EQU	BIOSS + 027H	;READ A SECTOR
@SCTRN	EQU	BIOSS + 030H	;TRANSLATE A PHYSICAL SECTOR
@MOV	EQU	BIOSS + 04BH	;MOVE BETWEEN BANKS
;
;	BDOS CALL FUNCTIONS
;
PRINT$STRING	EQU	000H	;DE=STRING ADDRESS
DISK$RESET	EQU	001H	;NO PARAMETERS
SEL$DISK	EQU	002H	;  WAS FUNCT E
SRCH$FIRST	EQU	003H	;DE=FCB ADDR, A=DIR CODE
READ$SEQUENT	EQU	004H	;DE=FCB ADDR, A=ERROR CODE
RETURN$CUR$DSK	EQU	005H	;  WAS FUNCT 19
SET$DMA$ADDR	EQU	006H	;DE=DMA ADDRESS
;
FCB	EQU	0005CH
TBUFF	EQU	00080H
;
	ORG	00100H
;
;
	LXI	SP,STACK1	;SET UP THE STACK
	CALL	@BOOT		;COLD BOOT
;
	MVI	C,DISK$RESET	;RESET DISK
	CALL	BDOS		;
;
	MVI	C,PRINT$STRING	;PRINT STRING
	LXI	D,MSBANR	;SIGN ON BANNER
	CALL	BDOS
;
	MVI	C,SRCH$FIRST	;LOCATE FILE ON DISK
	LXI	D,FCBT
	CALL	BDOS
;
	CPI	0FFH		;CHECK FOR ERROR
	LXI	D,MSOPEN	;OPEN ERROR MESSAGE
	JZ	QUITS		;HALT
;
	LXI	D,TBUFF		;POINT TO TEMP BUFFER
	CALL	DMASET		;IDENTIFY AS DMA ADDR
	CALL	READSECT
	LXI	H,TBUFF		;POINT TO TEMP BUFFER
	LXI	D,HEDR1		;POINT TO NEW STORAGE
	MVI	C,006H		;MOVE 6 BYTES
;
MOVER:	MOV	A,M		;MOVE MEMORY BLOCK
	STAX	D
	INX	D
	INX	H
	DCR	C
	JNZ	MOVER
;
	CALL	READSECT	;PUT SECTOR INTO TEMP BUFFER
	MVI	C,PRINT$STRING	;PRINT OUT THE CONTENTS OF
	LXI	D,TBUFF		;THE STRING JUST READ
	CALL	BDOS
;
	LDA	HEDR2 		;GET 2ND GYTE OF HEADER
	MOV	H,A		;PUT IN H
	LDA	HEDR1 		;GET FIRST BYTE OF HEADER
	CALL	READFILE
;
	LDA	HEDR4 		;GET 4TH BYTE OF HEADER
	ORA	A		;SET FLAGS
	JZ	SKIPRD		;SKIP IF EMPTY
	MOV	H,A
	LDA	HEDR3 
	CALL	READFILE
;
SKIPRD:	LXI	H,FCB+1		;1ST CHAR OF FILENAME
	MOV	A,M		;GET IT
	CPI	024H		;IS IT A $?
	JNZ	SKIP2		;IF NOT THEN SKIP
	INX	H		;2ND CHAR OF FILENAME
	MOV	A,M		;GET IT
	CPI	042H		;IS IT A 'B'?
	CZ	REBOOT		;THEN GO TO REBOOT
SKIP2:	LXI	SP,HEDR5	;ELSE JUMP TO ADDRESS OF
	RET			; BYTES 5,6 OF HEADER 

READFILE:
	ORA	A		;SET FLAGS
	MOV	D,A		;FIRST BYTE TO D
	MVI	E,000H		;E=0
	MOV	A,H		;SECOND BYTE TO A
	RAL			;X2
	MOV	H,A		;BACK TO H
READLOOP:
	XCHG			;SWAP DE,HL
	LXI	B,0FF80H	; -80	
	DAD	B		;SUBTRACT 80 FROM 2ND BYTE
	XCHG			;SWAP AGAIN
	PUSH	D		;SAVE REGISTERS
	PUSH	H
	CALL	DMASET		;POINT TO NEW ADDRESS
	CALL	READSECT	;READ DISK INTO THERE	
	POP	H		;RESTORE REGISTERS
	POP	D
	DCR	H		;H-1
	JNZ	READLOOP		;LOOP TIL H=0
	RET

DMASET:	MVI	C,SET$DMA$ADDR
	CALL	BDOS
	RET

READSECT:
	MVI	C,READ$SEQUENT
	LXI	D,FCBT		;POINT TO FILE CONTROL BLOCK
	CALL	BDOS
;
	ORA	A		;SET FLAGS
	LXI	D,MSREAD	;READ ERROR MESSAGE
	RZ			;NO ERROR
QUITS:	MVI	C,PRINT$STRING	;ELSE PRINT ERROR MSG
	CALL	BDOS
;
	DI			;KILL INTERRUPTS
	HLT			;FREEZE
;
REBOOT:	RST	07		
	RET
;
BDOS:	XCHG			;MSG POINTER IN HL
	SHLD	INFO		;STORE MSG LOCATION
	XCHG			;MSG POINTER BACK TO DE
	MOV	A,C
	CPI	00EH
	JC	BDOS1
	STA	SAVEC 		;SAVE # IN C
	XRA	A		;CLEAR ACCUMULATOR
	STA	ZERO3 		;SET TO FALSE 
	STA	CURDSK
	STA	ZERO4 
BDOS1:	MOV	A,E		;GET LO BYTE OF MEM ADDRESS
	STA	LINFO 		;SAVE IT
	LXI	H,00000H	;CLEAR HL
	SHLD	ARET		;RETURN VALUE DEFAULTS TO 0000
	SHLD	FCBDSK		;  SAVE USERS STACK PTR, SET TO LOCAL STACK
	DAD	SP		;MOVE SP TO HL
	SHLD	ENTSP		;ENTSP=STACK POINTER
	LXI	SP,LSTACK	;POINT STACK TO END OF C7 TABLE
	LXI	H,GOBACK	;RETURN HERE AFTER ALL FUNCTIONS
	PUSH	H		;JUMP GOBACK EQUIV TO RETURN
	MOV	A,C
	CPI	006H		;NUMBER OF FUNCTIONS
	JNC	BDOS2		;INVALID NUMBER
	MOV	C,E		;POSSIBLE OUTPUT CHAR. TO C
	LXI	H,FUNCTAB	;POINT TO FUNCTION TABLE
	JMP	BDOS3
;
BDOS2:	SBI	064H
	JC	SEARCH$FIN
BDOS3:	MOV	E,A		;E GETS A
	MVI	D,000H		;DE=FUNCTION, HL=.CIOTAR
	DAD	D		;ADD DE, HL
	DAD	D		;ADD DE, HL
	MOV	E,M		;MEMORY WORD TO DE
	INX	H
	MOV	D,M		;DE=FUNCTION ARGUMENT
	LHLD	INFO		;POINT TO MESSAGE WITH HL
	XCHG			;DE POINTS MSG, HL TO DEST.
	PCHL			;JUMP TO ROUTINE
;
FUNCTAB:
	DW	FUNCT00		;FUNCTION 9=PRINT STRING
	DW	FUNCT01		;FUNCTION D=RESET DISK
	DW	FUNCT02		;FUNCTION E=SELECT DISK
	DW	FUNCT03		;FUNCTION F=OPEN FILE
	DW	FUNCT04		;FUNCTION 14=READ SEQUENTIAL
	DW	FUNCT05		;FUNCTION 19=RETURN CURRENT DISK
	DW	FUNCT06		;FUNCTION 1A=SET DMA ADDRESS
;
;	COMPUTE CHARACTER POSITION/WRITE CONSOLE
;	CHARACTER FROM C
;	COMPCOL = TRUE IF COMPUTING COLUMN POSITION
;
CONOUT:	LDA	COMPCOL
	ORA	A
	JNZ	COMPOUT		;WRT CHAR FROM C, THEN COMP COL.
	PUSH	B
	CALL	@CONOT		;PRINT CHARACTER ON CONSOLE
	POP	B
COMPOUT:MOV	A,C		;RECALL CHAR & COMPUTE COL POSIT
	LXI	H,COLUMN	;A=CHAR, HL=.COLUMN
	CPI	07FH		;RUBOUT?
	RZ			;NO COL CHANGE IF NULLS
	INR	M		;NEXT COLUMN
	CPI	020H		;SPACE?
	RNC			;RETURN IF GRAPHIC,ELSE RESET COL.
	DCR	M		;NEXT COLUMN
	MOV	A,M
	ORA	A
	RZ			;RET IF ZERO, ELSE MAYBE BS OR EOL
	MOV	A,C		;CHAR IN A
	CPI	008H		;CTL H?
	JNZ	NOTBACKSP	;ELSE BACKSPACE
	DCR	M		;NEXT COLUMN
	RET
;
;	NOT A BACKSPACE CHARACTER, EOL?
;
NOTBACKSP:
	CPI	00AH		;LINEFEED?
	RNZ
	MVI	M,000H		;END OF LINE, COL=0
	RET
;
;	PRINT STRING  DE=STRING ADDRESS, ENDS IN $ 
;
FUNCT00:XCHG			;WAS LHLD INFO
	MOV	C,L
	MOV	B,H		;BC=STRING ADDRESS
;
;	PRINT MESSAGE UNTIL M(BC) = $
;
PRINT:	LXI	H,DOLLAR
	LDAX	B
	CMP	M		;$?
	RZ			;STOP ON $, ELSE MORE TO PRINT
	INX	B
	PUSH	B
	MOV	C,A
	MOV	A,C
	CPI	009H		;TAB?
	JNZ	CONOUT		;IF NO TAB, DIRECT TO CONOUT
TAB0:	MVI	C,020H		;ELSE CHANGE TO SPACE
	CALL	CONOUT
	LDA	COLUMN		
	ANI	007H		;COLUMN MOD 8 = 0 7
	JNZ	TAB0		;BACK FOR ANOTHER IF NOT
	POP	B
	JMP	PRINT
;
DISKEOF:MVI	A,001H		;SET LRET=1
STA$RET:STA	ARET 		;STORE THE REGISTER TO ARET
FNC$RET:RET	;JMP GOBACK (POP STACK FOR NON CPM FUNCTIONS)
;
SEL$ERROR:
	LXI	B,MSBDOS	;BDOS ERROR MESSAGE
	CALL	PRINT
	LXI	B,MSSLCT	;SELECT MSG
 	JMP	QUIT2
;
PERMSG:	LXI	B,MSBDOS	;BDOS ERROR MESSAGE
	CALL	PRINT
	LXI	B,MSPERM	;PERMANENT MSG
QUIT2:	CALL	PRINT
;
	DI			;KILL INTERRUPTS
	HLT			;FREEZE
;
SUBDH:	MOV	A,E		;SUBTRACT HL = DE-HL
	SUB	L
	MOV	E,A
	MOV	A,D
	SBB	H
	MOV	D,A
	RNC
	DCR	B
	RET
;
RECOK8:	LDAX	D		;A GETS MEMORY POINTED BY DE
	CMP	M		;COMPARE TO MEMORY POINTED BY HL
	RNZ			;QUIT IF DIFFERENT
	INX	H		;NEXT HL
	INX	D		;NEXT DE
	DCR	C		;TIL COUNT IN C
	RZ			; DROPS TO ZERO
	JMP	RECOK8		;ELSE TRY AGAIN
;
;	MOVE DATA OF LENGTH C FROM DE TO HL
;
MOVE:	INR	C		;IN CASE IT IS ZERO
MOVE0:	DCR	C		;FOR COUNT IN C
	RZ			;DONE?
	LDAX	D		;MEMORY POINTED BY DE TO A
	MOV	M,A		;MOVE TO MEMORAY POINTED BY HL
	INX	D		;NEXT DE
	INX	H		;NEXT HL
	JMP	MOVE0		;KEEP LOOPING TIL C=0
;
;	SELECT DISK DRIVE AT CURDSK, FILL THE BASE ADDRESS
;	CURTKA-ALLOCA, THEN FILL VALUES IN DISK PARAMETER BLOCK
;
SELECTDISK:
	MOV	C,D		;CURRENT DISK TO C
				;LSB OF C=0 IF NOT YET LOGGED IN
	CALL	@SLDSK		;SELECT DISK, SETS HL
	MOV	A,H
	ORA	L
	RZ			;HL=0 FOR ERROR ELSE DISK HEADER
	MOV	E,M
	INX	H
	MOV	D,M
	INX	H		;DE=.TRAN
	INX	H
	INX	H		;.CDRMAX
	SHLD	CURTRKA
	INX	H
	INX	H		;HL=.CURREC
	SHLD	CURRECA
	INX	H
	INX	H
	INX	H
	INX	H
	INX	H
	INX	H
	XCHG
	SHLD	TRANV		;.TRAN VECTOR
	LXI	H,BUFFB		;DE=SOURCE, HL=DEST FOR MOVE 
	MVI	C,00DH		;ADDLIST
	CALL	MOVE		;ADDLIST FILLED
	LHLD	BUFFB		;NOW FILL DPB
	XCHG			;DE IS SOURCE
	LXI	H,SECTPT	;HL IS DEST.
	MVI	C,011H		;DPBLIST
	CALL	MOVE		;DATA FILLED
	LHLD	MAXALL		;LARGEST ALLOC NUMBER
	MOV	A,H		;00 INDICATES <255
	LXI	H,SINGLE
	MVI	M,0FFH		;FF=TRUE, ASSUME A=00
	ORA	A
	JZ	RETSELECT
	MVI	M,000H		;M=FALSE
RETSELECT:			;HIGH ORDER OF MAXALL NOT 0
				; USE DOUBLE DM
	STC
	RET
;
;	MOVE TO TRACK 0
;
HOME:	CALL	@HOME		;HOME THE DISK
	XRA	A		;ZERO THE ACCCUM
	LHLD	CURTRKA
	MOV	M,A
	INX	H
	MOV	M,A		;CURTRAK=0000
	LHLD	CURRECA
	MOV	M,A
	INX	H
	MOV	M,A		;CURREC=0000
	INX	H
	MOV	M,A
	RET
;
;
RDBUFF1:
	LXI	H,ARECORD
	MOV	E,M
	INX	H
	MOV	D,M
	INX	H
	MOV	B,M
	RET
;
;
;
RDBUFF:	CALL	RDBUFF1
	CALL	@READ		;READ A SECTOR
	ORA	A		;SET FLAGS
	RZ			;OK
	MOV	C,A		;SAVE ERROR
	CPI	003H		;CHECK ERROR
	JC	PERMSG		;PERMANENT BDOS ERROR
	MVI	C,001H		;ELSE CHANGE ERROR FLAG
	JMP	PERMSG		;PERMANENT BDOS ERROR

;
;	SEEK RECORD CONTAINING THE CURRENT DIRECTORY ENTRY
;
SEEKDIR:LHLD	DCNT		;DIRECTORY COUNTER TO COUNTER
	MVI	C,002H		;DSKSHF
	CALL	HLROTR		;VALUE TO HL
	MVI	B,000H
	XCHG
	LXI	H,ARECORD
	MOV	M,E		;STORE ARECORD
	INX	H
	MOV	M,D		;STORE DREC
	INX	H
	MOV	M,B
	RET
;
;	SEEK THE TRACK GIVEN BY ARECORD (ACTUAL RECORD)
;
SEEK:	LHLD	CURTRKA
	MOV	C,M
	INX	H
	MOV	B,M
	PUSH	B
	LHLD	CURRECA
	MOV	E,M
	INX	H
	MOV	D,M
	INX	H
	MOV	B,M
	LHLD	ARECORD
	LDA	SHIFTED
	MOV	C,A
SEEK0:	MOV	A,L		;LOOP WHILE ARECORD < CURREC
	SUB	E
	MOV	A,H
	SBB	D
	MOV	A,C
	SBB	B
	PUSH	H
	JNC	SEEK1		;SKIP IF ARECORD >= CURREC
	LHLD	SECTPT		;CURREC = SECTPT
	CALL	SUBDH		;SUBTRACT HL FROM DE
	POP	H
	XTHL
	DCX	H		;CURTRK = CURTRK-1
	XTHL
	JMP	SEEK0		;FOR ANOTHER TRY
;
SEEK1:	LHLD	SECTPT		;LOOK WHILE ARECORD >= (T:CURREC+SECTP)
	MOV	A,E		;ADD HL AND DE
	ADD	L
	MOV	E,A
	MOV	A,D
	ADC	H
	MOV	D,A
	RNC
	INR	B
	POP	H
	MOV	A,L
	SUB	E
	MOV	A,H
	SBB	D
	MOV	A,C
	SBB	B
	JC	SEEK2		;SKIP IF T>ARECORD, CURREC=T,
	XTHL			; CURTRK=CURTRK+1
	INX	H
	XTHL
	PUSH	H
	JMP	SEEK1		;FOR ANOTHER TRY
;
SEEK2:	XTHL
	PUSH	H
	LHLD	SECTPT
	CALL	SUBDH		;SUBTRACT HL FROM DE
	POP	H		;ARRIVE HERE WITH UPDATED VALUES
				; IN EACH REGISTER
	PUSH	D
	PUSH	B
	PUSH	H		;TO STACK FOR LATER
				;STACK=BC=ARECORD, DE=CURREC, HL=CURTRK
	XCHG
	LHLD	OFFSET
	DAD	D		;HL=CURTRK+OFFSET
	MOV	B,H
	MOV	C,L
	SHLD	TRACK
	CALL	@STTRK		;TRACK SET UP. NOTE THAT BC-CURTRK
				; IS DIFFERENCE TO MOVE IN BIOS
	POP	D		;RECALL CURTRK
	LHLD	CURTRKA
	MOV	M,E
	INX	H
	MOV	M,D		;CURTRK UPDATED
				; NOW COMPUTE SECTOR AS ARECORD-CURREC
	POP	B		
	POP	D		;RECALL CURREC
	LHLD	CURRECA
	MOV	M,E
	INX	H
	MOV	M,D
	INX	H
	MOV	M,B
	POP	B		;BC=ARECORD, DE=CURREC
	MOV	A,C
	SUB	E
	MOV	L,A
	MOV	A,B
	SBB	D
	MOV	H,A
	CALL	SEEK3
	MOV	B,H
	MOV	C,L
	LHLD	TRANV
	XCHG			;BC=SECTOR#, DE=.TRAN
	CALL	@SCTRN		;HL=TRAN (SECTOR)
	MOV	C,L
	MOV	B,H
	SHLD	SAVSEC
	CALL	@STSEC		;SECTOR SELECTED
	LHLD	DMAADDR
	MOV	C,L
	MOV	B,H
	JMP	@STDMA		;SET DMA
;
SEEK3:	LDA	ZERO1
	MOV	C,A
	JMP	HLROTR
;
;	COMPUTE DISK MAP POSITION FOR VRECORD TO HL
;
DM$POSITION:
	LXI	H,BLKSHF
	MOV	C,M		;SHIFT COUNT TO C
	LDA	VRECORD		;CURRENT VIRTUAL RECORD TO A
DMPOS0:	ORA	A
	RAR
	DCR	C
	JNZ	DMPOS0		;A=SBR(VRECORD,BLKSHF)=
				; VRECORD/2**(SEC/BLOCK)
	MOV	B,A		;SAVE IT FOR LATER ADDITION
	MVI	A,008H
	SUB	M		;8-BLKSHF TO ACCUMULATOR
	MOV	C,A		;EXTENT SHIFT COUNT IN REGISTER C
	LDA	EXTVAL		;EXTENT VALUE ANI EXTMASK
;
;	BLKSHF=3,4,5,6,7, C=5,4,3,2,1
;	SHIFT IS 4,3,2,1,0
;
DMPOS1:	DCR	C
	JZ	DMPOS2
	ORA	A
	RAL
	JMP	DMPOS1
;
;	ARRIVE HERE WITH A=SHL(EXT AND EXTMSK), 7-BLKSHF)
;
DMPOS2:	ADD	B		;ADD THE PREVIOUS SHR(VRECORD,BLKSHF)
				;VALUE. A IS ONE OF THE VALUES,
				;DEPENDING ON 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
;
;	GET DISK MAP VALUE FROM POSITION GIVEN BY BC
;
GETDM:				;POINT TO 16TH CHAR IN INFO
	LHLD	INFO		;BASE ADDRESS OF FCB
	LXI	D,00010H	;DSKMAP
	DAD	D		;HL=.DSKMAP
	DAD	B		;INDEX BY SINGLE BYTE VALUE
	LDA	SINGLE		;SINGLE BYTE/MAP ENTRY?
	ORA	A
	JZ	GETDMD		;GET DISK MAP SINGLE BYTE
	MOV	L,M
	MOV	H,B
	RET			;WITH HL=00BB
;
GETDMD:	DAD	B		;HL=.FCB(DM+I*2)
				; DOUBLE PRECISION VALUE RETURNED
	MOV	A,M
	INX	H
	MOV	H,M
	MOV	L,A
	RET
;
;	COMPUTE DISK BLOCK NUMBER FROM CURRENT FCB
;
INDEX:	CALL	DM$POSITION	;0....15 IN REGISTER A
	STA	DMPOS 
	MOV	C,A
	MVI	B,000H
	CALL	GETDM		;VALUE TO HL
	SHLD	ARECORD
	MOV	A,L
	ORA	H
	RET
;
;	COMPUTE ACTUAL RECORD ADDRESS, ASSUMING INDEX CALLED
;
ATRAN:	LDA	BLKSHF		;SHIFT COUNT TO REG A
	MOV	C,A
	LHLD	ARECORD	
	XRA	A
	CALL	ATRAN0
	SHLD	ARECORD
	STA	SHIFTED 
	SHLD	SHAREC
	LDA	BLKMSK
	MOV	C,A		;MASK VALUE TO C
	LDA	VRECORD 
	ANA	C		;MASKED VALUE IN A
	MOV	B,A
	STA	MSKVREC 
	LXI	H,ARECORD	;ARECORD=HL OR (VRECORD AND BLKMSK)
	ORA	M
	MOV	M,A
	RET
;
ATRAN0:	INR	C
ATRAN1:	DCR	C
	RZ
	DAD	H
	ADC	A
	JMP	ATRAN1
;
;	GET CURRENT EXTENT FIELD ADDRESS TO A
;
GETEXTA:LHLD	INFO
	LXI	D,0000CH	;EXTNUM
	DAD	D		;HL=.FCB(EXTNUM)
	RET

GETFCBB:	LHLD	INFO
	LXI	D,0000FH
	DAD	D
	RET
;
;	COMPUTE RECCNT AND NXTREC ADDRESSES FOR GET/SETFCB
;
GETFCBA:CALL	GETFCBB
	XCHG			;DE=.FCB(NXTREC)
	LXI	H,00011H	;NXTREC-RECC
	DAD	D		;HL=.FCB(NXTREC)
	RET
;
;	SET VARIABLES FROM CURRENTLY ADDRESSED FCB
;
GETFCB:	CALL	GETFCBA		;ADDRESSES IN DE, HL
	MOV	A,M
	STA	VRECORD 	;VRECORD=FCB(NXTREC)
	XCHG
	MOV	A,M
	STA	RCOUNT 		;RCOUNT=FCB(RECCNT)
	CALL	GETEXTA		;HL=.FCB(EXTNUM)
	LDA	EXTMSK		;EXTENT MASK TO A
	ANA	M		;FCB(EXTNUM) AND EXTMASK
	STA	EXTVAL 
	RET
;
;	PLACE VALUES BACK IN CURRENT FCB
;
SETFCB:	CALL	GETFCBA		;ADDRESSES TO DE, HL
	MVI	C,001H
	LDA	VRECORD
	ADD	C
	MOV	M,A		;FCB(NXTREC)=VRECORD+SEQIO
	XCHG
	LDA	RCOUNT
	MOV	M,A		;FCB(RECCNT)=RCOUNT
	RET
;
;	HL ROTATE RIGHT BY AMOUNT C
;
HLROTR:	INR	C		;IN CASE 0
HLROTR0:DCR	C
	RZ			;RETURN WHEN C=0
	MOV	A,H
	ORA	A
	RAR
	MOV	H,A		;HIGH BYTE
	MOV	A,L
	RAR
	MOV	L,A		;LOW BYTE
	JMP	HLROTR0
;
;	ROTATE THE MASK IN HL BY THE AMOUNT IN C
;
HLROTL:	INR	C		;MAY BE ZERO
HLROTL0:DCR	C
	RZ			;RETURN WHEN C=0
	DAD	H
	JMP	HLROTL0
;
;	SET A "1" VALUE IN CURDSK POSITION OF BC
;
SET$DISK:
	LDA	CURDSK
	PUSH	B		;SAVE INPUT PARAMETER
	MOV	C,A		;READY PARAMETER FOR SHIFT
	LXI	H,00001H
	CALL	HLROTL		;HL=MASK TO INTEGRATE
	POP	B		;ORIGINAL MASK
	MOV	A,C
	ORA	L
	MOV	L,A
	MOV	A,B
	ORA	H
	MOV	H,A		;HL=MASK OR ROL(1,CURDSK)
	RET
;
;	RETURN TRUE IF DIR CHECKSUM
;
NOWRITE:LDA	CURDSK
	MOV	C,A
	CALL	HLROTR
	MOV	A,L
	ANI	001H
	RET			;NOW 0 IF NOWRITE
;
;	COMPUTE THE ADDRESS OF A DIRECTORY ELEMENT AT
;	POSITION DPTR IN THE BUFFER
;
GETDPTRA:
	LHLD	BUFFA
	LDA	DPTR
	ADD	L		;HL=HL+A
	MOV	L,A
	RNC			;OVERFLOW TO H
	INR	H
	RET
;
RDDIR2:	PUSH	D
	LXI	D,0000AH
	DAD	D
	MOV	E,M
	INX	H
	MOV	D,M
	XCHG
	POP	D
	RET
;
READDIR3:	CALL	SEEKDIR
	LDA	ZERO2
	ORA	A
	JZ	RD$DIR
	MVI	A,003H
	CALL	READDIR4
	JMP	SETDATA
;
;	READ A DIRECTORY ENTRY INTO THE DIRECTORY BUFFER
;
RD$DIR:	CALL	RDDIR1
	SHLD	BUFFA
	CALL	SEEK
	CALL	RDBUFF		;DIRECTORY ENTRY LOADED
;
;	SET DATA DMA ADDRESS
;
SETDATA:LHLD	DMAAD
	JMP	SETDMA
;
RDDIR1:	LHLD	ALLOCA
	CALL	RDDIR2
;
;	HL=ADDRESS TO SET (I.E., BUFFA OR DMAAD)
;
SETDMA:	SHLD	DMAADDR
	RET
;
;	RETURN ZERO FLAG IF AT END OF DIRECTORY, NON ZERO
;	IF NOT AT END (END OF DIR IF DCNT=0FFFFH)
;
END$OF$DIR:	LXI	H,DCNT
	MOV	A,M		;MAY BE 0FFH
	INX	H
	CMP	M		;LOW(DCNT)=HIGH(DCNT)?
	RNZ			;NOW ZERO RETURNED IF DIFFERENT
				;HIGH AND LOW THE SAME,=0FFH?
	INR	A		;0FFH BECOMES 00 IF SO
	RET
;
;	SET DCNT TO THE END OF THE DIRECTORY
;
SET$END$DIR:
	LXI	H,0FFFFH	;(ENDDIR)
	SHLD	DCNT
	RET
;
READ$DIR:
	LHLD	DIRMAX
	XCHG
	LHLD	DCNT
	INX	H
	SHLD	DCNT
	MOV	A,E
	SUB	L
	MOV	L,A
	MOV	A,D
	SBB	H
	MOV	H,A
	JC	SET$END$DIR
	LDA	DCNT
	ANI	003H
	MVI	B,005H
READDIR2:
	ADD	A
	DCR	B
	JNZ	READDIR2
	STA	DPTR 
	ORA	A
	RNZ
	PUSH	B
	CALL	READDIR3
	POP	B
	RET
;
;	COMPARE EXTENT# IN A TO THAT IN C, RETURN NONZERO
;	IF NO MATCH
;
COMPEXT:
	PUSH	B		;SAVE C'S ORIGINAL VALUE
	PUSH	PSW
	LDA	EXTMSK
	CMA
	MOV	B,A		;B HAS NEGATED FORM OF EXTENT MASK
	MOV	A,C
	ANA	B
	MOV	C,A		;LOW BITS REMOVED FROM C
	POP	PSW
	ANA	B		;LOW BITS REMOVED FROM A
	SUB	C
	ANI	01FH		;(MAXEXT) SET FLAGS
	POP	B		;RESTORE ORIGINAL VALUES
	RET
;
OPENER:	CALL	GETFCBA
	MVI	C,010H
	MOV	B,C
	INR	C
	PUSH	B
OPENER1:
	POP	B
	DCR	C
	XRA	A
OPENER2:
	DCX	H
	DCR	B
	CMP	M
	JNZ	OPENER3
	DCR	C
	JNZ	OPENER2
OPENER3:
	MOV	A,C
	STA	DMPOS 
	LDA	SINGLE
	ORA	A
	MOV	A,B
	JNZ	OPENER4
	RAR
OPENER4:
	PUSH	B
	PUSH	H
	MOV	L,A
	MVI	H,000H
	LDA	BLKSHF
	MOV	D,A
	MVI	A,007H
	SUB	D
	MOV	C,A
	CALL	HLROTR
	MOV	B,L
	LDA	EXTMSK
	CMP	B
	POP	H
	JC	OPENER1
	CALL	GETEXTA
	MOV	C,M
	CMA
	ANI	01FH
	ANA	C
	ORA	B
	POP	B
	RET
;
;	SEARCH FOR DIRECTORY ELEMENT OF LENGTH C AT INFO
;
SEARCH:	LHLD	INFO
	SHLD	SEARCHA		;SEARCHA=INFO
	MOV	A,C
	STA	SEARCHL 
	CALL	SET$END$DIR	;DCNT=ENDIR
	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
	MVI	C,000H		;(FALSE)
	CALL	READ$DIR	;READ NEXT DIRECTORY ELEMENT
	CALL	END$OF$DIR
	JZ	SEARCH$FIN	;SKIP TO END IF SO
				;NOT END OF DIRECTORY, SCAN FOR MATCH
	LHLD	SEARCHA	
	XCHG
	CALL	GETDPTRA	;HL=BUFFA+DPTR
	LDA	SEARCHL
	MOV	C,A		;LENGTH OF SEARCH TO C
	MVI	B,000H		;B COUNTS UP, C COUNTS DOWN
	MOV	A,M
	CPI	0E5H		;(EMPTY)
	JZ	SEARCHN
SEARCHLOOP:
	MOV	A,C
	ORA	A
	JZ	ENDSEARCH
	MOV	A,B
	CPI	00DH		;(UBYTES)
	JZ	SEARCHOK	;
				;NOT THE UBYTES FIELD, EXTENT FIELD?
	CPI	00CH		;(EXTNUM) MAY BE EXTENT FIELD
	JZ	SEARCHEXT	;SKIP TO SEARCH EXTENT
	LDAX	D		;FCB CHARACTER
	SUB	M
	ANI	07FH		;MASK-OUT FLAGS/EXTENT MODULE
	JNZ	SEARCHN		;SKIP IF NOT MATCHED
	JMP	SEARCHOK	;MATCHED CHARACTER

SEARCHEXT:			;A HAS FCB CHARACTER
				;ATTEMPT AN EXTENT # MATCH
	LDAX	D
	PUSH	B		;SAVE COUNTERS
	MOV	C,M		;DIRECTORY CHARACTER TO C
	CALL	COMPEXT		;COMPARE USER/DIR CHAR
	POP	B		;RECALL COUNTERS
	ORA	A
	JNZ	SEARCHN		;SKIP IF NO MATCH
;
SEARCHOK:			;CURRENT CHARACTER MATCHES
	INX	D
	INX	H
	INR	B
	DCR	C
	JMP	SEARCHLOOP
;
ENDSEARCH:
	XRA	A
	STA	ARET 
	MOV	B,A
	INR	B
	RET
;
;	END OF DIRECTORY OR EMPTY NAME
;
SEARCH$FIN:		
	MVI	A,0FFH	
	MOV	B,A
	INR	B
	JMP	STA$RET
;
;	SEARCH FOR THE DIRECTORY ENTRY, COPY TO FCB
;
OPEN:	MVI	C,00FH		;NAMLEN
	CALL	SEARCH
	RZ			;RETURN WITH ARET=255 IF END
				;NOT END OF DIRECTORY. COPY FCB
				;INFORMATION (REFERENCED BELOW
				;TO COPY FCB INFO)
OPEN$COPY:
	CALL	GETEXTA
	MOV	A,M
	PUSH	PSW
	CALL	GETDPTRA
	XCHG			;DE=.BUFF(DPTR)
	LHLD	INFO		;HL=.FCB(0)
	MVI	C,020H		;(NXTREC) LENGTH OF MOVE OPERATION
	CALL	MOVE		;FROM .BUFF(DPTR) TO .FCB(0)
				;NOTE THAT ENTIRE FCB IS COPIED,
				; INCLUDING INDICATORS
	CALL	OPENER
	MOV	C,A
	POP	PSW
	MOV	M,A
OPEN$COPY1:
	MVI	B,000H
	XCHG
	LXI	H,00003H
	DAD	D
	LDAX	D
	SUB	C
	JZ	OPEN$COPY2
	MOV	A,B
	JNC	OPEN$RCNT	;USER IS LARGER
	MVI	A,080H		;DIRECTORY IS LARGER
	MOV	B,M
OPEN$RCNT:			;A HAS RECORD COUNT TO FILL
	MOV	M,A
	MOV	A,B
	STA	RECCNT 
	RET

OPEN$COPY2:
	STA	RECCNT 
	MOV	A,M
	ORA	A
	RNZ
	LDA	DMPOS
	ORA	A
	RZ
	LDA	SAVEC
	CPI	00FH
	RZ
	MVI	M,080H
	RET
;
OPEN$REEL3:
	PUSH	H
	LDA	RECCNT
	ORA	A
	JZ	OPEN$REEL4
	LXI	D,00003H
	DAD	D
	MOV	M,A
	XRA	A
	STA	RECCNT 
OPEN$REEL4:
	POP	H
	RET
;
;	CLOSE THE CURRENT EXTENT, AND OPEN THE NEXT ONE
;	IF POSSIBLE. RMF IS TRUE IF IN READ MODE
;
OPEN$REEL:
	CALL	GETEXTA
	MOV	A,M
	MOV	C,A
	INR	C
	CALL	COMPEXT
	JZ	OPEN$REEL1
	MVI	A,01FH
	ANA	C
	MOV	M,A
	MVI	C,00FH
	CALL	SEARCH
	CALL	OPEN$COPY
OPEN$REEL0:
	CALL	GETFCB
	XRA	A
	STA	VRECORD 
	JMP	STA$RET

OPEN$REEL1:
	INR	M
	CALL	OPENER
	MOV	C,A
	CMP	M
	JNC	OPEN$REEL2
	DCR	M
	JMP	DISKEOF
;
OPEN$REEL2:
	CALL	OPEN$REEL3
	CALL	OPEN$COPY1
	JMP	OPEN$REEL0
;
;	SEQUENTIAL DISK READ OPERATION
;
SEQDISKREAD:
	CALL	GETFCB		;SETS PARAMETERS FOR THE READ
	LDA	VRECORD
	LXI	H,RCOUNT
	CMP	M		;VRECORD-RCOUNT,SKIP IF RCOUNT>VRECORD
	JC	RECORDOK	;NOT ENOUGH RECORDS IN THE EXTENT,
				; RECORD COUNT MUST BE 128 TO CONTINUE
	CPI	080H		;VRECORD=128
	JNZ	DISKEOF		;SKIP IF VRECORD<>128
	CALL	OPEN$REEL	;GO TO NEXT EXTENT IF SO
	LDA	ARET
	ORA	A
	JNZ	DISKEOF		;STOP AT EOF
;
;	ARRIVE WITH FCB ADDRESSING A RECORD TO READ
;
RECORDOK:
	CALL	INDEX		;ERROR 2 IF READING UNWRITTEN DATA
				; (RETURNS 1 TO BE COMPATABLE WITH 1.4)
	JZ	DISKEOF		;RECORD HAS BEEN ALLOCATED, READ IT
	CALL	ATRAN		;ARECORD NOW A DISK ADDRESS
	LDA	ZERO2
	ORA	A
	JNZ	RECORDOK1
	CALL	SETDATA
	CALL	SEEK		;TO PROPER TRACK, SECTOR
	CALL	RDBUFF		;TO DMA ADDRESS
	JMP	SETFCB		;REPLACE PARAMETER

SELECT:	LDA	CURDSK
	INR	A
	JZ	SEL$ERROR
	DCR	A
	LXI	H,SELSTR
	CMP	M
	RZ			;SKIP IN LINFO=CURDSK
	MOV	M,A
	MOV	D,A
	LHLD	DLOG
	CALL	NOWRITE
	MOV	E,A
	PUSH	D
	CALL	SELECTDISK
	POP	H		;RECALL DLOG VECTOR
	JNC	SEL$ERROR
	DCR	L
	RZ
	LHLD	DLOG
	MOV	C,L
	MOV	B,H		;CALL READY
	CALL	SET$DISK
	SHLD	DLOG
	RET
;
CURSELECT:
	LDA	LINFO
	STA	CURDSK 
	RET
;
CLRMODNUM:
	XRA	A
	STA	MODNUM 
	JMP	RSEL1
;
;	CHECK CURRENT FCB TO SEE IF RESELECTION NECESSARY
;
RESELECT:
	MVI	A,080H
	MOV	B,A
	DCR	A
	MOV	C,A
	LHLD	INFO
	LXI	D,00007H
	XCHG
	DAD	D
	MOV	A,M
	ANA	B
	MOV	A,M
	ANA	C
	MOV	M,A
	INX	H
	MOV	A,M
	ANA	B
	STA	MODNUM 
	MOV	A,M
	ANA	C
	MOV	M,A
	CALL	GETEXTA
	MOV	A,M
	ANI	01FH
	MOV	M,A
	CALL	GETFCBB
	MOV	A,M
	ANA	B
	JZ	RSEL1
	MOV	A,M
	ANA	C
	MOV	M,B
RSEL1:	STA	RECCNT 
	LXI	H,00000H
	SHLD	OLDDSK
	MVI	A,0FFH
	STA	FCBDSK 
	LHLD	INFO
	MOV	A,M
	ANI	01FH
	DCR	A
	STA	LINFO 
	CPI	0FFH
	JZ	RSEL2
	MOV	A,M
	STA	OLDDSK 
	CALL	CURSELECT
RSEL2:	CALL	SELECT
	MVI	A,000H
	LHLD	INFO
	MOV	M,A
	RET
;
;	RESET DISK SYSTEM TO DISK 0   SEVERAL PARAMETERS RETURNED
;
FUNCT01:LXI	H,00000H
	SHLD	DLOG
	XRA	A
	STA	CURDSK 		;NOTE THAT USER CODE IS UNCHANGED
	DCR	A
	STA	SELSTR 
	LXI	H,TBUFF
	SHLD	DMAAD		;DMAAD=TBUFF
	JMP	SETDATA		;TO DATA DMA ADDR
;
;	SELECT DISK  E=DISK NUMBER
;
FUNCT02:CALL	CURSELECT
	JMP	SELECT
;
;	OPEN FILE  DE=FCB ADDRESS  A=DIRECTORY CODE
;
FUNCT03:CALL	CLRMODNUM	
	CALL	OPEN
	CALL	END$OF$DIR
	RZ
	CALL	GETFCBA
	MOV	A,M
	INR	A
	JNZ	OPENX1
	DCX	D
	DCX	D
	LDAX	D
	MOV	M,A
OPENX1:	POP	H
	MVI	C,040H
	RET
;
;	READ SEQUENTIAL   DE=FCB  A=ERROR CODE
;
FUNCT04:CALL	RESELECT
	JMP	SEQDISKREAD
;
;	RETURN CURRENT DISK  HL=CURRENT DISK NUMBER
;
FUNCT05:LDA	CURDSK
	JMP	STA$RET
;
;	SET DMA ADDRESS  DE=DMA ADDRESS  
;	SET SUBSEQUENT DMA ADDRESS TO INFO
;
FUNCT06:XCHG			;WAS LHLD INFO
	SHLD	DMAAD		;DMAAD=INFO
	JMP	SETDATA		;TO DATA DMA ADDRESS
;
;	ARRIVE HERE AT END OF PROCESSING TO RETURN TO USER
;
GOBACK:	LDA	SAVEC
	CPI	00FH
	JC	RETMON		;RESELECTION MAY HAVE TAKEN PLACE
	LDA	ZERO4
	STA	CURDSK 
	LDA	FCBDSK
	ORA	A
	JZ	RETMON		;RESTORE DISK NUMBER
	LHLD	INFO
	MVI	M,000H		;FCB(0)=0
	LDA	OLDDSK
	ORA	A
	JZ	GOBACK1
	MOV	M,A		;FCB(0)=FCBDSK
GOBACK1:INX	H
	LDA	MODNUM
	ORA	M
	MOV	M,A
	CALL	GETFCBB
	LDA	RECCNT
	ORA	M
	MOV	M,A
;
;	RETURN FROM THE DISK MONITOR
;
RETMON:	LHLD	ENTSP		;GET OLD STACK POINTER
	SPHL			;RESTORE THE STACK
	LHLD	ARET
	MOV	A,L
	MOV	B,H		;BA=HL=ARET
	RET
;
RECORDOK1:
	MVI	A,001H
	CALL	RECORDOK2
	JMP	SETFCB
;
READDIR4:
	LHLD	ALLOCA
	JMP	READDIR5

RECORDOK2:
	LHLD	ZERO5
READDIR5:
	PUSH	PSW
	LDA	ZERO2
	MOV	B,A
	CMA
	MOV	C,A
	LDA	ARECORD
	MOV	E,A
	ANA	B
	STA	SOMREC 
	MOV	A,E
	ANA	C
	STA	ARECORD 
	SHLD	RECADR
	CALL	RDDIR2
	SHLD	DMAADDR
	LHLD	RECADR
	LXI	D,SELSTR
	MVI	C,004H
	POP	PSW
	PUSH	PSW
	CPI	004H
	JNC	RECOK2
	CALL	RECOK8	
	JZ	RECOK3
	XRA	A
RECOK2:	
	LXI	D,00004H
	LHLD	RECADR
	DAD	D
	MVI	A,002H
	CALL	RECOK7
	LHLD	RECADR
	LXI	D,SELSTR
	MVI	C,004H
	CALL	MOVE
	MVI	M,000H
RECOK3:	LDA	SOMREC
	INR	A
	LXI	D,TBUFF
	LXI	H,0FF80H
RECOK4:	DAD	D
	DCR	A
	JNZ	RECOK4
	XCHG
	LHLD	DMAADDR
	DAD	D
	POP	PSW
	CPI	003H
	JNZ	BNKMOV
	SHLD	BUFFA
	RET
;
BNKMOV:	XCHG
	LHLD	DMAAD
	LXI	B,TBUFF
	JMP	@MOV		;MOVE BETWEEN BANKS
;
RECOK7:	PUSH	PSW
	CALL	SEEK
	POP	PSW
	DCR	A
	CP	RDBUFF
	LXI	D,00004H
	LHLD	RECADR
	DAD	D
	INX	H
	INX	H
	LXI	D,TRACK
	MVI	C,004H
	JMP	MOVE
;
;	STORAGE LOCATIONS AND MESSAGES
;
COMPCOL:DB	000H

MSBDOS:	DB	00DH,00AH
	DB	'BDOS ERR: $'

MSSLCT:	DB	'Select$'

MSPERM:	DB	'Perm.$'

ARET:	DW	0000H

;
FCBT:	DB	000H
	DB	'CPM3    SYS'
	DB	0,0,0,0,0,0,0,0
	DB	0,0,0,0,0,0,0,0
	DB	0,0,0,0,0,0,0,0
;
MSOPEN:	DB	00DH,00AH
	DB	'CPMLDR error:  failed to open CPM3.SYS'
	DB	00DH,00AH
	DB	'$' 
;
MSREAD:	DB	00DH,00AH
	DB	'CPMLDR error:  failed to read CPM3.SYS'
	DB	00DH,00AH
	DB	'$' 
;
MSBANR:	DB	00DH
	DB	00AH,00AH,00AH,00AH,00AH,00AH
	DB	00AH,00AH,00AH,00AH,00AH,00AH
	DB	00AH,00AH,00AH,00AH,00AH,00AH
	DB	00AH,00AH,00AH,00AH,00AH,00AH
	DB	'CP/M V3.0 Loader'
	DB	00DH,00AH
	DB	'Copyright (C) 1982, Digital Research'
	DB	00DH,00AH
	DB	'$' 
	DB	'021182'
	DB	0,0,0,0
STACK1:				;STACK WALKS OVER THE BANNER
;
HEDR1:	DB	000H		

HEDR2:	DB	000H

HEDR3:	DB	000H

HEDR4:	DB	000H

HEDR5:	DB	0,0,0,0,0,0,0,0

ENTSP:	DB	0,0

DLOG:	DW	0000H

DMAADDR:DW	0000H

BUFFA:	DW	0000H

	DW	0000H

CURTRKA:DW	0000H

CURRECA:DW	0000H

BUFFB:	DW	0000H

	DW	0000H

	DW	0000H

ALLOCA:	DW	0000H

ZERO5:	DB	0,0,0,0,0

SECTPT:	DW	0000H

BLKSHF:	DB	000H

BLKMSK:	DB	000H

EXTMSK:	DB	000H

MAXALL:	DW	0000H

DIRMAX:	DW	0000H

OFFSET:	DW	0000H

ZERO1:	DB	000H

ZERO2:	DB	000H

MSKVREC:DB	000H

ZERO3:	DB	000H

TRANV:	DW	0000H

LINFO:	DB	000H

DMPOS:	DB	000H

RECCNT:	DB	000H

SINGLE:	DB	000H

ZERO4:	DB	000H

RCOUNT:	DB	000H

EXTVAL:	DB	000H

VRECORD:DB	000H

SELSTR:	DB	0FFH

ARECORD:DW	0000H

SHIFTED:DB	000H

SHAREC:	DW	0000H

MODNUM:	DB	000H

DPTR:	DB	000H

OLDDSK:	DB	000H

SOMREC:	DB	000H

RECADR:	DW	0000H

TRACK:	DW	0000H

SAVSEC:	DW	0000H

COLUMN:	DB	000H		;COLUMN NUMBER

DOLLAR:	DB	024H		;$

DMAAD:	DB	080H,000H	;DMA ADDRESS

CURDSK:	DB	000H		;CURRENT DISK

INFO:	DW	0000H		;MESSAGE LOCATION

FCBDSK:	DB	000H

SAVEC:	DB	000H

DCNT:	DW	0000H

SEARCHA:DW	0000H

SEARCHL:DB	000H

;
	DS	80
LSTACK:
;
	DS	134D		;F1LL OUT TO THE END OF THE BLOCK
;
	END
