CPMuffin : A Call-A.P.P.L.E. Utility

by Val J. Golding

Call-A.P.P.L.E. Magazine March 1983 PP81

CP/MUFFIN was a rush job. We had a story submitted to us on Wordstar on a CP/M formatted diskette. Fortunately for us, we had just published a story in the December Call -A.P.PL.E. by Art Messler, which pretty well explained the structure of a CP/M directory. As it turned out, the clues in that story were sufficient to start us on the right path. We were working in the dark, not having a Z-80 system ourselves, thus writing a program to convert CP/M files to standard DOS presented an intriguing challenge.

At the same time, we had just finished editing a story which was published in “All About DOS” by Bill Parker, which outlined some simple techniques to call RWTS from BASIC. The basic premise of Bill’s routine was to repoint two Applesoft strings at pre-existing data in the RWTS data buffer and write them to disk. With Bill’s short assembly program as a core, we set about getting the data from the CP/M disk into a form we could interpret. In brief, this meant reading the CP/M directory into an array, checking for and bypassing deleted and dummy files, then presenting the user with the option of copying the entire disk or single files. Later, after having done some test conversions, we determined it would be helpful to have a choice of removing control characters from the file or removing [CTRL-J] only. (CP/M uses a [CTRL-J] after each carriage return.)

CP/M data is stored on a diskette in manner different than standard DOS, with respect to the sequence in which sectors are read, thus based on Messler’s article, we had to establish an array to be used as a look-up table, then base our read on the four block patterns in the table. This was the essence of the whole program, since DOS RWTS can read any sector on a CP/M or Pascal diskette.

We put this together over a weekend and ended up with a working program, albeit slow. (45 minutes to convert a single diskette is not exactly the world’s land speed record.) At this point, having gotten our needed results, we called in David Sparks, who waved his magic wand, added an additional assembly language section which laid out a buffer to handle an entire CP/M file and then write it to disk.

The BASIC program is well modularized and with the REMs should be easy to follow. It will now convert a full CP/M disk in about seven or eight minutes. This speed could be improved on by deleting line 1030 which puts a MONO into effect. CP/M does not use a track! sector list to keep track of its files; this is done via directory entries and dynamic block allocation, therefore it is possible although unlikely to encounter a DISK FULL ERROR while converting a very full CP/M diskette. The BASIC program is complete in itself, since the machine code (which is fully re-locatable) is loaded using the S. H. Lam routine. The assembly listing, which is well commented, is for the use of those who wish to further expand on the program. It should be fairly simple to write a program to convert DOS files to CP/M.

Name : CP/MUFFIN
Length : $1279 (4729)
Load at : $0801 (2049)

        10  REM ^J^M  [CP/M]UFFIN BY VAL J GOLDING^J^MWITH DAVE SPARKS AND
                 BILL PARKER^M  CALL -A.P.P.L.E. : MARCH 1983^J^M CONVERTS
                 CP/M TEXT FILES TO DOS^J
       100  GOTO 9000: REM ^J
       500  REM ^J^MGET TRACK NBR & PATTERN FOR BLOCK
       510  TRACK% = INT (BLOCK%(I,K + 1) / MOD) + 3
       520  PTN    = INT ( ABS(BLOCK%(I,K + 1)) - INT ( ABS(BLOCK%(I,K + 1) / MOD)) * ABS(MOD) + .05) * SGN(BLOCK%(I,K + 1) / MOD)
       530  RETURN: REM  ^J
      1000  REM ^J^MWRITE ONE FILE^J
      1010  VTAB 7:
            PRINT "WRITING ";NUMOFBLOCKS * 4;" SECTORS..."
      1020  POKE 34,11:
            HOME
      1030  PRINT CHR$(4);"MONO"
      1040  PRINT CHR$(4);"OPENT.";FILNAME$(I);",D2"
      1050  PRINT CHR$(4);"WRITET.";FILNAME$(I)
      1060  & OUTPUT > BFPTR(1),BFPTR(2) - 1
      1070  PRINT CHR$(4);"NOMONO"
      1080  PRINT CHR$(4);"CLOSE"
      1090  POKE 34,5:
            HOME:
            RETURN
      1100  END: REM ^J
      2000  REM ^J^MCALL RWTS TO READ CPM SECTOR^J
      2010  POKE TRACK,TRACK%:
            POKE SECTR,SECTR%:
            POKE DRIVE,DRIVE%
      2020  VTAB 4:
            HTAB 1:
            PRINT "READING BLOCK:  ";:
            INVERSE:
            PRINT BLOCK%(I,K + 1);"  ";:
            NORMAL:
            PRINT " OF:  ";:
            INVERSE:
            PRINT FILNAME$(I):
            NORMAL
      2030  PRINT "TRACK:          ";:
            :
            INVERSE:
            PRINT TR%;" ";:
            NORMAL:
            PRINT "  SECTOR: ";:
            INVERSE:
            PRINT SECTR%;" ":
            NORMAL
      2040  POKE RWCODE,1:
            POKE PREGISTER,0
      2050  CALL USERRWTS
      2060  POKE RWCODE,2:
            POKE PREGISTER,0
      2070  IF CTRLCHR THEN 
              & CTRLKILL
      2080  IF JCTRL THEN 
              & LINEFEEDKILL
      2090  & R > BUFFR$(1),BUFFR$(2)
      2100  RETURN: REM ^J
      3000  REM ^J^MREAD AND WRITE A BLOCK^J
      3010  FOR I = 1 TO LSTFILE
      3020    RNUM = 0:
              IF SNGL THEN 
                I = SNGL: REM ^J
      3200    FOR J = 0 TO 15:
                IF BLOCK%(I,J + 1) = 0 THEN 
                  NUMOFBLOCKS = J:
                  J           = 15:
                  NEXT J:
                  GOTO 3220
      3210  NEXT J:
            NUMOFBLOCKS = 16
      3220  IF NOT LEN(FILNAME$(I)) THEN 
              3710: REM ^J
      3300  BFPTR(2) = BFPTR(1)
      3310  HOME
      3320  VTAB 10:
            PRINT "DATA LOCATED FROM ";BFPTR(1);" TO ";: REM ^J
      3400  FOR K = 0 TO NUMOFBLOCKS - 1
      3410    GOSUB 500: REM ^J
      3500    FOR J = 1 TO 4
      3510      SECTR% = STBL(PTN,J)
      3520      GOSUB 2000
      3530      & MOVE > BFPTR(2):
                BFPTR(2) = BFPTR(2) + 256:
                VTAB 10:
                HTAB 28:
                PRINT BFPTR(2) - 1
      3540    NEXT J: REM ^J
      3600  NEXT K: REM ^J
      3700  GOSUB 1000
      3710  IF SNGL THEN 
              POKE 34,3:
              HOME:
              GOTO 6100
      3720  NEXT I
      3730  END: REM  ^J
      4000  REM ^J^MREAD CPM DIRECTORY^J
      4010  TRACK% = 3:
            SECTR% = 0:
            ENTNBR = 0
      4020  FOR I = 1 TO 6:
              GOSUB 2000: REM ^J
      4100    NDX = 1:
              FOR J = 1 TO 4:
                ENTNBR       = ENTNBR + 1
      4110      DIR$(ENTNBR) = MID$(BUFFR$(1),NDX,32)
      4120      NDX          = NDX + 32:
                :
              NEXT J: REM ^J
      4200    NDX = 1:
              FOR J = 1 TO 4:
                ENTNBR       = ENTNBR + 1
      4210      DIR$(ENTNBR) = MID$(BUFFR$(2),NDX,32)
      4220      NDX          = NDX + 32:
              NEXT J: REM ^J
      4300    SECTR% = SECTR% + 6
      4310    IF SECTR% > 15 THEN 
                SECTR% = SECTR% - 15
      4330  NEXT I: REM ^J
      5000  REM ^J^MINTREPRET CPM DIRECTORY^J
      5010  K = 0:
            FOR I = 1 TO 48
      5020    IF MID$(DIR$(I),2,1) = E5$ THEN 
                I = 48:
                NEXT I:
                GOTO 5420: REM   
      5030  IF LEFT$(DIR$(I),1) = E5$ THEN 
              5410: REM   ^MCHECK IF DELETED FILE^J
      5040  K           = K + 1:
            FILNAME$(K) = MID$(DIR$(I),2,11)
      5050  TEST$       = ( MID$(DIR$(I),13,1)):
            IF TEST$ < > CHR$(0) THEN 
              FILNAME$(K) = FILNAME$(K) + "." + CHR$( ASC(TEST$) + 48): REM  ^MCHECK FOR FILENAME SUFFIX^J
      5060  FOR J = 1 TO LEN(FILNAME$(K)):
              IF MID$(FILNAME$(K),J,1) < > " " THEN 
                X$ = X$ + MID$(FILNAME$(K),J,1): REM  REMOVE SPACES^J
      5070  NEXT :
            FILNAME$(K) = X$:
            X$          = ""
      5080  NREC%(K)    = ASC( MID$(DIR$(I),16,1)): REM  CHECK FOR   DUMMY FILE^J
      5090  IF NOT NR%(K) THEN 
              FILNAME$(K) = "":
              GOTO 5410: REM  ^J
      5200  FOR J = 1 TO 16
      5210    BLOCK%(K,J) = ASC( MID$(DIR$(I),J + 16,1))
      5220    IF BLOCK%(K,J) = 0 THEN 
                J = 16:
                NEXT J:
                GOTO 5410: REM  
      5400  NEXT J
      5410  NEXT I
      5420  LSTFILE = K: REM  FALL THRU TO 6000^J
      6000  REM ^J^MCOPY OPTIONS MENU^J
      6010  POKE 35,24:
            HOME:
            VTAB 9:
            PRINT "COPY ENTIRE DISK (Y/N) ";:
            GET CHOICE$:
            PRINT CHOICE$:
      6020  IF CHOICE$ = "Y" THEN 
              GOSUB 6400:
              GOTO 3000: REM ^J
      6100  HOME:
            FOR DIR = 1 TO LSTFILE
      6110    IF NOT LEN(FILNAME$(DIR)) THEN 
                6130
      6120    PRINT " [";DIR;"] ";FILNAME$(DIR)
      6130  NEXT DIR: REM ^J
      6200  PRINT :
            INPUT "WHICH FILE NBR ? [PRESS RETURN TO QUIT]";CHOICE$:
            SNGL = VAL(CHOICE$):
            IF NOT SNGL THEN 
              IF LEN(CHOICE$) THEN 
                6200
      6210  IF NOT SNGL THEN 
              POKE 34,0:
              END
      6220  GOSUB 6400:
            GOTO 3020: REM ^J
      6400  PRINT :
            PRINT "REMOVE ALL CONTROL CHARACTERS FROM TEXT 
                   (Y/N) ? ";:
            GET CHOICE$:
            PRINT CHOICE$
      6410  IF CHOICE$ = "Y" THEN 
              CTRLCHR = 1:
              RETURN: REM ^J
      6500  PRINT :
            PRINT "REMOVE CONTROL J'S FROM TEXT (Y/N) ? ";:
            GET CHOICE$:
            PRINT CHOICE$
      6510  IF CHOICE$ = "Y" THEN 
              JCTRLCHR = 1
      6520  RETURN: REM ^J
      9000  REM ^J^MINITIALIZATION^J
      9010  HOME:
            VTAB 12:
            HTAB 11:
            FLASH:
            PRINT " LOADING HEX DATA ":
            NORMAL
      9020  HX$ = "2E0:A9 4C 8D F5 03 A9 F0 8D F6 03 A9 02 8D F7 03 60 A2 CF 86 0D 86 0E C9 52 D0 48 20 AA D9 C8 20 98 D9 20 E3 DF A9 80 20 52 E4 A0 00 91 83 C8 A5 71":
            GOSUB 9900
      9030  HX$ = "310:91 83 C8 A5 72 91 83 A0 B4 A2 BB A9 80 20 E2 E5 20 BE DE 20 E3 DF A9 80 20 52 E4 A0 00 91 83 C8 A5 71 91 83 C8 A5 72 91 83 A0 B5 A2 3B A9 80 4C":
            GOSUB 9900
      9040  HX$ = "340:E2 E5 C9 43 D0 1D A2 00 BD BB B4 9D BB B4 C9 0D F0 04 C9 20 90 06 E8 D0 EF 4C 95 D9 A9 20 9D BB B4 D0 F3 C9 4C D0 19 A2 00 BD BB B4 9D BB B4 C9":
            GOSUB 9900
      9050  HX$ = "370:0A F0 06 E8 D0 F3 4C 95 D9 A9 20 9D BB B4 D0 F3 C9 4D D0 18 20 AA D9 C8 20 98 D9 20 67 DD 20 52 E7 A0 00 B9 BB B4 91 50 C8 D0 F8 60 C9 4F D0 2E":
            GOSUB 9900
      9060  HX$ = "3A0:20 AA D9 C8 20 98 D9 20 67 DD 20 52 E7 20 BE DE 20 67 DD 20 F2 EB B1 50 20 5C DB E6 50 D0 02 E6 51 A5 A1 C5 50 A5 A0 E5 51 B0 EB 4C FB DA 00":
            GOSUB 9900
      9070  CALL 736: REM ^J
      9080  REM ^J^MRELOCATABLE MACHINE CODE^MMODIFIED BY DAVID SPARKS^J
      9200  BFPTR(1) = 22016:
            HIMEM:22016
      9210  RWCODE = 45121:
            SLOT   = 46583:
            DRIVE  = 46584:
      9220  TRACK  = 45975:
            SECTR  = 45976:
            MOD    = 4:
            E5$    = CHR$(229)
      9230  USERRW = 45111:
            PREGISTER = 72
      9240  DIM DIR$(49),FILNAME$(49),NR%(49),BLOCK%(49,17): REM ^J
      9400  TEXT:
            HOME:
            VTAB 2:
            PRINT "[CP/MUFFIN]  CP/M->DOS FILE CONVERTER":
            PRINT :
            POKE 34,3:
            INPUT "SOURCE SLOT, DRIVE: ";SLOT%,DRIVE%
      9410  POKE SLOT,SLOT% * 16:
            POKE DRIVE,DRIVE%
      9420  POKE 35,16:
            VTAB 20:
            HTAB 11:
            PRINT :
            INVERSE:
            PRINT " READING CP/M DIRECTORY ":
            :
            :
            NORMAL: REM  ^J
      9600  REM ^J^MSET UP 4 SECTOR READ SEQUENCE^MPATTERNS^J
      9610  STBL(0,1) = 0:
            STBL(0,2) = 6:
            STBL(0,3) = 12:
            STBL(0,4) = 3
      9620  STBL(1,1) = 9:
            STBL(1,2) = 15:
            STBL(1,3) = 14:
            STBL(1,4) = 5
      9630  STBL(2,1) = 11:
            STBL(2,2) = 2:
            STBL(2,3) = 8:
            STBL(2,4) = 7
      9640  STBL(3,1) = 13:
            STBL(3,2) = 4:
            STBL(3,3) = 10:
            STBL(3,4) = 1
      9650  GOTO 4000: REM ^J
      9900  REM ^J^MPOKE RWTS STRING MACHINE CODE^MINTO MEMORY WITH S H LAM
                 ROUTINE^J
      9910  HX$ = HX$ + " N D9C6G":
            FOR I = 1 TO LEN(HX$):
              POKE 511 + I, ASC( MID$(HX$,I,1)) + 128:
            NEXT :
            POKE 72,0:
            CALL - 144:
            RETURN
Please follow and like us:
error

About the Author