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