From: duck@pembvax1.pembroke.edu
Newsgroups: comp.sys.cbm
Subject: C= Hacking Issue #4 4/5
Date: 5 Oct 92 14:02:33 GMT
Organization: Pembroke State University
NNTP-Posting-Host: pembvax1.pembroke.edu

.%125!     rts
.%126!  +  lda #1            ;interleave of 1 for 1581
.%127!     bit sourceType
.%128!     bmi +
.%129!     lda #4            ;interleave of 4 for 1571
.%130!  +  jsr kernelCiout
.%131!     jsr kernelUnlsn
.%132!     mountExit = *
.%133!     rts
.%134!

Read all of the sectors of a given track into the track cache.

.%135!  bufptr = 2
.%136!  secnum = 4
.%137!
.%138!  readTrack = *  ;( .A=cylinder, .X=side ) : trackbuf, .CS=err
.%139!     pha
.%140!     txa

Get the side and put it into the command byte.  Remember that we have to flip
the side bit for a 1581.

.%141!     and #$01
.%142!     asl
.%143!     asl
.%144!     asl
.%145!     asl
.%146!     bit sourceType
.%147!     bpl +
.%148!     eor #$10
.%149!  +  jsr sendU0
.%150!     bcc +
.%151!     rts
.%152!  +  pla                  ;cylinder number
.%153!     jsr kernelCiout
.%154!     lda #1               ;start sector number
.%155!     jsr kernelCiout
.%156!     lda #9               ;sector count
.%157!     jsr kernelCiout
.%158!     jsr kernelUnlsn

Prepare to receive the track data.

.%159!     sei
.%160!     clc
.%161!     jsr kernelSpinp
.%162!     bit ciaFlags
.%163!     jsr toggleClock
.%164!     lda #<trackbuf
.%165!     ldy #>trackbuf
.%166!     sta bufptr
.%167!     sty bufptr+1

Get the sector data for each of the 9 sectors of the track.

.%168!     lda #0
.%169!     sta secnum
.%170!  -  bit sourceType
.%171!     bmi +

If we are dealing with a 1571, we have to set the buffer pointer for the next
sector, taking into account the soft interleave of 4.

.%172!     jsr get1571BufPtr
.%173!  +  jsr readSector
.%174!     bcs trackExit
.%175!     inc secnum
.%176!     lda secnum
.%177!     cmp #9
.%178!     bcc -
.%179!     clc
.%180!     trackExit = *
.%181!     cli
.%182!     rts
.%183!

Get the buffer pointer for the next 1571 sector.

.%184!  get1571BufPtr = *
.%185!     lda #<trackbuf
.%186!     sta bufptr
.%187!     ldx secnum
.%188!     clc
.%189!     lda #>trackbuf
.%190!     adc bufptr1571,x
.%191!     sta bufptr+1
.%192!     rts
.%193!
.%194!  bufptr1571 = *
.%195!     .byte 0,8,16,6,14,4,12,2,10
.%196!

Read an individual sector into memory at the specified address.

.%197!  readSector = *  ;( bufptr ) : .CS=err

Get and check the burst status byte for errors.

.%198!     jsr getBurstByte
.%199!     sta errno
.%200!     and #$0f
.%201!     cmp #2
.%202!     bcc +
.%203!     rts
.%204!  +  ldx #2
.%205!     ldy #0
.%206!

Receive the 512 sector data bytes into memory.

.%207!     readByte = *
.%208!     lda #$08
.%209!  -  bit ciaFlags
.%210!     beq -
.%211!     lda ciaClock
.%212!     eor #$10
.%213!     sta ciaClock
.%214!     lda ciaData
.%215!     sta (bufptr),y
.%216!     iny
.%217!     bne readByte
.%218!     inc bufptr+1
.%219!     dex
.%220!     bne readByte
.%221!     rts
.%222!

This next level of routines deals with logical sectors and the track cache
rather than with hardware.

.%223!  ;====logical sector level====
.%224!

Invalidate the track cache if the MS-DOS drive number is changed or if a new
disk is inserted.  This routine has to establish a RAM configuration of $0E
since it will be called from RAM0.  Configuration $0E gives RAM0 from $0000 to
$BFFF, Kernal ROM from $C000 to $FFFF, and the I/O space over the Kernal from
$D000 to $DFFF.  This configuration is set by all application interface
subroutines.

.%225!  initPackage = *
.%226!     lda #$0e
.%227!     sta $ff00
.%228!     lda #$ff
.%229!     sta bufCylinder
.%230!     sta bufSide
.%231!     clc
.%232!     rts
.%233!

Locate a sector (block) in the track cache, or read the corresponding physical
track into the track cache if necessary.  This routine accepts the cylinder,
side, and sector numbers of the block.

.%234!  sectorSave = 5
.%235!
.%236!  readBlock = *  ;( .A=cylinder,.X=side,.Y=sector ) : .AY=blkPtr,.CS=err

Check if the correct track is in the track cache.

.%237!     cmp bufCylinder
.%238!     bne readBlockPhysical
.%239!     cpx bufSide
.%240!     bne readBlockPhysical

If so, then locate the sector's address and return that.

.%241!     dey
.%242!     tya
.%243!     asl
.%244!     clc
.%245!     adc #>trackbuf
.%246!     tay
.%247!     lda #<trackbuf
.%248!     clc
.%249!     rts
.%250!

Here, we have to read the physical track into the track cache.  We save the
input parameters and call the hardware-level track-reading routine.

.%251!     readBlockPhysical = *
.%252!     sta bufCylinder
.%253!     stx bufSide
.%254!     sty sectorSave
.%255!     jsr readTrack

Check for errors.

.%256!     bcc readBlockPhysicalOk
.%257!     lda errno
.%258!     and #$0f
.%259!     cmp #11    ;disk change
.%260!     beq +
.%261!     sec
.%262!     rts

If the error that happened is a "Disk Change" error, then mount the disk and
try to read the physical track again.

.%263!  +  jsr mountDisk
.%264!     lda bufCylinder
.%265!     ldx bufSide
.%266!     ldy sectorSave
.%267!     bcc readBlockPhysical
.%268!     rts
.%269!

Here, the physical track has been read into the track cache ok, so we recover
the original input parameters and try the top of the routine again.

.%270!     readBlockPhysicalOk = *
.%271!     lda bufCylinder
.%272!     ldx bufSide
.%273!     ldy sectorSave
.%274!     jmp readBlock
.%275!

Divide the given number by 18.  This is needed for the calculations to convert
a logical sector number to the corresponding physical cylinder, side, and
sector numbers that the lower-level routines require.  The method of repeated
subtraction is used.  This routine would probably work faster if we tried to
repeatedly subtract 360 (18*20) at the top, but I didn't bother.

.%276!  divideBy18 = *  ;( .AY=number ) : .A=quotient, .Y=remainder
.%277!     ;** could repeatedly subtract 360 here
.%278!     ldx #$ff
.%279!  -  inx
.%280!     sec
.%281!     sbc #18
.%282!     bcs -
.%283!     dey
.%284!     bpl -
.%285!     clc
.%286!     adc #18
.%287!     iny
.%288!     tay
.%289!     txa
.%290!     rts
.%291!

Convert the given logical block number to the corresponding physical cylinder,
side, and sector numbers.  This routine follows the formulae given earlier
with a few simplifying tricks.

.%292!  convertLogicalBlockNum = *  ;( .AY=blockNum ) : .A=cyl, .X=side, .Y=sec
.%293!     jsr divideBy18
.%294!     ldx #0
.%295!     cpy #9
.%296!     bcc +
.%297!     pha
.%298!     tya
.%299!     sbc #9
.%300!     tay
.%301!     pla
.%302!     ldx #1
.%303!  +  iny
.%304!     rts
.%305!

Copy a sequential group of logical sectors into memory.  This routine is used
by the directory loading routine to load the FAT and Root Directory, and is
used by the cluster reading routine to retrieve all of the blocks of a
cluster.  After the given starting logical sector number is converted into its
physical cylinder, side, and sector equivalent, the physical values are
incremented to get the address of successive sectors of the group.  This
avoids the overhead of the logical to physical conversion.  Quite a number of
temporaries are needed.

.%306!  destPtr = 6
.%307!  curCylinder = 8
.%308!  curSide = 9
.%309!  curSector = 10
.%310!  blockCountdown = 11
.%311!  sourcePtr = 12
.%312!
.%313!  copyBlocks = *  ;( .AY=startBlock, .X=blockCount, ($6)=dest ) : .CS=err
.%314!     stx blockCountdown
.%315!     jsr convertLogicalBlockNum
.%316!     sta curCylinder
.%317!     stx curSide
.%318!     sty curSector
.%319!
.%320!     copyBlockLoop = *
.%321!     lda curCylinder
.%322!     ldx curSide
.%323!     ldy curSector
.%324!     jsr readBlock
.%325!     bcc +
.%326!     rts
.%327!  +  sta sourcePtr
.%328!     sty sourcePtr+1
.%329!     ldx #2
.%330!     ldy #0

Here I unroll the copying loop a little bit to cut the overhead of the branch
instruction in half.  (A cycle saved... you know).

.%331!  -  lda (sourcePtr),y
.%332!     sta (destPtr),y
.%333!     iny
.%334!     lda (sourcePtr),y
.%335!     sta (destPtr),y
.%336!     iny
.%337!     bne -
.%338!     inc sourcePtr+1
.%339!     inc destPtr+1
.%340!     dex
.%341!     bne -

Increment the cylinder, side, sector values.

.%342!     inc curSector
.%343!     lda curSector
.%344!     cmp #10
.%345!     bcc +
.%346!     lda #1
.%347!     sta curSector
.%348!     inc curSide
.%349!     lda curSide
.%350!     cmp #2
.%351!     bcc +
.%352!     lda #0
.%353!     sta curSide
.%354!     inc curCylinder
.%355!  +  dec blockCountdown
.%356!     bne copyBlockLoop
.%357!     clc
.%358!     rts
.%359!

Read a cluster into the Cluster Buffer, given the cluster number.  The cluster
number is converted to a logical sector number and then the sector copying
routine is called.  The formula given earlier is used for the conversion.

.%360!  readCluster = *  ;( .AY=clusterNumber ) : clusterBuf, .CS=err
.%361!     ;** convert cluster number to logical block number
.%362!     sec
.%363!     sbc #2
.%364!     bcs +
.%365!     dey
.%366!  +  ldx clusterBlockCount
.%367!     cpx #1
.%368!     beq +
.%369!     asl
.%370!     sty 7
.%371!     rol 7
.%372!     ldy 7
.%373!  +  clc
.%374!     adc firstFileBlock
.%375!     bcc +
.%376!     iny
.%377!
.%378!     ;** read logical blocks comprising cluster
.%379!  +  ldx #<clusterBuf
.%380!     stx 6
.%381!     ldx #>clusterBuf
.%382!     stx 7
.%383!     ldx clusterBlockCount
.%384!     jmp copyBlocks
.%385!

This next level of routines deal with the data structures of the MS-DOS disk
format.

.%386!  ;====MS-DOS format level====
.%387!
.%388!  bootBlock = 2
.%389!

Read the disk format parameters, directory, and FAT into memory.

.%390!  loadDirectory = *  ;( ) : .AY=dirbuf, .X=dirEntries, .CS=err
.%391!     lda #$0e
.%392!     sta $ff00
.%393!

Read the boot sector and extract the parameters.

.%394!     ;** get parameters from boot sector
.%395!     lda #0
.%396!     ldy #0
.%397!     jsr convertLogicalBlockNum
.%398!     jsr readBlock
.%399!     bcc +
.%400!     rts
.%401!  +  sta bootBlock
.%402!     sty bootBlock+1
.%403!     ldy #13              ;get cluster size
.%404!     lda (bootBlock),y
.%405!     sta clusterBlockCount
.%406!     cmp #3
.%407!     bcc +
.%408!

If a disk parameter is found to exceed the limits of LRR, error code #60 is
returned.

.%409!     invalidParms = *
.%410!     lda #60
.%411!     sta errno
.%412!     sec
.%413!     rts
.%414!
.%415!  +  ldy #16              ;check FAT replication count, must be 2
.%416!     lda (bootBlock),y
.%417!     cmp #2
.%418!     bne invalidParms
.%419!     ldy #22              ;get FAT size in sectors
.%420!     lda (bootBlock),y
.%421!     sta fatBlocks
.%422!     cmp #4
.%423!     bcs invalidParms
.%424!     ldy #17              ;get directory size
.%425!     lda (bootBlock),y
.%426!     sta rootDirEntries
.%427!     cmp #129
.%428!     bcs invalidParms
.%429!     lsr
.%430!     lsr
.%431!     lsr
.%432!     lsr
.%433!     sta rootDirBlocks
.%434!     ldy #19              ;get total sector count
.%435!     lda (bootBlock),y
.%436!     sta totalSectors
.%437!     iny
.%438!     lda (bootBlock),y
.%439!     sta totalSectors+1
.%440!     ldy #24              ;check sectors per track, must be 9
.%441!     lda (bootBlock),y
.%442!     cmp #9
.%443!     bne invalidParms
.%444!     ldy #26
.%445!     lda (bootBlock),y
.%446!     cmp #2               ;check number of sides, must be 2
.%447!     bne invalidParms
.%448!     ldy #14              ;check number of boot sectors, must be 1
.%449!     lda (bootBlock),y
.%450!     cmp #1
.%451!     bne invalidParms
.%452!

Calculate the derived parameters.

.%453!     ;** get derived parameters
.%454!     lda fatBlocks        ;first root directory sector
.%455!     asl
.%456!     clc
.%457!     adc #1
.%458!     sta firstRootDirBlock
.%459!     clc                  ;first file sector
.%460!     adc rootDirBlocks
.%461!     sta firstFileBlock
.%462!     lda totalSectors     ;number of file clusters
.%463!     ldy totalSectors+1
.%464!     sec
.%465!     sbc firstFileBlock
.%466!     bcs +
.%467!     dey
.%468!  +  sta fileClusterCount
.%469!     sty fileClusterCount+1
.%470!     lda clusterBlockCount
.%471!     cmp #2
.%472!     bne +
.%473!     lsr fileClusterCount+1
.%474!     ror fileClusterCount
.%475!

Gee, I have more comments embedded in the code than I did last issue.

.%476!     ;** load FAT
.%477!  +  lda #<fatbuf
.%478!     ldy #>fatbuf
.%479!     sta 6
.%480!     sty 7
.%481!     lda #1
.%482!     ldy #0
.%483!     ldx fatBlocks
.%484!     jsr copyBlocks
.%485!     bcc +
.%486!     rts
.%487!
.%488!     ;** load actual directory
.%489!  +  lda #<dirbuf
.%490!     ldy #>dirbuf
.%491!     sta 6
.%492!     sty 7
.%493!     lda firstRootDirBlock
.%494!     ldy #0
.%495!     ldx rootDirBlocks
.%496!     jsr copyBlocks
.%497!     bcc +
.%498!     rts
.%499!  +  lda #<dirbuf
.%500!     ldy #>dirbuf
.%501!     ldx rootDirEntries
.%502!     clc
.%503!     rts
.%504!

This routine locates the given FAT table entry number and returns the value
stored in it.  Some work is needed to deal with the 12-bit compressed data
structure.

.%505!  entryAddr = 2
.%506!  entryWork = 4
.%507!  entryBits = 5
.%508!  entryData0 = 6
.%509!  entryData1 = 7
.%510!  entryData2 = 8
.%511!
.%512!  getFatEntry = *  ;( .AY=fatEntryNumber ) : .AY=fatEntryValue
.%513!     sta entryBits

Divide the FAT entry number by two and multiply by three because two FAT
entries are stored in three bytes.  Then add the FAT base address and we have
the address of the three bytes that contain the FAT entry we are interested
in.  I retrieve the three bytes into zero-page memory for easy manipulation.

.%514!     ;** divide by two
.%515!     sty entryAddr+1
.%516!     lsr entryAddr+1
.%517!     ror
.%518!
.%519!     ;** times three
.%520!     sta entryWork
.%521!     ldx entryAddr+1
.%522!     asl
.%523!     rol entryAddr+1
.%524!     clc
.%525!     adc entryWork
.%526!     sta entryAddr
.%527!     txa
.%528!     adc entryAddr+1
.%529!     sta entryAddr+1
.%530!
.%531!     ;** add base, get data
.%532!     clc
.%533!     lda entryAddr
.%534!     adc #<fatbuf
.%535!     sta entryAddr
.%536!     lda entryAddr+1
.%537!     adc #>fatbuf
.%538!     sta entryAddr+1
.%539!     ldy #2
.%540!  -  lda (entryAddr),y
.%541!     sta entryData0,y
.%542!     dey
.%543!     bpl -
.%544!     lda entryBits
.%545!     and #1
.%546!     bne +
.%547!

If the original given FAT entry number is even, then we want the first 12-bit
compressed field.  The nybbles are extracted according to the diagram shown
earlier.

.%548!     ;** case 1: first 12-bit cluster
.%549!     lda entryData1
.%550!     and #$0f
.%551!     tay
.%552!     lda entryData0
.%553!     rts
.%554!

Otherwise, we want the second 12-bit field.

.%555!     ;** case 2: second 12-bit cluster
.%556!  +  lda entryData1
.%557!     ldx #4
.%558!  -  lsr entryData2
.%559!     ror
.%560!     dex
.%561!     bne -
.%562!     ldy entryData2
.%563!     rts
.%564!

Finally, this is the file copying level.  It deals with reading the clusters
of MS-DOS files and copying the data they contain to the already-open CBM
Kernal file, possibly with ASCII-to-PETSCII translation.

.%565!  ;====file copy level====
.%566!
.%567!  transMode = 14
.%568!  lfn = 15
.%569!  cbmDataPtr = $60
.%570!  cbmDataLen = $62
.%571!  cluster = $64
.%572!

Copy the given cluster to the CBM output file.  This routine fetches the next
cluster of the file for the next time this routine is called, and if it hits
the NULL pointer of the last cluster of a file, it adjusts the number of valid
file data bytes the current cluster contains to FileLength % ClusterLength
(see note below).

.%573!  copyFileCluster = *  ;( cluster, lfn, transMode ) : .CS=err

Read the cluster and setup to copy the whole cluster to the CBM file.

.%574!     lda cluster
.%575!     ldy cluster+1
.%576!     jsr readCluster
.%577!     bcc +
.%578!     rts
.%579!  +  lda #<clusterBuf
.%580!     ldy #>clusterBuf
.%581!     sta cbmDataPtr
.%582!     sty cbmDataPtr+1
.%583!     lda #0
.%584!     sta cbmDataLen
.%585!     lda clusterBlockCount
.%586!     asl
.%587!     sta cbmDataLen+1
.%588!

Fetch the next cluster number of the file, and adjust the cluster data length
for the last cluster of the file.

.%589!     ;**get next cluster
.%590!     lda cluster
.%591!     ldy cluster+1
.%592!     jsr getFatEntry
.%593!     sta cluster
.%594!     sty cluster+1
.%595!     cmp #$ff
.%596!     bne copyFileClusterData
.%597!     cpy #$0f
.%598!     bne copyFileClusterData
.%599!     lda lenML
.%600!     sta cbmDataLen
.%601!     lda #$01
.%602!     ldx clusterBlockCount
.%603!     cpx #1
.%604!     beq +
.%605!     lda #$03
.%606!  +  and lenML+1

The following three lines were added in a last minute panic after realizing
that if FileLength % ClusterSize == 0, then the last cluster of the file
contains ClusterSize bytes, not zero bytes.

.%000!     bne +
.%000!     ldx lenML
.%000!     beq copyFileClusterData
.%607!  +  sta cbmDataLen+1
.%608!
.%609!     copyFileClusterData = *
.%610!     jsr commieOut
.%611!     rts
.%612!

Copy the file data in the MS-DOS cluster buffer to the CBM output file.

.%613!  cbmDataLimit = $66
.%614!
.%615!  commieOut = *  ;( cbmDataPtr, cbmDataLen ) : .CS=err

If the the logical file number to copy to is 0 ("null device"), then don't
bother copying anything.

.%616!     ldx lfn
.%617!     bne +
.%618!     clc
.%619!     rts

Otherwise, prepare the logical file number for output.

.%620!  +  jsr kernelChkout
.%621!     bcc commieOutMore
.%622!     sta errno
.%623!     rts
.%624!
.%625!     commieOutMore = *

Process the cluster data in chunks of up to 255 bytes or the number of data
bytes remaining in the cluster.

.%626!     lda #255
.%627!     ldx cbmDataLen+1
.%628!     bne +
.%629!     lda cbmDataLen
.%630!  +  sta cbmDataLimit
.%631!     ldy #0
.%632!  -  lda (cbmDataPtr),y
.%633!     bit transMode
.%634!     bpl +

If we have to translate the current ASCII character, look up the PETSCII value
in the translation table and output that value.  If the translation table
entry value is $00, then don't output a character (filter out invalid
character codes).

.%635!     tax
.%636!     lda transBuf,x
.%637!     beq commieNext
.%638!  +  jsr kernelChrout
.%639!     commieNext = *
.%640!     iny
.%641!     cpy cbmDataLimit
.%642!     bne -
.%643!

Increment the cluster buffer pointer and decrement the cluster buffer character
count according to the number of bytes just processed, and repeat the above if
more file data remains in the current cluster.

.%644!     clc
.%645!     lda cbmDataPtr
.%646!     adc cbmDataLimit
.%647!     sta cbmDataPtr
.%648!     bcc +
.%649!     inc cbmDataPtr+1
.%650!  +  sec
.%651!     lda cbmDataLen
.%652!     sbc cbmDataLimit
.%653!     sta cbmDataLen
.%654!     bcs +
.%655!     dec cbmDataLen+1
.%656!  +  lda cbmDataLen
.%657!     ora cbmDataLen+1
.%658!     bne commieOutMore

If we are finished with the cluster, then clear the CBM Kernal output channel.

.%659!     jsr kernelClrchn
.%660!     clc
.%661!     rts
.%662!

The file copying main routine.  Set up for the starting cluster, and call
the cluster copying routine until end-of-file is reached.  Checks for a
NULL cluster pointer in the directory entry to handle zero-length files.

.%663!  copyFile = *  ;( startCluster, lenML, .A=transMode, .X=lfn ) : .CS=err
.%664!     ldy #$0e
.%665!     sty $ff00
.%666!     sta transMode
.%667!     stx lfn
.%668!     lda startCluster
.%669!     ldy startCluster+1
.%670!     sta cluster
.%671!     sty cluster+1
.%672!     jmp +
.%673!  -  jsr copyFileCluster
.%674!     bcc +
.%675!     rts
.%676!  +  lda cluster
.%677!     cmp #$ff
.%678!     bne -
.%679!     lda cluster+1
.%680!     cmp #$0f
.%681!     bne -
.%682!     clc
.%683!     rts
.%684!

This is the translation table used to convert from ASCII to PETSCII.  You can
modify it to suit your needs if you wish.  If you cannot reassemble this file,
then you can sift through the binary file and locate the tabel and change it
there.  An entry of $00 means the corresponding ASCII character will not be
translated.  You'll notice that I have set up translations for the following
ASCII control characters into PETSCII: Backspace, Tab, Linefeed (CR), and
Formfeed.  I also translate the non-PETSCII characters such as {, |, ~, and _
according to what they probably would have been if Commodore wasn't so
concerned with the graphics characters.

.%685!  transBuf = *
.%686!         ;0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
.%687!  .byte $00,$00,$00,$00,$00,$00,$00,$00,$14,$09,$0d,$00,$93,$00,$00,$00 ;0
.%688!  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;1
.%689!  .byte $20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$2b,$2c,$2d,$2e,$2f ;2
.%690!  .byte $30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f ;3
.%691!  .byte $40,$c1,$c2,$c3,$c4,$c5,$c6,$c7,$c8,$c9,$ca,$cb,$cc,$cd,$ce,$cf ;4
.%692!  .byte $d0,$d1,$d2,$d3,$d4,$d5,$d6,$d7,$d8,$d9,$da,$5b,$5c,$5d,$5e,$5f ;5
.%693!  .byte $c0,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f ;6
.%694!  .byte $50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$db,$dc,$dd,$de,$df ;7
.%695!  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;8
.%696!  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;9
.%697!  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;a
.%698!  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;b
.%699!  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;c
.%700!  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;d
.%701!  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;e
.%702!  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;f
.%703!

This is where the track cache, etc. are stored.  This section requires 11K of
storage space but does not increase the length of the binary program file
since these storage areas are DEFINED rather than allocated with ".buf"
directives.  The Unix terminology for this type of uninitialized data is "bss".

.%704!  ;====bss storage====
.%705!
.%706!  bss = *
.%707!  trackbuf   = bss
-- 

- Craig Taylor   || Standard Disclaimer Applies (of course!)
duck@pembvax1.pembroke.edu (Pembroke State University)

Oregon, n.:
	Eighty billion gallons of water with no place to go on Saturday
	night.