.title 'pubpatch.asm 4-13-84   (c) 1984 Plu*Perfect Systems'
.settim 16:19:00
.setdat 11/18/84
.setwid 90
.setlen 60

;11/18/84 minor change to published DDJ version to produce
;relocatable object file, to be relocated using HXRLOAD

.remark ~
			 -- PUBPATCH --

A CP/M 2.2 BDOS modification to support the PUBlic filetype. 

	  --------------------------------------------
	   Copyright (c) 1984 -- All rights reserved.

		       Plu*Perfect Systems
			 P. O. Box 1494
		       Idyllwild CA 92349
	  --------------------------------------------

Attribute bit 2 of a filename signifies a PUBlic file,
accessible by its unambiguous filename from all user
numbers.

PUBlic files are not accessible via the usual ambiguous
filenames (e.g. *.* or ABC.D?F), to prevent unintentional
erasure	and avoid directory clutter.

Directory entries for PUBlic files are, however, accessible
via ambiguous filenames by using the BDOS search-for-first,
search-for-next functions with a '?' in the drive-byte of the
fcb.  Extended versions of SD and DISK7 displays PUBlic files.

To erase a PUBlic file, use "ERA unambiguous-filename". Or use
DISK7.  Or change it to a private file and then erase with a
wildcard erase command.

The PUBLIC.COM utility is available to make files either
PUBlic or private and to list the current PUBlic files.

If another utility is used to set the PUBlic attribute bit,
avoid creating multiple files with the same name on the same
drive, unless all of them are private. (PUBLIC.COM checks
for this situation and prevents a conflict.)
	
The REName command removes all attributes, so RENaming a
PUBlic file will make it private, R/W, DIR in its original
user number.
~
.page
.remark ~
		       --- TO INSTALL ---

1a. Determine the BIOSBASE address of your system in memory
    by subtracting 3 from the warm-boot address in memory:
	DDT
	L0
	subtract 3 ==> BIOSBASE address
1b. Subtract 1600H to determine the CCPBASE_MEMORY address.
1c. Assemble PUBPATCH for these addresses.

    Either: use CDL's MACROIII assembler:

	MACROIII PUBPATCH A:DHK

    Or:	convert the pseudo-opcodes to your assembler's 
	pseudo-ops and assemble into a HEX file.
	e.g. 	.loc	==>	org
		=    	==>	set
		=\	==>	????, 	etc.

2. Create a system image for the SYSGEN operation.
   There are two ways to get the image:

   a. Either use SYSGEN to extract a system image from a disk
      in the usual manner --
 	SYSGEN
	source drive? A
	destination drive? <CR>
     	SAVE pp ORIG.SYS.    Use pp=50 pages or so to get
				the entire BIOS.

   b. Or generate a new system --
	MOVCPM ss *		where ss=64 for a 64K system,
	SAVE pp ORIG.SYS	or whatever you are running.

3. Find the base address of the Command Processor in the image
	DDT ORIG.SYS
   Look for the command processor at 980H:
   You recognize it by two JMP instructions, followed by
   the command buffer (containing a Digital Research
   copyright notice, in the case of the original CCP):
	L980
	D980
   Call that address CCPBASE_IMAGE (normally 980H).
   (If you don't find it, you have a non-standard system, and
   your user's manual should have a memory map.  See, e.g.,
   the Compupro Disk 1 Controller manual, sec. 6.4).

4. Calculate the offset needed to cause the PUBPATCH.HEX file to
   load on top of the BDOS image.  'offset' will satisfy:

	CCPBASE_IMAGE = CCPBASE_MEMORY + offset

5. Create a new system image containing the patch:

	DDT ORIG.SYS
	IPUBPATCH.HEX
	Roffset
	G0
	SAVE pp NEWSYS.SYS

6. Finally, put the new system on a FLOPPY disk for testing:
	
	SYSGEN NEWSYS.SYS
	<CR>
	destination_drive


	------------------------------------------------
	Code also corrects a CP/M 2.2 bug that caused
	Rename, Set Attribute, and Delete File functions
	to return 0 status on success instead of 0,1,2,3 
	per CP/M 2.2 Installation Guide.
	------------------------------------------------
~
	.page
	.phex

bdosbase = .

;	Internal BDOS locations:
;
FINDNXT	=	bdosbase+072Dh
NXENTRY	=	bdosbase+0605h
CKFILPOS =	bdosbase+05F5h
MOREFLS	=	bdosbase+057Fh
FCB2HL	=	bdosbase+055Eh
SAMEXT	=	bdosbase+0707h
STFILPOS =	bdosbase+05FEh
SETSTAT	=	bdosbase+0301h
SAVEFCB	=	bdosbase+0DD9h
COUNTER	=	bdosbase+0DD8h
FILEPOS	=	bdosbase+0DEAh
STATUS	=	bdosbase+0345h
FNDSTAT	=	bdosbase+0DD4h
CHKWPRT	=	bdosbase+0554h
CHKROFL	=	bdosbase+0544h
FINDFST	=	bdosbase+0718h
SETFILE	=	bdosbase+066bh
DIRWRITE =	bdosbase+05c6h
CKFILPOS =	bdosbase+05f5h
DELFILE	=	bdosbase+0cd7h
EXTMASK	=	bdosbase+0dc5h
CLOSEFLG =	bdosbase+0dd2h
RDWRTFLG =	bdosbase+0dd3h
GETEMPTY =	bdosbase+0924h
OPENIT1	=	bdosbase+085ah
STRDATA	=	bdosbase+04bbh
SETSTAT	=	bdosbase+0301h
IOERR1	=	bdosbase+0305h
SETS2B7	=	bdosbase+0578h
;
	.page
	.loc	FINDNXT

fnxt0:	lxi	h,0
	shld	pflag	;initialize PUBlic & wildcard flags
	mov	c,h	;0
	call	NXENTRY
	call	CKFILPOS
	jrz	nomatch		;if done
	lhld	SAVEFCB
	xchg			;de=user-fcb
	ldax	d
	cpi	0E5h	;if Getempty fn  wants first
	jrz	fnxt1	;..deleted file slot in directory
	push	d
	call	MOREFLS
	pop	d
	jrnc	nomatch		;if no more files
;
fnxt1:	call	FCB2HL		;hl=directory fcb
	lda	COUNTER
	mov	b,a		;b=count
	mvi	c,0		;c=byte #
	ora	a		;COUNTER=0 ==> Search fn
	jrz	matched		;..so match every entry
;
fnxt2:	mov	a,c		;get byte #
	cpi	13
	jrz	nxtbyte		;omit S1 byte
	ldax	d		;get user-fcb char
	cpi	'?'
	jrnz	fnxt3
	sta	qflag	;flag wildcard
	jr	nxtbyt
;
fnxt3:	mov	a,m	;get directory-fcb char
	cpi	0E5h	;check for blank/deleted file
	mov	a,c	;A = byte #
	jrz	chkext	;if a deleted file, omit user # check
	ora	a
	jrnz	chkext	;or if not user # byte
	inx	h	;else check for PUBlic file
	inx	h
	bit	7,m	;..at attribute bit 2
	dcx	h
	dcx	h
	jrz	chkext	;if not PUBlic, match on user #
;
; the file is PUBlic
; -- but is BDOS looking for an empty directory slot?
	mov	a,b	;if COUNTER=1, this is a Getempty request
	dcr	a	;
	jrz	fnxt0	;..so go to next file
	sta	pflag	;else flag the file PUBlic,
	jr	nxtbyt	;..and omit matching user #
;	
chkext:	cpi	12	; (A=byte #)
	ldax	d
	jrz	tstext	;extent byte(#12) is special case
	sub	m	;compare the characters
	ani	07fh	;..excluding attribute bits
	jr	extdone
tstext:	push	b	;check for same extent
	mov	c,m
	call	SAMEXT
	pop	b
extdone:jrnz	fnxt0	;if mismatch, get next file
;
nxtbyt:	inx	d	;chars match, bump to next byte
	inx	h
	inr	c	;byte # ++
	djnz	fnxt2	;count--
;
; here if-- COUNTER > 1 and filenames match
; 
;Test for PUBlic file and wild-card combination:
;			;flags initially = 0, but
pflag = .+1		;  = COUNTER-1 if PUBlic
qflag = .+2		;  = '?'(3Fh) if '?' in fcb+1...
	lxi	h,.-.
	mov	a,l		;if file is PUBlic
	ana	h		;..and there's a wildcard
				;(3Fh & 1...n) ==> NZ
	jrnz	fnxt0		;..get next directory entry
;
; here if--
;     (a) non-PUBlic filenames match, 
;  or (b) find-all-files (searchfirst/searchnext functions
;	  with drive byte = '?')
;  or (c) delete unambiguous PUBlic-filename.
;
matched:jmp	PATCH1
;
nomatch:call	STFILPOS
	mov	a,l		;l=0ffh
	jmp	SETSTAT
;
	.page
;	the ERASE FILE routine -- in a new location
;
;	Routine is split, with remainder stuffed
;	into free bytes at end of BDOS.
;
ERAFILE:call	CHKWPRT	;write-protect aborts
	mvi	c,12
	call	FINDFST
eraf1:	call	CKFILPOS ;check for 'E5' case
	rz
	call	CHKROFL	;read-only file aborts
	call	FCB2HL
	jmp	PATCH2
LAST1 = .	;must be <= bdosbase+07BEh
;
;


;Remainder of ERASE routine goes at end of BDOS
;
	.loc	bdosbase+0deeh	;there are 18 spare bytes

PATCH2:
eraf2:	mvi	m,0E5h	;install erase mark
	mvi	c,0
	call	SETFILE	;clear file's space in bitmap
	call	DIRWRITE ;write directory sector
eraf3:	call	FINDNXT	;look for next entry
	jmp	eraf1
LAST2 = .	;must be <= bdosbase+0E00h
;
	.page
;rewrite last part of bdos GETNEXT routine to gain space
;
	.LOC	bdosbase+0971h
;
gnxt0:	jrz	gnxt1	;( overlaying jz gtnext1)
	mov	b,a	;extent byte
	lda	EXTMASK
	ana	b
	lxi	h,CLOSEFLG
	ana	m
	jrz	gnxt2	;must read next extent
gnxt3:	call	OPENIT1	;open current extent
gnxt4:	call	STRDATA ;update rec#, extent#,...
	xra	a
jsetst:	jmp	SETSTAT
;
;	have overflowed normal extent, check s2 byte
gnxt1:	inx	h	;shorter code, replacing
	inx	h	;lxi b,2 & dad b
	inr	m	;bump s2 byte
	mov	a,m
	ani	0fh
	jrz	gnxt5	;error if too many extents
gnxt2:	mvi	c,15	;open the next extent
	call	FINDFST
	call	CKFILPOS
	jrnz	gnxt3
	lda	RDWRTFLG ;no extant extent
	inr	a	;..if reading, can't open one
	jrz	gnxt5
	call	GETEMPTY ;writing, so get next free entry
	call	CKFILPOS
	jrnz	gnxt4	;and if no error, save the data
gnxt5:	call	IOERR1	;set error &
	jmp	SETS2B7	;..don't close file
;	
;
;	use space (14 bytes) for fragment from FINDNXT routine:
;
PATCH1:	lda	FILEPOS
	ani	03h
	sta	STATUS	;save its directory buffer index
	lxi	h,FNDSTAT
;
	.remark ~
The original CP/M 2.2 code removed below is erroneous, and
causes BDOS Erasefile, Renamefile, Setattribute functions
to return A=0 on success rather than the directory index
(0,1,2 or 3) specified in the Interface Guide.

;;	mov	a,m
;;	ral
;;	rnc
;;	xra	a
~
;
	mov	m,a	;also save it for use
	ret		;..by Erase,Rename,Set Attribute fns
;
LAST3 = .	;must be <= bdosbase+09BCh
;
;
;patch ERAFILE reference to its new location
;
	.loc	(DELFILE+3)
	CALL	ERAFILE
;
	.end