User Tools

Site Tools


magazines:chacking6

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

magazines:chacking6 [2015-04-17 04:34] (current)
Line 1: Line 1:
 +<code>
 +                   ########
 +             ##################
 +         ######            ######
 +      #####
 +    #####  ####  ####      ##      #####   ####  ####  ####  ####  ####   #####
 +  #####    ##    ##      ####    ##   ##   ##  ###     ##    ####  ##   ##   ##
 + #####    ########     ##  ##   ##        #####       ##    ## ## ##   ##
 +#####    ##    ##    ########  ##   ##   ##  ###     ##    ##  ####   ##   ##
 +#####  ####  ####  ####  ####  #####   ####  ####  ####  ####  ####   ######
 +#####                                                                    ##
 + ######            ######           Issue #6
 +   ##################            Sept. 5, 1993
 +       ########
  
 +------------------------------------------------------------------------------
 +Editor's Notes:
 +by Craig Taylor
 +
 +  School, 2 jobs, a play, and other work has seriously restricted the amount
 +  of time that I am able to devote to Commodore Hacking. What I am looking at
 +  doing now is to stop writing articles for Commodore Hacking and hoping that
 +  I'll still have enough time to write these notes, and organize and pull all
 +  the other articles by other contributors together. The two articles I
 +  had hoped to include in this issue : the one about multi-tasking and
 +  about the 1351 have again been delayed. I've decided to go ahead and
 +  release issue 6 and hopefully will have them in the next issue.
 +  (Remember: You get what you pay for.. *smile*)
 +
 +  As always, Commodore Hacking is constantly looking for articles, notes,
 +  short little programs, what-not on any side of the Commodore 64 and 128 -
 +  primarily on the technical or hardware side. If you think you have something
 +  in mind, or already written then feel free to drop me a line letting me
 +  know.
 +
 +  In regards to several queries recently about reprinting individual articles
 +  that have appeared in Commodore Hacking. You may reprint Commodore Hacking
 +  and redistribute in whole, freely. For use of individual articles you _must_
 +  contanct the individual author as they still retain rights to it. Please see
 +  the legal notice / mumbo below.
 +
 +  I recently recieved some mail from wolfgang@halcyon regarding a disk
 +  magazine that he was in the process of starting and he has written the
 +  following preview of a disk magazine that looks to be exciting:
 +
 +    "_Scenery_, a new disk-magazine focusing on the american and
 +    european demo scenes, will soon be available for download at a
 +    site/BBS near you! With articles on everything from coding to
 +    instrument synthesis to art, _Scenery_ will be the definitive word
 +    when it comes to creating a demo, or simply coding in general.
 +    Articles are being written by some of the top names in the scene,
 +    and promise to help everybody from the Basic Baby to the ML Mogul!
 +    Set to be released mid-August, _Scenery_ will hopefully be a worthy
 +    edition to the likes of Coder's World and C=Hacking.  I am making
 +    the magazine available on various Internet sites, so look for it. We
 +    are always on the lookout for art, music, and coding talent, and if
 +    you'd be interested in submitting an article for publication, or
 +    simply have a question or comment, please mail me at
 +    'wolfgang@halcyon.com'. Thanks.. and see you on the Net!"
 +
 +================================================================================
 +
 +  Please note that this issue and prior ones are available via anonymous FTP
 +  from ccosun.caltech.edu under pub/rknop/hacking.mag and via a mailserver
 +  which documentation can be obtained by sending mail to
 +  "duck@pembvax1.pembroke.edu" with a subject line of "mailserver" and the
 +  line "help" in the body of the message.
 +
 +================================================================================
 +
 +  NOTICE: Permission is granted to re-distrubte this "net-magazine", in
 +  whole, freely for non-profit use. However, please contact individual 
 +  authors for permission to publish or re-distribute articles seperately.
 +  A charge of no greather than 5 US dollars or equivlent may be charged for 
 +  library service / diskette costs for this "net-magazine".
 +
 +================================================================================
 +</code>
 +===== In This Issue: =====
 +<code>
 +
 +DYCP - Horizontal Scrolling
 +
 +DYCP - is a name for a horizontal scroller, where characters go smoothly
 +up and down during their voyage from right to left. One possibility is a
 +scroll with 8 characters - one character per sprite, but a real demo coder
 +won't be satisfied with that.
 +
 +Opening the borders
 +
 +VIC has many features and transparent borders are one of them. You can not
 +make characters appear in the border, but sprites are displayed in the
 +border too.
 +
 +A Heavy Duty Power supply for the C-64
 +
 +This article describes how to build a heavier duty power supply for your
 +Commodore 64 computer and includes a full schematic in GeoPaint format.
 +
 +LZW Compression
 +
 +LZW is perhaps the most widely used form of data compression today. It
 +is simple to implement and achieves very decent compression at a fairly
 +quick pace. LZW is used in PKZIP (shrink),PKARC (crunch), gifs,V.42bis
 +and unix's compress. This article will attempt to explain how the
 +compression works with a short example and 6502 source code in Buddy
 +format.
 +
 +THREE-KEY ROLLOVER for the C-128 and C-64.
 +
 +This article examines how a three-key rollover mechanism works for the
 +keyboards of the C=128 and C=64 and will present Kernal-wedge
 +implementations for both machines. Webster's doesn't seem to know, so I'll
 +tell you that this means that the machine will act sensibly if you are
 +holding down one key and then press another without releasing the first.
 +This will be useful to fast touch typers.
 +
 +================================================================================
 +</code>
 +===== The Demo Corner: DYCP - Horizontal Scrolling =====
 +<code>
 +by Pasi 'Albert' Ojala (po87553@cs.tut.fi or albert@cc.tut.fi))
 +        Written: 16-May-91 Translation 02-Jun-92
 +
 + DYCP - too many sprites !?
 + --------------------------
 +
 +DYCP - Different Y Character Position - is a name for a horizontal scroller,
 +where characters go smoothly up and down during their voyage from right to
 +left. One possibility is a scroll with 8 characters - one character in each
 +sprite, but a real demo coder won't be satisfied with that.
 +
 +Demo coders thought that it looks good to make the scrolling text change its
 +vertical position in the same time it proceeded from the right side of the
 +screen to the left. The only problem is that there is only eight sprites
 +and that is not even nearly enough to satisfy the requirements needed for
 +great look. So the only way is to use screen and somehow plot the text in
 +graphics, because character columns can not be scrolled individually.
 +Plotting the characters take absolutely too much time, because you have to
 +handle each byte seperately and the graphics bitmap must be cleared too.
 +
 +
 +_Character hack_
 +
 +The whole DYCP started using character graphics. You plot six character
 +rows where the character (screen) codes increase to the right and down.
 +This area is then used like a small bitmap screen. Each of the text chars
 +are displayed one byte at a time on each six rows high character columns.
 +This 240 character positions big piece of screen can be moved horizontally
 +using the x-scroll register (three lowest bits in $D016) and after eight
 +pixels you move the text itself, like in any scroll. The screen is of course
 +reduced to 38 columns wide to hide the jittering on the sides.
 +
 +A good coder may also change the character sets during the display and
 +even double the size of the scroll, but because the raster time happens
 +to go to waste using this technique anyway, that is not very feasible. There
 +are also other difficulties in this approach, the biggest is the time needed
 +to clear the display.
 +
 +
 +_Save characters - and time_
 +
 +But why should we move an eight-byte-high character image in a 48-line-high
 +area, when 16 is really enough ?  We can use two characters for the graphics
 +bitmap and then move this in eight pixel steps up and down. The lowest
 +three bits of the y-position then gives us the offset where the data must
 +be plotted inside this graphical region. The two character codes are usually
 +selected to be consecutive ones so that the image data has also 16
 +consecutive bytes. [See picture 1.]
 +
 +
 +_Demo program might clear things up_
 +
 +The demo program is coded using the latter algorithm. The program first
 +copies the Character ROM to ram, because it is faster to use it from there.
 +You can easily change the program to use your own character set instead,
 +if you like. The sinus data for the vertical movement is created of a 1/4
 +of a cycle by mirroring it both horizontally and vertically.
 +
 +Two most time critical parts are clearing the character set and plotting the
 +new one. Neither of these may happen when VIC is drawing the area where the
 +scroll is, so there is a slight hurry. Using double buffering technique we
 +could overcome this limitation, but this is just an example program. For
 +speed there is CLC only when it is absolutely needed.
 +
 +The NTSC version is a bit crippled, it only covers 32 columns and thus the
 +characters seem to appear from thin air. Anyway, the idea should become
 +clear.
 +
 +
 +_Want to go to the border ?_
 +
 +Some coders are always trying to get all effects ever done using the C64 go
 +to the border, and even successfully. The easiest way is to use only a region
 +of 21 pixels high - sprites - and move the text exactly like in characters.
 +In fact only the different addressing causes the differences in the code.
 +
 +Eight horizontally expanded sprites will be just enough to fill the side
 +borders. You can also mix these techiques, but then you have the usual
 +"chars-in-the-screen-while-border-opened"-problems (however, they are
 +solvable). Unfortunately sprite-dycp is even more slower than char-dycp.
 +
 +
 +_More movement vertically_
 +
 +You might think that using the sprites will restrict the sinus to only
 +14 pixels. Not really, the only restriction is that the vertical position
 +difference between three consequent text character must be less than 14
 +pixel lines. Each sprites' Y-coordinate will be the minimum of the three
 +characters residing in that sprite. Line offsets inside the sprites
 +are then obtained by subtracting the sprite y-coordinate from the character
 +y-coordinate. Maybe a little hard to follow, but maybe a picture will
 +clear the situation. [See picture 2.]
 +
 +Scrolling horizontally is easy. You just have to move sprites like you would
 +use the character horizontal scroll register and after eight pixels you
 +reset the sprite positions and scroll the text one position in memory.
 +And of course, you fetch a new character for the scroll. When we have
 +different and changing sprite y-coordinates, opening the side borders become
 +a great deal more difficult. However, in this case there is at least two
 +different ways to do it.
 +
 +
 +_Stretch the sprites_
 +
 +The easiest way is to position all of the sprites where the scroll will
 +be when it is in its highest position. Then stretch the first and last line
 +of each sprite so that the 19 sprite lines in the middle will be on the
 +desired place. Opening the borders now is trivial, because all of the sprites
 +are present on all of the scan lines and they steal a constant amount of
 +time. However, we lose two sprite lines. We might not want to use the first
 +and the last line for graphics, because they are stretched.
 +[See previous C=Hacking Issues for more information about stretching and
 + stolen cycles.]
 +
 +A more difficult approach is to unroll the routine and let another routine
 +count the sprites present in each line and then change the time the routine
 +uses accordingly. In this way you save time during the display for other
 +effects, like color bars, because stretching will take at least 12 cycles
 +on each raster line. On the other hand, if the sinus is constant (user is
 +not allowed to change it), it is usually possible to embedd the count
 +routine directly to the border opening part of the routine.
 +
 +
 +_More sprites_
 +
 +You don't necassarily need to plot the characters in sprites to have more
 +than eight characters. Using a sprite multiplexing techiques you can double
 +or triple the number of sprites available. You can divide the scroll
 +vertically into several areas and because the y-coordinate of the scroll
 +is a sinus, there always is a fixed maximum number of sprites in each area.
 +This number is always smaller than the total number of sprites in the
 +whole scroll. I won't go into detail, but didn't want to leave this out
 +completely. [See picture 3.]
 +
 +
 +_Smoother and smoother_
 +
 +Why be satisfied with a scroll with only 40 different slices horizontally ?
 +It should be possible to count own coordinates for each pixel column on
 +the scroll. In fact the program won't be much different, but the routine
 +must also mask the unwanted bits and write the byte to memory with ORA+STA.
 +When you think about it more, it is obvious that this takes a generous amount
 +of time, handling every bit seperately will take much more than eight times
 +the time a simple LDA+STA takes. Some coders have avoided this by plotting
 +the same character to different character sets simultaneously and then
 +changing the charsets appropriately, but the resulting scroll won't be much
 +larger than 96x32 pixels.
 +
 +--------------------------------------------------------------------------
 +Picture 1 - Two character codes will make a graphical bitmap
 +
 +Screen memory:
 + ____________________________________
 +|Char   |Char   |Char   |Char    ...
 +|Code   |Code   |Code   |Code   |
 +|0      |2      |80     |80     | .
 +|       |**  ** |             | .
 +|       |**  ** |             | .
 +|*****  |****** |             |
 +|****** | ****  |             |
 +|**__**_|__**___|_______|_______|
 +|**  ** |  **   | ****  |Char   |
 +|**  ** |  **   |****** |Code   |
 +|****** |       |**  ** |6      |
 +|*****  |       |**           |
 +|Char   |Char   |**  ** |       |
 +|Code   |Code   |****** |       |
 +|1      |3      |4****  |*****  |
 +|_______|_______|_______|******_|
 +|Char   |Char         |**  ** |
 +|Code   |Code         |****** |
 +|80     |80           |*****  |
 +|                   |**     |
 +|             |Char   |**ar   |
 +|             |Code   |Code   |
 +|             |5      |7      |
 +|_______|_______|_______|_______|
 +
 +Character set memory:
 +
 + _________________________________________________________________
 +|Char 0 |Char 1 |Char 2 |Char 3 |Char 4 |Char 5 |Char 6 |Char 7 | ...
 +|_______|_______|_______|_______|_______|_______|_______|_______|__
 +      DDDDDDDD      YYYYYYYY     CCCCCCCC              PPPPPPPP
 + First column    Second column   Third column    Fourth column
 +
 +--------------------------------------------------------------------------
 +Picture 2 - DYCP with sprites
 +
 +Sprite 0
 + _______________________
 +|       |**  ** |       |
 +|       |**  ** |       |
 +|       |****** |       |
 +|*****  | ****  |       |
 +|****** |  **         |
 +|**  ** |  **         |
 +|**  ** |  **         |
 +|**__**_|_______|_______|
 +|****** |       | ****  |
 +|*****  |       |****** |
 +|             |**  ** |
 +|             |**     |
 +|             |**  ** |
 +|             |****** |
 +|             | ****  |
 +|_______|_______|_______| Sprite 1
 +|                   | _______________________
 +|                   ||*****  |             |
 +|                   ||****** |             |
 +|                   ||**  ** |             |
 +|_______|_______|_______||****** |             |
 +                         |*****  |             |
 +                         |**                 |
 +                         |**                 |
 +                         |_______|_______|_______|
 +                                           |
 +                                           |
 +                                           |
 +                               | ****  |       |
 +                               | ****  |       |
 +                                     |****** |
 +                                     |****** |
 +                         |_______|_______|__**___|
 +                                      **   |
 +                                      **   |
 +                                      **   |
 +                                      **   |
 +                         |_______|_______|_______|
 +
 +--------------------------------------------------------------------------
 +Picture 3 - Sprite multiplexing
 +
 +                               __          Set coordinates for eight sprites
 +                            __|3 |         that start from the top half.
 +                           |4 |  |__
 +                         __|  `--|2 |
 +                        |5 `--'  |  |
 +                        |  |     `--'__
 +                        `--'        |1 |
 +                                    |  |
 +                      __            `--'
 +                     |6 |
 +                      |               __
 +                     `--'              |0 |
 +                                        |
 +-__------------------------------------`--'When VIC has displayed the last
 +|0 |               __                      sprite, set coordinates for the
 +|  |              |6 |                     sprites in the lower half of the
 +`--'              |  |                     area.
 +                  `--'
 +    __
 +   |1 |         __
 +    |        |5 |
 +   `-- __      |  |
 +      |2 |   __`--'
 +      |  |__|4 |                           You usually have two sprites that
 +      `--|3 |  |                           are only 'used' once so that you
 +          `--'                           can change other sprites when VIC
 +         `__'                              is displaying them.
 +--------------------------------------------------------------------------
 +
 +DYCP demo program (PAL)
 +
 +
 +SINUS=  $CF00   ; Place for the sinus table
 +CHRSET= $3800   ; Here begins the character set memory
 +GFX=    $3C00   ; Here we plot the dycp data
 +X16=    $CE00   ; values multiplicated by 16 (0,16,32..)
 +D16=    $CE30   ; divided by 16  (16 x 0,16 x 1 ...)
 +START=  $033C   ; Pointer to the start of the sinus
 +COUNTER= $033D  ; Scroll counter (x-scroll register)
 +POINTER= $033E  ; Pointer to the text char
 +YPOS=   $0340   ; Lower 4 bits of the character y positions
 +YPOSH=  $0368   ; y positions divided by 16
 +CHAR=   $0390   ; Scroll text characters, multiplicated by eight
 +ZP=     $FB     ; Zeropage area for indirect addressing
 +ZP2=    $FD
 +AMOUNT= 38      ; Amount of chars to plot-1
 +PADCHAR= 32     ; Code used for clearing the screen
 +
 +*= $C000
 +
 +        SEI             ; Disable interrupts
 +        LDA #$32        ; Character generator ROM to address space
 +        STA $01
 +        LDX #0
 +LOOP0   LDA $D000,    ; Copy the character set
 +        STA CHRSET,X
 +        LDA $D100,X
 +        STA CHRSET+256,X
 +        DEX
 +        BNE LOOP0
 +        LDA #$37        ; Normal memory configuration
 +        STA $01
 +        LDY #31
 +LOOP1   LDA #66         ; Compose a full sinus from a 1/4th of a
 +        CLC             ;   cycle
 +        ADC SIN,X
 +        STA SINUS,X
 +        STA SINUS+32,Y
 +        LDA #64
 +        SEC
 +        SBC SIN,X
 +        STA SINUS+64,X
 +        STA SINUS+96,Y
 +        INX
 +        DEY
 +        BPL LOOP1
 +        LDX #$7F
 +LOOP2   LDA SINUS,X
 +        LSR
 +        CLC
 +        ADC #32
 +        STA SINUS+128,X
 +        DEX
 +        BPL LOOP2
 +
 +        LDX #39
 +LOOP3   TXA
 +        ASL
 +        ASL
 +        ASL
 +        ASL
 +        STA X16,X       ; Multiplication table (for speed)
 +        TXA
 +        LSR
 +        LSR
 +        LSR
 +        LSR
 +        CLC
 +        ADC #>GFX
 +        STA D16,X       ; Dividing table
 +        LDA #0
 +        STA CHAR,     ; Clear the scroll
 +        DEX
 +        BPL LOOP3
 +        STA POINTER     ; Initialize the scroll pointer
 +        LDX #7
 +        STX COUNTER
 +LOOP10  STA CHRSET,   ; Clear the @-sign..
 +        DEX
 +        BPL LOOP10
 +
 +        LDA #>CHRSET    ; The right page for addressing
 +        STA ZP2+1
 +        LDA #<IRQ       ; Our interrupt handler address
 +        STA $0314
 +        LDA #>IRQ
 +        STA $0315
 +        LDA #$7F        ; Disable timer interrupts
 +        STA $DC0D
 +        LDA #$81        ; Enable raster interrupts
 +        STA $D01A
 +        LDA #$A8        ; Raster compare to scan line $A8
 +        STA $D012
 +        LDA #$1B        ; 9th bit
 +        STA $D011
 +        LDA #30
 +        STA $D018       ; Use the new charset
 +        CLI             ; Enable interrupts and return
 +        RTS
 +
 +IRQ     INC START       ; Increase counter
 +        LDY #AMOUNT
 +        LDX START
 +LOOP4   LDA SINUS,    ; Count a pointer for each text char and according
 +        AND #7          ;  to it fetch a y-position from the sinus table
 +        STA YPOS,     ;   Then divide it to two bytes
 +        LDA SINUS,X
 +        LSR
 +        LSR
 +        LSR
 +        STA YPOSH,Y
 +        INX             ; Chars are two positions apart
 +        INX
 +        DEY
 +        BPL LOOP4
 +
 +        LDA #0
 +        LDX #79
 +LOOP11  STA GFX,X       ; Clear the dycp data
 +        STA GFX+80,X
 +        STA GFX+160,X
 +        STA GFX+240,X
 +        STA GFX+320,X
 +        STA GFX+400,X
 +        STA GFX+480,X
 +        STA GFX+560,X
 +        DEX
 +        BPL LOOP11
 +
 +MAKE    LDA COUNTER     ; Set x-scroll register
 +        STA $D016
 +        LDX #AMOUNT
 +        CLC             ; Clear carry
 +LOOP5   LDY YPOSH,    ; Determine the position in video matrix
 +        TXA
 +        ADC LINESL,   ; Carry won't be set here
 +        STA ZP          ; low byte
 +        LDA #4
 +        ADC LINESH,Y
 +        STA ZP+1        ; high byte
 +        LDA #PADCHAR    ; First clear above and below the char
 +        LDY #0          ; 0. row
 +        STA (ZP),Y
 +        LDY #120        ; 3. row
 +        STA (ZP),Y
 +        TXA             ; Then put consecuent character codes to the places
 +        ASL             ;  Carry will be cleared
 +        ORA #$80 ; Inverted chars
 +        LDY #40         ; 1. row
 +        STA (ZP),Y
 +        ADC #1          ; Increase the character code, Carry won't be set
 +        LDY #80         ; 2. row
 +        STA (ZP),Y
 +
 +        LDA CHAR,     ; What character to plot ? (source)
 +        STA ZP2         ;  (char is already multiplicated by eight)
 +        LDA X16,X       ; Destination low byte
 +        ADC YPOS,     ;  (16*char code + y-position's 3 lowest bits)
 +        STA ZP
 +        LDA D16,X       ; Destination high byte
 +        STA ZP+1
 +
 +        LDY #6          ; Transfer 7 bytes from source to destination
 +        LDA (ZP2),Y : STA (ZP),Y
 +        DEY             ; This is the fastest way I could think of.
 +        LDA (ZP2),Y : STA (ZP),Y
 +        DEY
 +        LDA (ZP2),Y : STA (ZP),Y
 +        DEY
 +        LDA (ZP2),Y : STA (ZP),Y
 +        DEY
 +        LDA (ZP2),Y : STA (ZP),Y
 +        DEY
 +        LDA (ZP2),Y : STA (ZP),Y
 +        DEY
 +        LDA (ZP2),Y : STA (ZP),Y
 +        DEX
 +        BPL LOOP5 ; Get next char in scroll
 +
 +        LDA #1
 +        STA $D019       ; Acknowledge raster interrupt
 +
 +        DEC COUNTER     ; Decrease the counter = move the scroll by 1 pixel
 +        BPL OUT
 +LOOP12  LDA CHAR+1,   ; Move the text one position to the left
 +        STA CHAR,     ;  (Y-register is initially zero)
 +        INY
 +        CPY #AMOUNT
 +        BNE LOOP12
 +        LDA POINTER
 +        AND #63         ; Text is 64 bytes long
 +        TAX
 +        LDA SCROLL,   ; Load a new char and multiply it by eight
 +        ASL
 +        ASL
 +        ASL
 +        STA CHAR+AMOUNT ; Save it to the right side
 +        DEC START       ; Compensation for the text scrolling
 +        DEC START
 +        INC POINTER     ; Increase the text pointer
 +        LDA #7
 +        STA COUNTER     ; Initialize X-scroll
 +
 +OUT     JMP $EA7E       ; Return from interrupt
 +
 +SIN     BYT 0,3,6,9,12,15,18,21,24,27,30,32,35,38,40,42,45
 +        BYT 47,49,51,53,54,56,57,59,60,61,62,62,63,63,63
 +                        ; 1/4 of the sinus
 +
 +LINESL  BYT 0,40,80,120,160,200,240,24,64,104,144,184,224
 +        BYT 8,48,88,128,168,208,248,32
 +
 +LINESH  BYT 0,0,0,0,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,3
 +
 +SCROLL  SCR "THIS@IS@AN@EXAMPLE@SCROLL@FOR@"
 +        SCR "COMMODORE@MAGAZINE@BY@PASI@OJALA@@"
 +                        ; SCR will convert text to screen codes
 +
 +--------------------------------------------------------------------------
 +Basic loader for the Dycp demo program (PAL)
 +
 +1 S=49152
 +2 DEFFNH(C)=C-48+7*(C>64)
 +3 CH=0:READA$,A:PRINTA$:IFA$="END"THENPRINT"<white><clr>":SYS49152:END
 +4 FORF=0TO31:Q=FNH(ASC(MID$(A$,F*2+1)))*16+FNH(ASC(MID$(A$,F*2+2)))
 +5 CH=CH+Q:POKES,Q:S=S+1:NEXT:IFCH=ATHEN3
 +6 PRINT"CHECKSUM ERROR":END
 +100 DATA 78A9328501A200BD00D09D0038BD00D19D0039CAD0F1A9378501A01FA942187D, 3441
 +101 DATA 75C19D00CF9920CFA94038FD75C19D40CF9960CFE88810E4A27FBD00CF4A1869, 4302
 +102 DATA 209D80CFCA10F3A2278A0A0A0A0A9D00CE8A4A4A4A4A18693C9D30CEA9009D90, 3231
 +103 DATA 03CA10E58D3E03A2078E3D039D0038CA10FAA93885FEA99B8D1403A9C08D1503, 3338
 +104 DATA A97F8D0DDCA9818D1AD0A9A88D12D0A91B8D11D0A91E8D18D05860EE3C03A026, 3864
 +105 DATA AE3C03BD00CF2907994003BD00CF4A4A4A996803E8E88810EAA900A24F9D003C, 3256
 +106 DATA 9D503C9DA03C9DF03C9D403D9D903D9DE03D9D303ECA10E5AD3D038D16D0A226, 3739
 +107 DATA 18BC68038A7995C185FBA90479AAC185FCA920A00091FBA07891FB8A0A0980A0, 4224
 +108 DATA 2891FB6901A05091FBBD900385FDBD00CE7D400385FBBD30CE85FCA006B1FD91, 4440
 +109 DATA FB88B1FD91FB88B1FD91FB88B1FD91FB88B1FD91FB88B1FD91FB88B1FD91FBCA, 6225
 +110 DATA 109FEE19D0CE3D031028B99103999003C8C026D0F5AD3E03293FAABDBFC10A0A, 3593
 +111 DATA 0A8DB603CE3C03CE3C03EE3E03A9078D3D034C7EEA000306090C0F1215181B1E, 2159
 +112 DATA 202326282A2D2F3133353638393B3C3D3E3E3F3F3F00285078A0C8F018406890, 2268
 +113 DATA B8E008305880A8D0F82000000000000000010101010101020202020202020314, 1379
 +114 DATA 08091300091300010E000518010D100C05001303120F0C0C00060F1200030F0D, 304
 +115 DATA 0D0F040F1205000D0107011A090E050002190010011309000F0A010C01000000, 257
 +200 DATA END,0
 +
 +--------------------------------------------------------------------------
 +Uuencoded C64 executable of the basic loader (PAL)
 +
 +begin 644 dycp.64
 +M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`4@@#`$-(@
 +MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(@63(CJ>-#DQ-3(Z@`")"`0`@4:RE
 +M,*0S,3I1LJ5(*,8HRBA!)"Q&K#*J,2DI*:PQ-JJE2"C&*,HH020L1JPRJC(IA
 +M*2D`J@@%`$-(LD-(JE$ZEU,L43I3LE.J,3J".HM#2+)!IS,`P@@&`)DB0TA%.
 +M0TM354T@15)23U(B.H``#PED`(,@-SA!.3,R.#4P,4$R,#!"1#`P1#`Y1#`P.
 +M,SA"1#`P1#$Y1#`P,SE#040P1C%!.3,W.#4P,4$P,49!.30R,3@W1"P@,S0T#
 +M,0!<"64`@R`W-4,Q.40P,$-&.3DR,$-&03DT,#,X1D0W-4,Q.40T,$-&.3DV&
 +M,$-&13@X.#$P131!,C=&0D0P,$-&-$$Q.#8Y+"`T,S`R`*D)9@"#(#(P.40X3
 +M,$-&0T$Q,$8S03(R-SA!,$$P03!!,$$Y1#`P0T4X031!-$$T031!,3@V.3-#P
 +M.40S,$-%03DP,#E$.3`L(#,R,S$`]@EG`(,@,#-#03$P134X1#-%,#-!,C`WY
 +M.$4S1#`S.40P,#,X0T$Q,$9!03DS.#@U1D5!.3E".$0Q-#`S03E#,#A$,34P@
 +M,RP@,S,S.`!#"F@`@R!!.3=&.$0P1$1#03DX,3A$,4%$,$$Y03@X1#$R1#!!B
 +M.3%".$0Q,40P03DQ13A$,3A$,#4X-C!%13-#,#-!,#(V+"`S.#8T`)`*:0"#]
 +M($%%,T,P,T)$,#!#1C(Y,#<Y.30P,#-"1#`P0T8T031!-$$Y.38X,#-%.$4X$
 +M.#@Q,$5!03DP,$$R-$8Y1#`P,T,L(#,R-38`W0IJ`(,@.40U,#-#.41!,#-#]
 +M.41&,#-#.40T,#-$.40Y,#-$.41%,#-$.40S,#-%0T$Q,$4U040S1#`S.$0Q*
 +M-D0P03(R-BP@,S<S.0`J"VL`@R`Q.$)#-C@P,SA!-SDY-4,Q.#5&0D$Y,#0W^
 +M.4%!0S$X-49#03DR,$$P,#`Y,49"03`W.#DQ1D(X03!!,#DX,$$P+"`T,C(T:
 +M`'<+;`"#(#(X.3%&0C8Y,#%!,#4P.3%&0D)$.3`P,S@U1D1"1#`P0T4W1#0P;
 +M,#,X-49"0D0S,$-%.#5&0T$P,#9",49$.3$L(#0T-#``Q`MM`(,@1D(X.$(Q?
 +M1D0Y,49".#A",49$.3%&0C@X0C%&1#DQ1D(X.$(Q1D0Y,49".#A",49$.3%&V
 +M0C@X0C%&1#DQ1D)#02P@-C(R-0`1#&X`@R`Q,#E&144Q.40P0T4S1#`S,3`RK
 +M.$(Y.3$P,SDY.3`P,T,X0S`R-D0P1C5!1#-%,#,R.3-&04%"1$)&0S$P03!!M
 +M+"`S-3DS`%X,;P"#(#!!.$1"-C`S0T4S0S`S0T4S0S`S144S13`S03DP-SA$H
 +M,T0P,S1#-T5%03`P,#,P-C`Y,$,P1C$R,34Q.#%",44L(#(Q-3D`JPQP`(,@0
 +M,C`R,S(V,C@R03)$,D8S,3,S,S4S-C,X,SDS0C-#,T0S13-%,T8S1C-&,#`R[
 +M.#4P-SA!,$,X1C`Q.#0P-C@Y,"P@,C(V.`#X#'$`@R!".$4P,#@S,#4X.#!!8
 +M.$0P1C@R,#`P,#`P,#`P,#`P,#`P,#$P,3`Q,#$P,3`Q,#(P,C`R,#(P,C`R^
 +M,#(P,S$T+"`Q,S<Y`$0-<@"#(#`X,#DQ,S`P,#DQ,S`P,#$P13`P,#4Q.#`Q7
 +M,$0Q,#!#,#4P,#$S,#,Q,C!&,$,P0S`P,#8P1C$R,#`P,S!&,$0L(#,P-`"02
 +M#7,`@R`P1#!&,#0P1C$R,#4P,#!$,#$P-S`Q,4$P.3!%,#4P,#`R,3DP,#$P.
 +L,#$Q,S`Y,#`P1C!!,#$P0S`Q,#`P,#`P+"`R-3<`G`W(`(,@14Y$+#`````PK
 +``
 +end
 +size 1439
 +--------------------------------------------------------------------------
 +Uuencoded C64 executable of the basic loader (NTSC)
 +
 +begin 644 dycp-ntsc.bas
 +M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`40@#`$-(?
 +MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(I,B.IXT.3$U,CJ``(@(!`"!1K(P/
 +MI#,Q.E&RI4@HQBC**$$D+$:L,JHQ*2DIK#$VJJ5(*,8HRBA!)"Q&K#*J,BDI:
 +M*0"I"`4`0TBR0TBJ43J74RQ1.E.R4ZHQ.H(ZBT-(LD&G,P#!"`8`F2)#2$5#F
 +M2U-532!%4E)/4B(Z@`#'"%H`@``4"60`@R`W.$$Y,S(X-3`Q03(P,$)$,#!$L
 +M,#E$,#`S.$)$,#!$,3E$,#`S.4-!1#!&,4$Y,S<X-3`Q03`Q1D$Y-#(Q.#=$I
 +M+"`S-#0Q`&$)90"#(#<U0S$Y1#`P0T8Y.3(P0T9!.30P,SA&1#<U0S$Y1#0P!
 +M0T8Y.38P0T9%.#@X,3!%-$$R-T9"1#`P0T8T03$X-CDL(#0S,#(`K@EF`(,@R
 +M,C`Y1#@P0T9#03$P1C-!,C(W.$$P03!!,$$P03E$,#!#13A!-$$T031!-$$QJ
 +M.#8Y,T,Y1#,P0T5!.3`P.40Y,"P@,S(S,0#["6<`@R`P,T-!,3!%-3A$,T4P.
 +M,T$R,#<X13-$,#,Y1#`P,SA#03$P1D%!.3,X.#5&14$Y.4(X1#$T,#-!.4,P;
 +M.$0Q-3`S+"`S,S,X`$@*:`"#($$Y-T8X1#!$1$-!.3@Q.$0Q040P03E",#A$:
 +M,3)$,$$Y,4(X1#$Q1#!!.3%%.$0Q.$0P-3@V,$5%,T,P,T$P,4,L(#,X-C(`9
 +ME0II`(,@044S0S`S0D0P,$-&,CDP-SDY-#`P,T)$,#!#1C1!-$$T03DY-C@PB
 +M,T4X13@X.#$P14%!.3`P03(T1CE$,#`S0RP@,S(U-@#B"FH`@R`Y1#4P,T,Y$
 +M1$$P,T,Y1$8P,T,Y1#0P,T0Y1#DP,T0Y1$4P,T0Y1#,P,T5#03$P135!1#-$E
 +M,#,X1#$V1#!!,C%#+"`S-S(Y`"\+:P"#(#$X0D,V.#`S.$$W.3DU0S$X-49")
 +M03DP-#<Y04%#,3@U1D-!.3(P03`P,#DQ1D)!,#<X.3%&0CA!,$$P.3@P03`L#
 +M(#0R,C0`?`ML`(,@,C@Y,49"-CDP,4$P-3`Y,49"0D0Y,#`S.#5&1$)$,#!#H
 +M13=$-#`P,S@U1D)"1#,P0T4X-49#03`P-D(Q1D0Y,2P@-#0T,`#)"VT`@R!&C
 +M0C@X0C%&1#DQ1D(X.$(Q1D0Y,49".#A",49$.3%&0C@X0C%&1#DQ1D(X.$(QA
 +M1D0Y,49".#A",49$.3%&0D-!+"`V,C(U`!8,;@"#(#$P.49%13$Y1#!#13-$T
 +M,#,Q,#(X0CDY,3`S.3DY,#`S0SA#,#(V1#!&-4%$,T4P,S(Y,T9!04)$0D9#0
 +M,3!!,$$L(#,U.3,`8PQO`(,@,$$X1$(V,#-#13-#,#-#13-#,#-%13-%,#-!D
 +M.3`W.$0S1#`S-$,W145!,#`P,S`V,#DP0S!&,3(Q-3$X,4(Q12P@,C$U.0"P2
 +M#'``@R`R,#(S,C8R.#)!,D0R1C,Q,S,S-3,V,S@S.3-",T,S1#-%,T4S1C-&/
 +M,T8P,#(X-3`W.$$P0SA&,#$X-#`V.#DP+"`R,C8X`/T,<0"#($(X13`P.#,P2
 +M-3@X,$$X1#!&.#(P,#`P,#`P,#`P,#`P,#`P,3`Q,#$P,3`Q,#$P,C`R,#(P>
 +M,C`R,#(P,C`S,30L(#$S-SD`20UR`(,@,#@P.3$S,#`P.3$S,#`P,3!%,#`P3
 +M-3$X,#$P1#$P,$,P-3`P,3,P,S$R,$8P0S!#,#`P-C!&,3(P,#`S,$8P1"P@J
 +M,S`T`)4-<P"#(#!$,$8P-#!&,3(P-3`P,$0P,3`W,#$Q03`Y,$4P-3`P,#(Q`
 +M.3`P,3`P,3$S,#DP,#!&,$$P,3!#,#$P,#`P,#`L(#(U-P"A#7@`@R!%3D0LZ
 +$,````#`P0
 +``
 +end
 +size 1444
 +
 +================================================================================
 +</code>
 +===== Opening the borders =====
 +<code>
 +by Pasi 'Albert' Ojala (po87553@cs.tut.fi or albert@cc.tut.fi)
 +        Written: 20-Jul-92
 +
 +       All timings are in PAL, principles will apply to NTSC too.
 +              Refer to VIC memory map in Hacking Issue 4.
 +
 +VIC has many features and transparent borders are one of them. You can not
 +make characters appear in the border, but sprites are displayed in the
 +border too. "How to do this then?" is the big question.
 +
 +The screen resolution in C64 has been and will be 320 x 200 pixels. Most
 +games need to use the whole screen to be efficient or just plain playable.
 +But there still is that useless border area, and you can put score and
 +other status information there instead of having them interfere with the
 +full-screen smooth-scrolling playing area.
 +
 +
 +_How to disable the vertical borders_
 +
 +When VIC (Video Interface Controller) has displayed all character rows,
 +it will start displaying the vertical border area. It will start displaying
 +the characters again in top of the screen. The row select register sets the
 +number of character lines on the screen. If we select the 24-row display
 +when VIC is drawing the last (25th) row, it does not start to draw the
 +border at all !  VIC will think that it already started to draw the border.
 +
 +The 25-row display must be selected again in the top of the screen, so that
 +the border may be opened in the next frame too. The number of displayed rows
 +can be selected with the bit 3 in $d011. If the bit is set, VIC will display
 +25 rows and 24 rows otherwise. We have to clear the bit somewhere during the
 +last row (raster lines $f2-$fa) and set it again in top of the screen or at
 +least somewhere before the last row (line $f2). This has to be done in every
 +frame (50 times per second in PAL).
 +
 +
 +_How to open the sideborders_
 +
 +The same trick can be applied to sideborders. When VIC is about to start
 +displaying the sideborder, just select 38-column mode and restore 40-column
 +mode so that you can do the trick again in the next scan line. If you need to
 +open the sideborders in the bottom or top border area, you have to open the
 +vertical borders also, but there shouldn't be any difficulty in doing that.
 +
 +There is two drawbacks in this. The timing must be precise, one clock cycle
 +off and the sideborder will not open (the sprites will generally take care of
 +the timing) and you have to do the opening on each and every line. With
 +top/bottom borders once in a frame was enough.
 +
 +Another problem is bad-lines. There is not enough time to open the borders
 +during a bad line and still have all of the sprites enabled. One solution
 +is to open the borders only on seven lines and leave the bad lines unopened.
 +Another way is to use less than eight sprites. You can have six of them
 +on a bad line and still be able to open the sideborders (PAL). The old and
 +still good solution is to scroll the bad lines, so that VIC will not start
 +to draw the screen at all until it is allowed to do so.
 +[Read more about bad lines from previous C=Hacking Issues]
 +
 +
 +_Scrolling the screen_
 +
 +VIC begins to draw the screen from the first bad line. VIC will know what
 +line is a bad line by comparing its scan line counter to the vertical
 +scroll register : when they match, the next line is a bad line. If we change
 +the vertical scroll register ($d011), the first bad line will move also.
 +If we do this on every line, the line counter in VIC will never match with
 +it and the drawing never starts (until it is allowed to do so).
 +
 +When we don't have to worry about bad lines, we have enough time to open the
 +borders and do some other effects too. It is not necassary to change the
 +vertical scroll on every line to get rid of the bad lines, just make sure
 +that it never matches the line counter (or actually the least significant
 +8 bits).
 +
 +You can even scroll the bad lines independently and you have FLD - Flexible
 +Line Distance. You just allow a bad line when it is time to display the next
 +character row. With this you can bounce the lines or scroll a hires picture
 +very fast down the screen. But this has not so much to do with borders, so
 +I will leave it to another article. (Just send requests and I might start
 +writing about FLD ..)
 +
 +
 +_Garbage appearing_
 +
 +When we open the top and bottom borders, some graphics may appear. Even
 +though VIC has already completed the graphics data fetches for the screen
 +area, it will still fetch data for every character position in top and bottom
 +borders. This will not do any harm though, because it does not generate any
 +bad lines and happens during video fetch cycles [see Missing Cycles article].
 +VIC reads the data from the last address in the current video bank, which is
 +normally $3fff and displays this over and over again.
 +
 +If we change the data in this address in the border area, the change will be
 +visible right away. And if you synchronize the routine to the beam position,
 +you can have a different value on each line. If there is nothing else to do
 +in the border, you can get seven different values on each scan line.
 +
 +The bad thing about this graphics is that it is impossible to change its
 +color - it is always black. It is of course possible to use inverted graphics
 +and change the background color. And if you have different data on each line,
 +you can as easily have different color(s) on each line too.
 +
 +If you don't use $3fff for any effects, it is a good idea to set it to zero,
 +but remember to check that you do not store anything important in that
 +address. In one demo I just cleared $3fff and it was right in the middle of
 +another packed demopart. It took some time to find out what was wrong with
 +the other part.
 +
 +
 +_Horizontal scrolling_
 +
 +This new graphics data also obeys the horizontal scroll register ($D016), so
 +you can do limited tech-tech effects in the border too. You can also use
 +sprites and open the sideborders. You can see an example of the tech-tech
 +effect in the first example program. Multicolor mode select has no effect
 +on this data. You can read more about tech-tech effects in a future article.
 +
 +
 +_Example routine_
 +
 +The example program will show how to open the top and bottom borders and how
 +to use the $3fff-graphics. It is fairly well commented, so just check it for
 +details. The program uses a sprite to do the synchronization [see Missing
 +Cycles article] and reads a part of the character ROM to the display data
 +buffer. To be honest, I might add that this is almost the same routine than
 +the one in the Missing Cycles article. I have included both PAL and NTSC
 +versions of the executables.
 +
 +--------------------------------------------------------------------------
 +The example program - $3fff-graphics
 +
 +IMAGE0= $CE00   ; First graphics piece to show
 +IMAGE1= $CF00   ; Second piece
 +TECH=   $CD00   ; x-shift
 +RASTER= $FA     ; Rasterline for the interrupt
 +DUMMY=  $CFFF   ; Dummy-address for timing (refer to missing_cycles-article)
 +
 +*= $C000
 +        SEI             ; Disable interrupts
 +        LDA #$7F        ; Disable timer interrupts (CIA)
 +        STA $DC0D
 +        LDA #$01        ; Enable raster interrupts (VIC)
 +        STA $D01A
 +        STA $D015       ; Enable the timing sprite
 +        LDA #<IRQ
 +        STA $0314       ; Interrupt vector to our routine
 +        LDA #>IRQ
 +        STA $0315
 +        LDA #RASTER     ; Set the raster compare (9th bit will be set
 +        STA $D012       ;  inside the raster routine)
 +        LDA #RASTER-20  ; Sprite is situated 20 lines before the interrupt
 +        STA $D001
 +
 +        LDX #111
 +        LDY #0
 +        STY $D017       ; Disable y-expand
 +        LDA #$32
 +        STA $01         ; Select Character ROM
 +LOOP0   LDA $D000,X
 +        STA IMAGE0,   ; Copy a part of the charset to be the graphics
 +        STA IMAGE0+112,Y
 +        LDA $D800,X
 +        STA IMAGE1,Y
 +        STA IMAGE1+112,Y
 +        INY             ; Until we copied enough
 +        DEX
 +        BPL LOOP0
 +        LDA #$37        ; Char ROM out of the address space
 +        STA $01
 +
 +        LDY #15
 +LOOP1   LDA XPOS,     ; Take a half of a sinus and mirror it to make
 +        STA TECH,     ;  a whole cycle and then copy it as many times
 +        STA TECH+32,  ;   as necassary
 +        LDA #24
 +        SEC
 +        SBC XPOS,Y
 +        STA TECH+16,Y
 +        STA TECH+48,Y
 +        DEY
 +        BPL LOOP1
 +        LDY #64
 +LOOP2   LDA TECH,Y
 +        STA TECH+64,Y
 +        STA TECH+128,Y
 +        DEY
 +        BPL LOOP2
 +        CLI             ; Enable interrupts
 +        RTS             ; Return to basic (?)
 +
 +
 +IRQ     LDA #$13        ; Open the bottom border (top border will open too)
 +        STA $D011
 +        NOP
 +        LDY #111 ; Reduce for NTSC ?
 +        INC DUMMY       ; Do the timing with a sprite
 +        BIT $EA         ; Wait a bit (add a NOP for NTSC)
 +
 +LOOP3   LDA TECH,     ; Do the x-shift
 +        STA $D016
 +FIRST   LDX IMAGE0,   ; Load the graphics to registers
 +SECOND  LDA IMAGE1,Y
 +        STA $3FFF       ; Alternate the graphics
 +        STX $3FFF
 +        STA $3FFF
 +        STX $3FFF
 +        STA $3FFF
 +        STX $3FFF
 +        STA $3FFF
 +        STX $3FFF
 +        STA $3FFF
 +        STX $3FFF
 +        LDA #0          ; Throw away 2 cycles (add a NOP for NTSC)
 +        DEY
 +        BPL LOOP3
 +
 +        STA $3FFF       ; Clear the graphics
 +        LDA #8
 +        STA $D016       ; x-scroll to normal
 +        LDA #$1B
 +        STA $D011       ; Normal screen (be ready to open the border again)
 +        LDA #111
 +        DEC FIRST+1     ; Move the graphics by changing the low byte of the
 +        BPL OVER        ;  load instruction
 +        STA FIRST+1
 +OVER    SEC
 +        SBC FIRST+1
 +        STA SECOND+1    ; Another graphics goes to opposite direction
 +        LDA LOOP3+1     ; Move the x-shift also
 +        SEC
 +        SBC #2
 +        AND #31         ; Sinus cycle is 32 bytes
 +        STA LOOP3+1
 +
 +        LDA #1
 +        STA $D019       ; Acknowledge the raster interrupt
 +        JMP $EA31       ; jump to the normal irq-handler
 +
 +XPOS    BYT $C,$C,$D,$E,$E,$F,$F,$F,$F,$F,$F,$F,$E,$E,$D,$C
 +        BYT $C,$B,$A,$9,$9,$8,$8,$8,$8,$8,$8,$8,$9,$9,$A,$B
 +                        ; half of the sinus
 +
 +--------------------------------------------------------------------------
 +Basic loader for the $3fff-program (PAL)
 +
 +1 S=49152
 +2 DEFFNH(C)=C-48+7*(C>64)
 +3 CH=0:READA$,A:PRINTA$:IFA$="END"THENPRINT"<clear>":SYS49152:END
 +4 FORF=0TO31:Q=FNH(ASC(MID$(A$,F*2+1)))*16+FNH(ASC(MID$(A$,F*2+2)))
 +5 CH=CH+Q:POKES,Q:S=S+1:NEXT:IFCH=ATHEN3
 +6 PRINT"CHECKSUM ERROR":END
 +100 DATA 78A97F8D0DDCA9018D1AD08D15D0A9718D1403A9C08D1503A9FA8D12D0A9E68D,4003
 +101 DATA 01D0A26FA0008C17D0A9328501BD00D09900CE9970CEBD00D89900CF9970CFC8,4030
 +102 DATA CA10EAA9378501A00FB9DCC09900CD9920CDA91838F9DCC09910CD9930CD8810,4172
 +103 DATA E8A040B900CD9940CD9980CD8810F45860A9138D11D0EAA06FEEFFCF24EAB906,4554
 +104 DATA CD8D16D0BE53CEB91CCF8DFF3F8EFF3F8DFF3F8EFF3F8DFF3F8EFF3F8DFF3F8E,4833
 +105 DATA FF3F8DFF3F8EFF3FA9008810D18DFF3FA9088D16D0A91B8D11D0A96FCE85C010,4163
 +106 DATA 038D85C038ED85C08D88C0AD7FC018E901291F8D7FC0EE19D04C31EA0C0C0D0E,3719
 +107 DATA 0E0F0F0F0F0F0F0F0E0E0D0C0C0B0A09090808080808080809090A0B00000000,318
 +200 DATA END,0
 +
 +--------------------------------------------------------------------------
 +An uuencoded C64 executable $3fff-program (PAL)
 +
 +begin 644 xFFF.64
 +M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`40@#`$-(?
 +MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(I,B.IXT.3$U,CJ``(@(!`"!1K(P/
 +MI#,Q.E&RI4@HQBC**$$D+$:L,JHQ*2DIK#$VJJ5(*,8HRBA!)"Q&K#*J,BDI:
 +M*0"I"`4`0TBR0TBJ43J74RQ1.E.R4ZHQ.H(ZBT-(LD&G,P#!"`8`F2)#2$5#F
 +M2U-532!%4E)/4B(Z@``."60`@R`W.$$Y-T8X1#!$1$-!.3`Q.$0Q040P.$0QK
 +M-40P03DW,3A$,30P,T$Y0S`X1#$U,#-!.49!.$0Q,D0P03E%-CA$+"`T,#`S9
 +M`%L)90"#(#`Q1#!!,C9&03`P,#A#,3=$,$$Y,S(X-3`Q0D0P,$0P.3DP,$-%Y
 +M.3DW,$-%0D0P,$0X.3DP,$-&.3DW,$-&0S@L(#0P,S``J`EF`(,@0T$Q,$5!S
 +M03DS-S@U,#%!,#!&0CE$0T,P.3DP,$-$.3DR,$-$03DQ.#,X1CE$0T,P.3DQL
 +M,$-$.3DS,$-$.#@Q,"P@-#$W,@#U"6<`@R!%.$$P-#!".3`P0T0Y.30P0T0Y0
 +M.3@P0T0X.#$P1C0U.#8P03DQ,SA$,3%$,$5!03`V1D5%1D9#1C(T14%".3`V5
 +M+"`T-34T`$(*:`"#($-$.$0Q-D0P0D4U,T-%0CDQ0T-&.$1&1C-&.$5&1C-&%
 +M.$1&1C-&.$5&1C-&.$1&1C-&.$5&1C-&.$1&1C-&.$4L(#0X,S,`CPII`(,@'
 +M1D8S1CA$1D8S1CA%1D8S1D$Y,#`X.#$P1#$X1$9&,T9!.3`X.$0Q-D0P03DQ-
 +M0CA$,3%$,$$Y-D9#13@U0S`Q,"P@-#$V,P#<"FH`@R`P,SA$.#5#,#,X140X+
 +M-4,P.$0X.$,P040W1D,P,3A%.3`Q,CDQ1CA$-T9#,$5%,3E$,#1#,S%%03!#.
 +M,$,P1#!%+"`S-S$Y`"@+:P"#(#!%,$8P1C!&,$8P1C!&,$8P13!%,$0P0S!#P
 +M,$(P03`Y,#DP.#`X,#@P.#`X,#@P.#`Y,#DP03!",#`P,#`P,#`L(#,Q.``T>
 +-"\@`@R!%3D0L,````#@P1
 +``
 +end
 +size 823
 +--------------------------------------------------------------------------
 +An uuencoded C64 executable $3fff-program (NTSC)
 +
 +begin 644 xfff-ntsc.64
 +M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`40@#`$-(?
 +MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(I,B.IXT.3$U,CJ``(@(!`"!1K(P/
 +MI#,Q.E&RI4@HQBC**$$D+$:L,JHQ*2DIK#$VJJ5(*,8HRBA!)"Q&K#*J,BDI:
 +M*0"I"`4`0TBR0TBJ43J74RQ1.E.R4ZHQ.H(ZBT-(LD&G,P#!"`8`F2)#2$5#F
 +M2U-532!%4E)/4B(Z@`#'"%H`@``4"60`@R`W.$$Y-T8X1#!$1$-!.3`Q.$0QX
 +M040P.$0Q-40P03DW,3A$,30P,T$Y0S`X1#$U,#-!.49!.$0Q,D0P03E%-CA$H
 +M+"`T,#`S`&$)90"#(#`Q1#!!,C9&03`P,#A#,3=$,$$Y,S(X-3`Q0D0P,$0PX
 +M.3DP,$-%.3DW,$-%0D0P,$0X.3DP,$-&.3DW,$-&0S@L(#0P,S``K@EF`(,@H
 +M0T$Q,$5!03DS-S@U,#%!,#!&0CE$14,P.3DP,$-$.3DR,$-$03DQ.#,X1CE$`
 +M14,P.3DQ,$-$.3DS,$-$.#@Q,"P@-#$W-@#["6<`@R!%.$$P-#!".3`P0T0Y8
 +M.30P0T0Y.3@P0T0X.#$P1C0U.#8P03DQ,SA$,3%$,$5!03`V1D5%1D9#1C(T+
 +M14%%04(Y+"`T-S@R`$@*:`"#(#`P0T0X1#$V1#!"13`P0T5".3`P0T8X1$9&>
 +M,T8X149&,T8X1$9&,T8X149&,T8X1$9&,T8X149&,T8X1$9&,T8L(#0U.#``?
 +ME0II`(,@.$5&1C-&.$1&1C-&.$5&1C-&14%!.3`P.#@Q,$0P.$1&1C-&03DP`
 +M.#A$,39$,$$Y,4(X1#$Q1#!!.39&0T4X-BP@-#,S,0#B"FH`@R!#,#$P,#,XY
 +M1#@V0S`S.$5$.#9#,#A$.#E#,$%$.#!#,#$X13DP,3(Y,48X1#@P0S!%13$YO
 +M1#`T0S,Q14$P0S!#+"`S.3`U`"X+:P"#(#!$,$4P13!&,$8P1C!&,$8P1C!&W
 +M,$4P13!$,$,P0S!",$$P.3`Y,#@P.#`X,#@P.#`X,#@P.3`Y,$$P0D$R,#`L%
 +4(#4P-P`["VP`@R!%3D0L+3$````PB
 +``
 +end
 +size 830
 +
 +=============================================================================
 +</code>
 +===== A Heavy-Duty Power Supply for the C-64 =====
 +<code>
 +by John C. Andrews (no email address)
 +
 +  As a Commodore User for the last 4 plus years, I am aware of the many
 +  articles and letters in the press that have bemoaned the burn-out
 +  problem of the C-64 power supply. When our Club BBS added a one meg
 +  drive and stayed on around the clock, the need for heavy-duty power
 +  supply became very apparent.... Three power supplies went in 3
 +  successive days!
 +
 +  Part of the problem was my ignoring the seasons. You see during the
 +  winter I had set the power supply between the window and the screen,
 +  Yes, outside! With the advent of Spring... well, you get the picture.
 +
 +  The turn-around time forgetting a new commerical supply was not in the
 +  best interest of the BBS and its members. Therefore, taking power
 +  supply inhand, I proceeded to cut one open on my shop bandsaw. I do
 +  not suggest that you do this. The parts are FIRMLY and COMPLETELY
 +  encased in a hard plastic potting compound. The purpose of this is not
 +  to make the item difficult to repair, but to make the entire unit
 +  conductive to the heat generated inside. I doubt the wisedom of
 +  potting the fuse as well. However, CBM was probably thinking of the
 +  number of little fingers that could fit into an accessable fuse
 +  holder. if you want the punch line it is: the final circuit board and
 +  its componets are about the size of a box of matches. This includes
 +  the built-in metal heat sink.
 +
 +  From these minscule innards I traced out the circuit and increased the
 +  size of ALL components.
 +
 +  The handiest source of electronic parts is, of course, Radio Shack.
 +  All but one part can be purchased there.
 +
 +        212-1013        Capacitor, 35V, 4700 mF
 +        212-1022        Capacitor, 35V, 10 uF
 +        273-1515        Transformer, 2 Amp, 9-0-9 VAC
 +        276-1184        Rectifier
 +
 +        270- 742        Fuse Block
 +        270-1275        Fuses
 +
 +  Note that there are only five parts. The rest are fuses, fuse blocks,
 +  heat sinks, wire and misc. hardware. Note also that I have not listed
 +  any plugs and cords. This because you can clip the cords off of both
 +  sides of your defunct power supply. This will save you the hassle of
 +  wriing the DIN power plug correctly:
 +
 +        DIN PIN OUT                   COLOR
 +          pin 6         9VAC            black
 +          pin 7         9VAC            black
 +          pin 5         +5 Volts        blue
 +          pin 1,2,3     shield, gnd     orange
 +
 +  The part that you can NOT get at Radio Shack is the power regulator.
 +  This part will have to be scrounged up from some local, big
 +  electronics supply house:
 +
 +        SK 9067    5 volt voltage regulator, 3+ amps. (I prefer the 5 amp.)
 +
 +  Radio Shack does carry regulators, but their capacity is no larger
 +  than that with which you started.
 +
 +  The Heat sinks, (yes, more than one!) are the key to the success of
 +  this project. The ones I used came from my Model Railroading days.
 +  Sorry to say, I did just ahve them 'lying about'. The heat sinks that
 +  I priced at the local electronics supply were more costly than the
 +  other parts. The worst case situation is that you may need to drill
 +  out a couple pieces of aluminum sheet. Try for 12 x 12, and bend them
 +  into square bottomed U-shapes to save room. heat sinks should not
 +  touch, or be electronically grounded to each other. You can also mount
 +  them on stand-offs from your chassis for total air circulation.
 +
 +  The Radio Shack transformer is rated at only 2 amps. If you can not
 +  find one with a higher rating elsewhere, it is possible to hook two in
 +  parallel to get a 4 ampere output. This si tricky, as it can be done
 +  either right or wrong!
 +
 +  Here is how to do it the right way:
 +    Tape off one yellow secondary lead on each transformer. With tape
 +    mark the four remaining secondary leads and letter them A and B on
 +    one transformer, C and D onthe other. Hook up the black primary
 +    leads to a plug to your 120 wall outlet:
 +
 +                                    |-------------
 +   Note: *'s - indicate connections |            3 ||
 +         +'s - indicate skip overs  |            3 || (Transformer)
 +                                    |            3 ||
 +                                    |            3 ||
 +                                    |   ----------
 +                                    |   |
 +          +--\  /-------------------*---+---------
 +        --|120|/                        |        3 ||
 +        --|Vlt|             ____        |        3 ||
 +         -|Plg|------------|FUSE|-------*        3 ||
 +          +--/              ----        |        3 || (Transformer)
 +                                        |---------
 +
 +    This would now be a good time to install a fuse in your 120 VAC
 +    line. Now before plugging this into the wall, tie two of the
 +    scondary leads (one from EACH transformer) together.
 +
 +    Something like this: A--Xfmr--B+C--Xfmr--D
 +
 +    Plug in your 120V side. Now using a VOM meter, measure the voltage
 +    between A and D.
 +      If the meter reads 18 volts, then:
 +        1. unplug from the 120.
 +        2. tie A and C together. tie B and D together.
 +        3. your 2 transformers will now give you 9 volts at 4 amps.
 +      If the meter reads 0 volts, then:
 +        1. unplug from the 120.
 +        2. tie A and D together. Tie B and C together.
 +        3. your 2 transformers will now give you 9 volts at 4 amps.
 + 
 +  Below is the file corresponding to the full schematic of the power supply.
 +  [Ed's note: in GeoPaint format, converted by convert 2.5, then uuencoded]. As
 +  you can see in the picture, I used only one transformer. Because it got hot,
 +  I epoxied a small heat sink to it. While this solved the heat problem, it did
 +  not increase the capacity of the total power supply.
 +
 +  Note that I used fuses on all lines.
 +
 +-----------------------------------------------------------------------------
 +begin 700 schematic.
 +M@PD,<V-H96UA=&ECH*"@H*"@H`D&`0=8!P8-,Q(`4%)'(&9O<FUA='1E9"!'
 +M14]3(&9I;&4@5C$N,``CF````"```.``0X(``$5P<V]N($U8+3@P`*"@H*``
 +MUH>-UH?(F!AE`H4"D`+F`V"@H*"@H*"@H`@%`0A6!`<,`"````"""@A30TA%
 +M34%424.@H*"@H*"@````````````$P!"3$%35$52)U,@0T].5D525$52(%8R
 +M+C4$6`<&#2X"````@RP#0T].5D525*"@H*"@H*"@H"P2``99`Q$&`10`````
 +M```````````````````````````````````````#%;_____```.@``6?__F5
 +M55F:JJF555F:JJF555F:JJF555F:JJF?__F@``7```/___\```````-__[:`
 +M`/Y__[R#!P$``/__``!086EN="!);6%G92!6,2XQ````````````````````
 +M````````````9V5O4&%I;G0@("`@5C(N,``````@*$')!M`"J1*-(D"I`(TG
 +M0"#]/Y`%J0"-)T`@#!\@2$&I`(VL7ZD`A1&I!X40J3^%%ZGQA1:I7X4-J:R%
 +M#*!`J0X@3S&B_Z4"R0+P)LG_T`8@-$&X4*G)!M`-H$.I)B!/,2"APKA0F*VL
 +M7_`&(+T\(($\8%!A:0#_"@#_`3P!>`)+`EL"/P&R`38!B@%Z`38!C@(G`/\`
 +M_P#_`/\`_P#_`/\`_P#_`/\`_P#_`/\`_P#_`/\`_P#_`/\`_P#_`/\`_P#_
 +M`/\`_P#_`/\`_P#_`/\`````````````````````````````````````````
 +M````````````````````````````````````````````````````````````
 +M````````````````````````````````````````````````````````````
 +M`````````````````````````````````````````````````````````/\`
 +M_P#_`/\`_P#_`/\`E0`"/R!%````````_P"%``,0_Q!$````````_P"&``+X
 +M"/\`_P"B`/^_H;\``"`@,!@,_PP810``````_P``"!`0,&#@_\#`0P``````
 +M_P``A1`#\!`0H`"("/\`_P#*``(@/X8``>#_`+```3"'(*@``F`PAA"8`(@0
 +MH`"("/\`_P"B`/^_H;\`(*@`B!"8`(00!!\0$!!$`````/\```"$"`3X"`@(
 +M_P#_`,(``O__A@`"X."&((L`#0$!`0($@("`#A$0$!.%``-98D.%``.,4MZ%
 +M``/@D)"0``$'0P````````#_AP`!@,\`B""H_P#.``H#1$0HA0`##03$A0`#
 +M@("9AP`!!*``B""H`(@0H`"("/\`_P#_`.,`'2D1$1$0````).0$),0```"E
 +MI*2DF`````2HJ%!0HP"&(`)@8*(``1^$``P!$!#_````/-,``/B$``&`F`"(
 +M"/\`_P"B`/^_H;\``+```3"'(*@``F`PAA"8`(@0H`"("/\`_P"B`/^_H;\`
 +M(*@`B!"8`(00!!\0$!!$`````/\```"$"`3X"`@(_P#_`,(``O__A@`"X."&
 +M((L`#0$!`0($@("`#A$0$!.%``-98D.%``.,4MZ%``/@D)"0``$'0P``````
 +M``#_AP`!@,\`B""HS@`""`B&``(*BH8``@D!O@`"#PA#````````_P"&``R`
 +M8``/##`P#`PP`/^%``,\`/^%``/``/^%``$$1`#_`````````P#_`84`"L#_
 +M@,#@8"`@`/^'``'PAA"0``(&"(8`A!`,$A(2$V`0```\!(3.A``$<8J*BH0`
 +M#,`@("<````X*"!P((<(`0^'``&`_P#_`,<``S\@((8``N`<B``1!`4%`@("
 +M``"34E(B(B(``)N$20E(``"8)#P@))BR`(@(D0`Q`0$```$!`&"%A65EA85E
 +M,`P,,#`,##`B(CPB(B(\`$!,4DY24DX`!&25AH:59```@(0``8"A`(@@A0`+
 +M`0(*!!`0.$2"`0"`A@`"@$"0`(@0`H2$A@`"BG&&``(JRH8`#*"@`0$#`P4%
 +M,,```(0%"&`8!`3R"@D1_P#_`)H`_[^AOP``````````````````````````
 +M````````````````````````````````````````````````````````````
 +M````````````````````````````````````````````````````````````
 +M````````````````````````````````````````````````````````````
 +M``````````````````````````````````"<``(!`T(```````#__P(``(0@
 +M!N#@("`#`88`"/^`0"`0"`0$2O\``````````?B8`!<!`0```0$`986%966%
 +MA64P#`\P,`P,,$8``/\```````,``/Z%`H@``3^'`!3X"P0"`0```(#G@(`!
 +M@D0X(+]`@$(``````/\``(0``A#_AA!#`/\````````8!?P$`@(!`0"-B8G9
 +M<7$`P.$A(2(2#`08_P#_`*T``@$#0@```````/__"P```"`@(.#@("`@B``$
 +M`@$!`8@`'("`@$```'E$1'A$1```@("8I9VE```(",DJ#`S)`#<!`0```0$`
 +M986%966%A64P#`PP,`P,,`!$"D0H*1$1$0`-!,0DY`0D`("`F:6DI*0````$
 +M!*BH4)```0-#`````````/^'``'PAQ"8`(@0CP`!#(<``0B(``(X#X8(`F"`
 +M_P#_`*``_[^AOP``````````````````````````````````````````````
 +M````````````````````````````````````````````````````````````
 +M````````````````````````````````````````````````````````````
 +M`````````````````````````````````````````````````````+``B""0
 +M`(5`%7]`0$1X````_P``I9P```#_```JR4(```#_``````<```#_```'A`0;
 +M_`0$_P`],3TQ,`#_`+.VL['G`/\`O#`\L#P`A8`#_X"`0@``````_P``-0`!
 +M`0``_P``986%8&"````P#`PP/P```!````#_````Q````/\```"8````_P``
 +M`%````#_AP`$X"`@(*@`B!"8`(00!!\0$!"$`!3_`````@$!`/\``0$("`B(
 +MCX@("(0`!/\```"$"`3X"`@(_P#_`,(``O__A@`"X."&((L`#0$!`0($@("`
 +M#A$0$!.%``-98D.%``.,4MZ%``/@D)"0``$'0P````````H`_X<``8#/`(@@
 +MJ`"($)@`B!"(``L"#```GJ&AH0@("(D`!&"PD("("/\`_P"B`/^_H;\```$,
 +MAP`!"(@``C@/A@@"8(#_`/\`H`#_OZ&_````````````````````````````
 +M````````````````````````````````````````````````````````````
 +M````````````````````````````````````````````````````````````
 +M````````````````````````````````````````````````````````````
 +M````````````L`"(((4`)@$"'`0($"!`_P``$1$.``#_``!"0D$``/\``!!2
 +MC```_P``D)"04```_P``````#0``_P``("`P&`S_#!A%``````#_```($!`P
 +M8.#_P,!#``````#_``"%$`/P$!"(``2AH:&>A``)`2(B(AP```#`A(`#````
 +MB`C_`/\`R@`"(#^&``'@_P"P``,P(`"%(*@``F`PAA"8`(@0H`"("/\`_P"B
 +M`/^_H;\`_P`!`0@("(B/B`@(A``$_P```(0(!/@("`C_`/\`P@`"__^&``+@
 +MX(8@BP`-`0$!`@2`@(`.$1`0$X4``UEB0X4``XQ2WH4``^"0D)#_`.D`B""H
 +M`(@0F``*B!"@`(@(_P#_`/\`_P"$`(@@J`"($)@`B!"@`(@(_P#_`*(`_[^A
 +MOP``#0``_P``("`P&`S_#!A%``````#_```($!`P8.#_P,!#``````#_``"%
 +M$`/P$!"(``2AH:&>A``)`2(B(AP```#`A(`#````B`C_`/\`R@`"(#^&``'@
 +M_P"P``,P(`"%(*@``F`PAA"8`(@0H`"("/\`_P"B`/^_H;\`_P`!`0@("(B/
 +MB`@(A``$_P```(0(!/@("`C_`/\`P@`"__^&``+@X(8@BP`-`0$!`@2`@(`.
 +M$1`0$X4``UEB0X4``XQ2WH4``^"0D)#_`.D`B""H`(@0F`"($*``B`C_`/\`
 +M_P#S``$#AP(8_P``>V-[8V'_``!G;&9CSO\``'A@>&!XB("8`(@0B`"(`1G_
 +M```],3TQ,/\``+.VL['G_P``O#`\L#S`AT")``,?$!"$$Q@(_P``VQO;&P#_
 +M```[8S,;`/P$!,0$Q`3_`/\`D@#_OZ&_`(8``>#_`+```S`@`(4@J``"8#"&
 +M$)@`B!"@`(@(_P#_`*(`_[^AOP#_``$!"`@(B(^("`B$``3_````A`@$^`@(
 +M"/\`_P#"``+__X8``N#@AB"+``T!`0$"!("`@`X1$!`3A0`#66)#A0`#C%+>
 +MA0`#X)"0D/\`V0`#`@(#0@``````"@``_X40`P``_X4``X"`@)T`B!"(``,!
 +M`0%"`````````/^%"`,``/^%``-`0,"-``03$!`?A``$#@``_X0$!',``/^$
 +M``3$!`3\_P#_`/\`]P"($*@`B!"8`(@(H`"(!/\`_P"B`/^_H;\`!,0$Q`3_
 +M`/\`D@#_OZ&_`(8``>#_`+```S`@`(4@J``"8#"&$)@`B!"@`(@(_P#_`*(`
 +M_[^AOP#_``$!"`@(B(^("`B$``3_````A`@$^`@("/\`_P#"``+__X8``N#@
 +MAB"+``T!`0$"!("`@`X1$!`3A0`#66)#A0`#C%+>A0`#X)"0D/\`Z0"($*@`
 +MB!"8`(@(H`"(!/\`_P#_`/\`A`"($*@`B!"8`(@(H`"(!/\`_P"B`/^_H;\`
 +M_X4``T!`P(T`!!,0$!^$``0.``#_A`0$<P``_X0`!,0$!/S_`/\`_P#W`(@0
 +MJ`"($)@`B`B@`(@$_P#_`*(`_[^AOP`$Q`3$!/\`_P"2`/^_H;\`A@`!X/\`
 +ML``#,"``A2"H``)@,(80F`"($*``B`C_`/\`H@#_OZ&_`/\``0$("`B(CX@(
 +M"(0`!/\```"$"`3X"`@(_P#_`,(``O__A@`"X."&((L`#0$!`0($@("`#A$0
 +M$!.%``-98D.%``.,4MZ%``/@D)"0_P#I`(@0J`"($)@`B`B@``J(!/\`_P#_
 +M`/@``P,$!(4``X%!0(00!``1$:*%``,.$9"4``0'"`@(A``,`H*!@1`0$``B
 +M(D5%A``$'"(@((<`"P,````?$!`>P0@(B0`-<HN#@IH````N*2BHJ(4`$X"`
 +M@`````$!!P$!`!\0$![!`1'_`/\`H@#_OZ&_`/\`L``#,"``A2"H``)@,(80
 +MF`"($*``B`C_`/\`H@#_OZ&_`/\``0$("`B(CX@("(0`!/\```"$"`3X"`@(
 +M_P#_`,(``O__A@`"X."&((L`#0$!`0($@("`#A$0$!.%``-98D.%``.,4MZ%
 +M``/@D)"0_P#9``P$`P```P```$#`0("$``VBI$=$1````)!0T%%.DP`*!P`!
 +M!@````^!@(4`$1!(CXB(`````Z"@HIP```#@A@`C`P(!$0X```#$(```("!`
 +M````BHIR````0<)H:2X```#!(("%``+P((0`#`<$!`0.````B$!;2H0`!`$!
 +M@4&$``3P``#@_P#_`/\`XP`3!P0$!`<$!`2(0%M*B@H*"@``@81!"4!@@`#@
 +M$!`0X)``"P@("`\("`@`@+>4A!0#````A8`:`"`@0$"`@(```@(#`@("```M
 +M)<4%!04``,"%(!X``$!`0$%"2P@0($"```'D!`A`X!`0$.````<$!`2$``2*
 +>"@H*A``$0$`*04"$``00$!#@_P#_`)8`_[^AOP`*
 +`
 +end
 +
 +=============================================================================
 +</code>
 +===== LZW Compression =====
 +<code>
 +by Bill Lucier (Blucier@ersys.edmonton.ab.ca, b.lucier1 on Genie)
 +
 +LZW is perhaps the most widely used form of data compression today. It
 +is simple to implement and achieves very decent compression at a fairly
 +quick pace. LZW is used in PKZIP (shrink),PKARC (crunch), gifs,V.42bis
 +and unix's compress. This article will attempt to explain how the
 +compression works with a short example and 6502 source code in Buddy
 +format.
 +
 +Originally named lz78, it was invented by Jacob Ziv and Abraham Lempel
 +in 1978 , it was later modified by Terry Welch to its present format.
 +The patent for the LZW compression method is presently held by Unisys.
 +
 +LZW compresses data by taking phrases and compressing them into codes.
 +The size of the codes could vary from 9 bits to 16 bits. Although for
 +this implementation we will be using only 12 bits. As byte are read in
 +from a file they are added to a dictionary. For a 12-bit implementation
 +a dictionary will be 4k (2^12=4096) . Each entry in the dictionary
 +requires five bytes, which will be built in the form of a tree. It is
 +not a binary tree because each node may have more than two offsprings.
 +In fact, because our dictionary can hold up to 4096 different codes it
 +is possible to have one node with 3800 children nodes, although this is
 +not likely to happen. The five bytes that make up our tree will be:
 +
 +The parent code: Each node has one and only one parent node. When the parent
 +                 code is less then 255 it is the end of a phrase. The codes
 +                 0-255 do not actually exist in the tree. The following
 +                 values do not appear either as they have special meaning:
 +
 +                 256 : End of Stream-This marks the end of one compressed file
 +                 257 : This tells the decompressor to increase the number
 +                       of bits its reading by one.
 +                 258 : Wipe out dictionary
 +                 
 +The code value : This is the code that will be sent to the compressor. 
 +The character  : The value contained at this node. It have a value of 0-255.
 +
 +Initially we send out codes that are 9 bits long, this will cover the values
 +0-511. Once we have reached 511, we will need to increase the number of
 +bits to write by 1. This will give room for code numbers 512-1023, or
 +(2^10)-1. At this point we must ensure that the decompressor knows how
 +bits to read in at once so a code number 257 is sent to indicate that
 +the number of bits to be read is to be bumped up by one. The size of the
 +dictionary is finite so at some point we do have to be concerned with
 +what we will do when it does fill up. We could stop compiling new
 +phrases and just compress with the ones that are already in the
 +dictionary. This is not a very good choice, files tend to change
 +frequently (eg. program files as they change from code to data) so
 +sticking with the same dictionary will actually increase the size of the
 +file or at best, give poor compression. Another choice is to wipe the
 +dictionary out and start building new codes and phrases, or wipe out
 +some of the dictionary leaving behind only the newer codes and phrases.
 +For the sake of simplicity this program will just wipe out the
 +dictionary when it becomes full.
 +
 +To illustrate how LZW works a small phrase will be compressed : heher.
 +To start the first two characters would be read in. The H would be
 +treated as the parent code and E becomes the character code. By means of
 +a hashing routine (the hashing routine will be explained more fully in
 +the source code) the location where HE should be is located. Since we
 +have just begun there will be nothing there,so the phrase will be added
 +to the dictionary. The codes 0-258 are already taken so we start using
 +259 as our first code. The binary tree would look something like this:
 +            
 +          node # 72 - H
 +                 |
 +     node #3200 259 - E
 +
 + The node # for E is an arbitrary one. The compressor may not choose
 +that location, 3200 is used strictly for demonstration purposes. So at
 +node #3200 the values would be:
 +
 +Parent code - 72
 +code value  - 259
 +character   - E
 +
 +The node #72 is not actually used. As soon as a value less than 255 is
 +found it is assumed to be the actual value. We can't compress this yet
 +so the value 72 is sent to the output file(remember that it is sent in 9
 +bits). The E then becomes the parent code and a new character code ( H )
 +is read in. After again searching the dictionary the phrase EH is not
 +found. It is added to the dictionary as code number 260. Then we send
 +the E to the disk and H becomes the new parent code and the next E
 +becomes the new character code. After searching the dictionary we find
 +that we can compress HE into the code 259,we want to compress as much as
 +possible into one code so we make 259 the parent code. There may be a
 +longer string then HE that can be compressed. The R is read in as the
 +new character code. The dictionary is searched for the a 259 followed a
 +R, since it is not found it is added to the dictioary and it looks like
 +this:
 +
 +           node #72 - H             node #69 - E
 +                                        | 
 +    node #3200  259 - E    node #1600    260 - H
 +                 |
 +    node #1262  261 - R
 +
 +Then the value 259 is sent to the output file (to represent the HE) and
 +since that is the EOF the R is sent as well,as well as a 256 to indicate
 +the EOF has been reached.
 +
 +Decompression is extremely simple. As long as the decompressor maintains
 +the dictionary as the compressor did, there will be no problems,except
 +for one problem that can be handled as an exceptional case. All of the
 +little details of increasing the number of bits to read, and when to
 +flush the dictionary are taken care of by the compressor. So if the
 +dictionary was increased to 8k, the compressor would have to be set up
 +to handle a larger dictionary, but the decompressor only does as the
 +compressed file tells it to and will work with any size dictionary. The
 +only problem would be that a larger dictionary will creep into the ram
 +under the rom or possibly even use all available memory, but assuming
 +that the ram is available the decompressor will not change. The
 +decompressor would start out reading 9 bits at a time, and starts it
 +free code at 259 as the compressor did. To use the above input from the
 +compressor as an example, the output was:
 +
 +72  - For the First H
 +69  - For the First E
 +259 - For the Compressed HE
 +82  - For the R
 +256 - Eof indicator
 +
 + To begin decompressing, two values are needed. The H and E are read in,
 +(note they will both be 9 bits long). As they are both below 256 they
 +are at the end of the string and are sent straight to the output file.
 +The first free code is 259 so that is the value assigned to the phrase
 +HE. Note when decompressing there is no need for the hashing routine,
 +the codes are the absolute locations in the dictionary (i.e. If the
 +dictionary was considered to be an array then the entry number 259 would
 +be dictionary[259]), because of this, the code value is no longer
 +needed. So the decompressor would have an entry that looks like this:
 +
 +Node # 259
 +Parent Code - H
 +Character   - E
 +
 +The decompressor will read in the next value (259). Because the node
 +number is at the end of the compressed string we will have to take the
 +code value and place it on a stack, and take them off in a
 +Last-in,First-out (LIFO) fashion. That is to say that the first
 +character to go on the stack (in this case the E) will be the last to
 +come off. The size of the stack is dependent on the size of the
 +dictionary, so for this implementation we need a stack that is 4k long.
 +After all the characters from the string have been placed on the stack
 +they are taken off and sent to the outputfile.
 + 
 +  There is one small error that is possible with LZW because of the way
 +the compressor defines strings. Consider the compression dictionary that
 +has the following in it:
 +
 +     node #   Code         Parent  character 
 +              Value         code 
 +     ------   ------        ------  ---------
 +        65      65           n/      
 +       723     259            65       C
 +      1262     260           259       U
 +      2104     261           260       T
 +      2506     262           261       E
 +
 +Now if the compressor was to try to compress the string ACUTEACUTEA The
 +compressor will find a match for the first five characters 'ACUTE' and
 +will send a 262 to the output file. Then it will add the following entry
 +to the dictionary:
 +
 +      3099     263           262       A
 +
 +Now it will try to compress the remaining characters, and it finds that
 +it can compress the entire string with the code 263, but notice that the
 +middle A, the one that was just added onto the end of the string 'ACUTE'
 +was never sent to the output file. The decompressor will not have the
 +code 263 defined in it's dictionary. The last code it will have defined
 +will be 262. This problem is easily remedied though, when the
 +decompressor does not have a code defined, it takes the first letter
 +from the last phrase defined and tacks it onto the end of the last
 +phrase. IE It takes the first letter (the A) from the phrase and adds it
 +on to the end as code #263.
 +
 +This particular implementation is fairly slow because it reads a byte
 +and then writes one, it could be made much faster with some buffering.
 +It is also limited to compressing and decompressing one file at a time
 +and has no error checking capabilities. It is meant strictly to teach
 +LZW compression, not provide a full fledged compressor.
 +
 +And now for the code:
 +  
 +SYS 4000      ; sys 999 on a 64
 +.DVO 9        ; or whatever drive used for output
 +.ORG 2500
 +.OBJ "LZW.ML"
 +
 +TABLESIZE =5021     
 +
 +; THE TABLESIZE IS ACTUALLY 5021, ABOUT 20% LARGER THEN 4K. THIS GIVES
 +; THE HASHING ROUTINE SOME ROOM TO MOVE. IF THE TABLE WAS EXACTLY 4K
 +; THERE WOULD BE FREQUENT COLLISIONS WHERE DIFFERENT COMBINATIONS OF
 +; CHARACTERS WOULD HAVE THE SAME HASH ADDRESS. INCREASING THE TABLE SIZE
 +; REDUCES THE NUMBER OF COLLISIONS.
 +
 +EOS =256          ; eos = End of stream This marks the end of file
 +
 +FIRSTCODE =259
 +MAXCODE =4096
 +
 +BUMPCODE =257     ; Whenever a 257 is encountered by the decompressor it
 +                  ; increases the number of bits it reads by 1
 +
 +FLUSHCODE =258   
 +
 +TABLEBASE =14336  ; The location that the dictionary is located at
 +
 +DECODESTACK =9300 ; The location of the 4k LIFO stack
 +
 +; ORG = DECOMPRESS FILE
 +; ORG + 3 = COMPRESS FILE
 +
 +JMP EXPANDFILE
 +
 +;********************************
 +; COMPRESSFILE
 +;********************************
 +
 +COMPRESSFILE JSR INITDIC ; EMPTY THE DICTIONARY
 +LDA #128
 +STA BITMASK
 +LDA #0
 +STA RACK
 +JSR GETCHAR      ; GET A CHAR FROM THE INPUT FILE
 +STA STRINGCODE   ; INITIALIZE THE STRINGCODE (PARENT CODE)
 +LDA #0
 +STA STRINGCODE+1
 +NEXTCHAR JSR GETCHAR
 +         STA CHARACTER
 +         JSR FINDNODE    ; FINDNODE CALCULATES THE HASHED LOCATION OF
 +         LDA ($FE),    ; THE STRINGCODE AND CHARACTER  IN THE DICT.
 +         INY             ; AND SETS $FE/$FF POINTING TO IT. IF THE ENTRY
 +         AND ($FE),    ; HAS TWO 255 IN IT THEN IT IS EMPTY AND SHOULD
 +         CMP #255        ; BE ADDED TO THE DICTIONARY.
 +         BEQ ADDTODICT
 +             LDA ($FE),    ; IT HAS A DEFINED PHRASE. STORE THE CODE VALUE IN
 +             STA STRINGCODE+1; THE PARENT CODE
 +             DEY
 +             LDA ($FE),Y
 +             STA STRINGCODE
 +         JMP EOF
 +         ADDTODICT LDY #0
 +         - LDA NEXTCODE,Y
 +           STA ($FE),Y
 +           INY
 +           CPY #5
 +         BNE -
 +         INC NEXTCODE             ; INCREASE THE NEXTCODE
 +         BNE +
 +             INC NEXTCODE+1
 +         + JSR OUTPUT
 +         LDA NEXTCODE+1          ; CHECK IF NEXTCODE=4096 IF SO THEN FLUSH THE
 +         CMP #>MAXCODE           ; DICTIONARY AND START ANEW
 +         BNE CHECKBUMP
 +         LDA NEXTCODE
 +         CMP #<MAXCODE
 +         BNE CHECKBUMP
 +         LDA #<FLUSHCODE        ; SEND THE FLUSH CODE TO THE COMPRESSED FILE SO
 +         STA STRINGCODE         ; THE DECOMPRESSOR WILL KNOW TO FLUSH THE
 +         LDA #>FLUSHCODE        ; DICTIONARY
 +         STA STRINGCODE+1
 +         JSR OUTPUT
 +         JSR INITDIC
 +         JMP CHECKEOF
 +         CHECKBUMP LDA NEXTBUMP+1
 +         CMP NEXTCODE+1         ; CHECKBUMP CHECK TO SEE IF THE NEXTCODE HAS
 +         BNE CHECKEOF        ; REACHED THE MAXIMUM VALUE FOR THE CURRENT
 +         LDA NEXTBUMP                    ; NUMBER OF BITS BEING OUTPUT.
 +         CMP NEXTCODE        ; FOR      BITS     NEXTCODE HAS Y PHRASES
 +         BNE CHECKEOF        ;        --------     -----------------------
 +         LDA #>BUMPCODE      ;                           511
 +         STA STRINGCODE+1    ;          10                1023
 +         LDA #<BUMPCODE      ;          11                2047
 +         STA STRINGCODE      ;          12                4095
 +         JSR OUTPUT
 +         INC CURRENTBITS
 +         ASL NEXTBUMP
 +         ROL NEXTBUMP+1
 +         CHECKEOF LDA #0
 +         STA STRINGCODE+1
 +         LDA CHARACTER
 +         STA STRINGCODE
 +         EOF LDA 144
 +         BNE DONE
 +JMP NEXTCHAR
 +DONE JSR OUTPUT
 +LDA #>EOS               ; SEND A 256 TO INDICATE EOF
 +STA STRINGCODE+1
 +LDA #<EOS
 +STA STRINGCODE
 +JSR OUTPUT
 +LDA BITMASK
 +BEQ +
 +    JSR $FFCC
 +    LDX #3
 +    JSR $FFC9
 +    LDA RACK              ; SEND WHAT BITS WEREN'T SEND WHEN OUTPUT
 +    JSR $FFD2
 ++ JSR $FFCC
 +LDA #3
 +JSR $FFC3
 +LDA #2
 +JMP $FFC3
 +
 +;**********************************
 +; INITDIC
 +; INITIALIZES THE DICTIONARY, SETS
 +; THE NUMBER OF BITS TO 9
 +;**********************************
 +
 +INITDIC LDA #9
 +STA CURRENTBITS
 +LDA #>FIRSTCODE
 +STA NEXTCODE+1
 +LDA #<FIRSTCODE
 +STA NEXTCODE
 +LDA #>512
 +STA NEXTBUMP+1
 +LDA #<512
 +STA NEXTBUMP
 +LDA #<TABLEBASE
 +STA $FE
 +LDA #>TABLEBASE
 +STA $FF
 +LDA #<TABLESIZE
 +STA $FC
 +LDA #>TABLESIZE
 +STA $FD
 +- LDY #0
 +  LDA #255      ; ALL THE CODE VALUES ARE INIT TO 255+256*255
 +  STA ($FE),  ; OR -1 IN TWO COMPLEMENT
 +  INY
 +  STA ($FE),Y
 +  CLC
 +  LDA #5        ; EACH ENTRY IN THE TABLE TAKES 5 BYTES
 +  ADC $FE
 +  STA $FE
 +  BCC +
 +      INC $FF
 +  + LDA $FC
 +  BNE +
 +      DEC $FD
 +  + DEC $FC
 +  LDA $FD
 +  ORA $FC
 +BNE -
 +RTS
 +
 +;************************************
 +; GETCHAR
 +;************************************
 +
 +GETCHAR JSR $FFCC
 +LDX #2
 +JSR $FFC6
 +JMP $FFCF
 +
 +;************************************
 +; OUTPUT
 +;************************************
 +
 +OUTPUT LDA #0      ; THE NUMBER OF BITS OUTPUT CAN BE OF A VARIABLE
 +STA MASK+1         ; LENGTH,SO THE BITS ARE ACCUMULATED TO A BYTE IS
 +LDA #1             ; FULL AND THEN IT IS SENT TO THE OUTPUT FILE
 +LDX CURRENTBITS
 +DEX
 +- ASL
 +  ROL MASK+1
 +  DEX
 +BNE -
 +STA MASK
 +MASKDONE LDA MASK
 +ORA MASK+1
 +BNE +
 +    RTS
 ++ LDA MASK
 +AND STRINGCODE
 +STA 3
 +LDA MASK+1
 +AND STRINGCODE+1
 +ORA 3
 +BEQ NOBITON
 +    LDA RACK
 +    ORA BITMASK
 +    STA RACK
 +NOBITON LSR BITMASK
 +LDA BITMASK
 +BNE +
 +    JSR $FFCC
 +    LDX #3
 +    JSR $FFC9
 +    LDA RACK
 +    JSR $FFD2
 +    LDA #0
 +    STA RACK
 +    LDA #128
 +    STA BITMASK
 ++ LSR MASK+1
 +ROR MASK
 +JMP MASKDONE
 +
 +;******************************
 +; FINDNODE
 +; THIS SEARCHES THE DICTIONARY TILL IT FINDS A PARENT NODE THAT MATCHES
 +; THE STRINGCODE AND A CHILD NODE THAT MATCHES THE CHARACTER OR A EMPTY
 +; NODE.
 +;*******************************
 +
 +; THE HASHING FUNCTION - THE HASHING FUNCTION IS NEEDED BECAUSE
 +; THERE ARE 4096 X 4096 (16 MILLION) DIFFERENT COMBINATIONS OF
 +; CHARACTER AND STRINGCODE. BY MULTIPLYING THE CHARACTER AND STRINGCODE
 +; IN A FORMULA WE CAN DEVELOP OF METHOD OF FINDING THEM IN THE
 +; DICTIONARY. IF THE STRINGCODE AND CHARACTER IN THE DICTIONARY
 +; DON'T MATCH THE ONES WE ARE LOOKING FOR WE CALCULATE AN OFFSET
 +; AND SEARCH THE DICTIONARY FOR THE RIGHT MATCH OR A EMPTY
 +; SPACE IS FOUND. IF AN EMPTY SPACE IS FOUND THEN THAT CHARACTER AND
 +; STRINGCODE COMBINATION IS NOT IN THE DICTIONARY
 +
 +FINDNODE LDA #0
 +STA INDEX+1
 +LDA CHARACTER     ; HERE THE HASHING FUNCTION IS APPLIED TO THE
 +ASL               ; CHARACTER AND THE STRING CODE. FOR THOSE WHO
 +ROL INDEX+1       ; CARE THE HASHING FORMULA IS:
 +EOR STRINGCODE    ; (CHARACTER << 1) ^ STRINGCODE
 +STA INDEX         ; FIND NODE WILL LOOP TILL IT FINDS A NODE
 +LDA INDEX+1       ; THAT HAS A EMPTY NODE OR MATCHES THE CURRENT
 +EOR STRINGCODE+1  ; PARENT CODE AND CHARACTER
 +STA INDEX+1
 +ORA INDEX 
 +BNE +
 +    LDX #1
 +    STX OFFSET
 +    DEX
 +    STX OFFSET+1
 +    JMP FOREVELOOP
 ++ SEC
 +LDA #<TABLESIZE
 +SBC INDEX
 +STA OFFSET
 +LDA #>TABLESIZE
 +SBC INDEX+1
 +STA OFFSET+1
 +
 +FOREVELOOP JSR CALCULATE     
 +           LDY #0
 +           LDA ($FE),Y
 +           INY
 +           AND ($FE),Y
 +           CMP #255
 +           BNE +
 +               LDY #0
 +               RTS
 +           + INY
 +           - LDA ($FE),Y
 +             CMP STRINGCODE-2,Y
 +             BNE +
 +             INY
 +             CPY #5
 +             BNE -
 +          LDY #0
 +          RTS
 +          + SEC
 +          LDA INDEX
 +          SBC OFFSET
 +          STA INDEX
 +          LDA INDEX+1
 +          SBC OFFSET+1
 +          STA INDEX+1
 +          AND #128
 +          BEQ FOREVELOOP
 +          CLC
 +          LDA #<TABLESIZE
 +          ADC INDEX
 +          STA INDEX
 +          LDA #>TABLESIZE
 +          ADC INDEX+1
 +          STA INDEX+1
 +JMP FOREVELOOP
 +
 +;***************************
 +; CALCULATE
 +; TAKES THE VALUE IN INDEX AND CALCULATES ITS LOCATION IN THE DICTIONARY
 +;****************************
 +
 +CALCULATE LDA INDEX
 +STA $FE
 +LDA INDEX+1
 +STA $FF
 +ASL $FE
 +ROL $FF
 +ASL $FE
 +ROL $FF
 +CLC
 +LDA INDEX
 +ADC $FE
 +STA $FE
 +LDA INDEX+1
 +ADC $FF
 +STA $FF
 +CLC
 +LDA #<TABLEBASE
 +ADC $FE
 +STA $FE
 +LDA #>TABLEBASE
 +ADC $FF
 +STA $FF
 +LDY #0
 +RTS
 +
 +;******************************
 +; DECODESTRING
 +;******************************
 +
 +DECODESTRING TYA   ; DECODESTRING PUTS THE STRING ON THE STACK
 +STA COUNT          ; IN A LIFO FASHION.
 +LDX #>DECODESTACK
 +CLC
 +ADC #<DECODESTACK
 +STA $FC
 +STX $FD
 +LDA #0
 +STA COUNT+1
 +- LDA INDEX+1
 +  BEQ +
 +  JSR CALCULATE
 +  LDY #4
 +  LDA ($FE),Y
 +  LDY #0
 +  STA ($FC),Y
 +  LDY #2
 +  LDA ($FE),Y
 +  STA INDEX
 +  INY
 +  LDA ($FE),Y
 +  STA INDEX+1
 +  JSR INFC
 +JMP -
 ++ LDY #0
 +LDA INDEX
 +STA ($FC),Y
 +INC COUNT
 +BNE +
 +    INC COUNT+1
 ++ RTS
 +
 +;******************************
 +; INPUT
 +;******************************
 +
 +INPUT LDA #0  ; THE INPUT ROUTINES IS USED BY THE DECOMPRESSOR
 +STA MASK+1    ; TO READ IN THE VARIABLE LENGTH CODES
 +STA RETURN
 +STA RETURN+1
 +LDA #1
 +LDX CURRENTBITS
 +DEX
 +- ASL
 +  ROL MASK+1
 +  DEX
 +BNE -
 +STA MASK
 +- LDA MASK
 +  ORA MASK+1
 +  BEQ INPUTDONE
 +  LDA BITMASK
 +  BPL +
 +      JSR GETCHAR
 +      STA RACK
 +  + LDA RACK
 +  AND BITMASK
 +  BEQ +
 +      LDA MASK
 +      ORA RETURN
 +      STA RETURN
 +      LDA MASK+1
 +      ORA RETURN+1
 +      STA RETURN+1
 +  + LSR MASK+1
 +  ROR MASK
 +  LSR BITMASK
 +  LDA BITMASK
 +  BNE +
 +      LDA #128
 +      STA BITMASK
 ++ JMP -
 +INPUTDONE RTS
 +
 +;*******************************
 +; EXPANDFILE
 +; WHERE THE DECOMPRESSION IS DONE
 +;*******************************
 +
 +EXPANDFILE LDA #0
 +STA RACK
 +LDA #128
 +STA BITMASK
 +START JSR INITDIC
 +JSR INPUT
 +LDA RETURN+1
 +STA OLDCODE+1       ; Save the first character in OLDCODE
 +LDA RETURN
 +STA CHARACTER
 +STA OLDCODE
 +CMP #<EOS
 +BNE +
 +    LDA RETURN+1    ; If return = EOS (256) then all done 
 +    CMP #>EOS
 +    BNE +
 +    JMP CLOSE
 ++ JSR $FFCC
 +LDX #3
 +JSR $FFC9
 +LDA OLDCODE         ; Send oldcode to the output file
 +JSR $FFD2
 +NEXT JSR INPUT
 +     LDA RETURN
 +     STA NEWCODE
 +     LDA RETURN+1
 +     STA NEWCODE+1
 +     CMP #1         ; All of the special codes Flushcode,BumpCode & EOS
 +     BNE ++         ; Have 1 for a MSB.
 +         LDA NEWCODE
 +         CMP #<BUMPCODE
 +         BNE +
 +             INC CURRENTBITS
 +             JMP NEXT
 +         + CMP #<FLUSHCODE
 +         BEQ START
 +         CMP #<EOS
 +         BNE +
 +         JMP CLOSE
 +     + SEC          ; Here we compare the newcode just read in to the
 +     LDA NEWCODE    ; next code. If newcode is greater than it is a 
 +     SBC NEXTCODE   ; ACUTEACUTEA situation and must be handle differently.
 +     STA 3
 +     LDA NEWCODE+1 
 +     SBC NEXTCODE+1
 +     ORA 3
 +     BCC +
 +         LDA CHARACTER
 +         STA DECODESTACK
 +         LDA OLDCODE
 +         STA INDEX
 +         LDA OLDCODE+1
 +         STA INDEX+1
 +         LDY #1
 +         BNE ++
 +     + LDA NEWCODE  ; Point index to newcode spot in the dictionary   
 +     STA INDEX      ; So DECODESTRING has a place to start
 +     LDA NEWCODE+1
 +     STA INDEX+1
 +     LDY #0
 +     + JSR DECODESTRING
 +     LDY #0
 +     LDA ($FC),Y
 +     STA CHARACTER
 +     INC $FC
 +     BNE +
 +         INC $FD
 +     + JSR $FFCC
 +     LDX #3
 +     JSR $FFC9
 +     L1 LDA COUNT+1  ; Count contains the number of characters on the stack
 +        ORA COUNT
 +        BEQ +       
 +        JSR DECFC
 +        LDY #0
 +        LDA ($FC),Y
 +        JSR $FFD2
 +     JMP L1
 +     + LDA NEXTCODE  ; Calculate the spot in the dictionary for the string
 +     STA INDEX       ; that was just entered.
 +     LDA NEXTCODE+1
 +     STA INDEX+1
 +     JSR CALCULATE
 +     LDY #2          ; The last character read in is tacked onto the end
 +     LDA OLDCODE     ; of the string that was just taken off the stack
 +     STA ($FE),    ; The nextcode is then incremented to prepare for the 
 +     INY             ; next entry.
 +     LDA OLDCODE+1
 +     STA ($FE),Y
 +     INY
 +     LDA CHARACTER
 +     STA ($FE),Y
 +     INC NEXTCODE
 +     BNE +
 +         INC NEXTCODE+1
 +     + LDA NEWCODE
 +     STA OLDCODE
 +     LDA NEWCODE+1
 +     STA OLDCODE+1
 +JMP NEXT
 +CLOSE JSR $FFCC
 +LDA #2
 +JSR $FFC3
 +LDA #3
 +JMP $FFC3
 +
 +DECFC LDA $FC
 +BNE +
 +    DEC $FD
 ++ DEC $FC
 +LDA COUNT
 +BNE +
 +    DEC COUNT+1
 ++ DEC COUNT
 +RTS
 +INFC INC $FC
 +BNE +
 +    INC $FD
 ++ INC COUNT
 +BNE +
 +    INC COUNT+1
 ++ RTS
 +
 +NEXTCODE .WOR 0
 +STRINGCODE .WOR 0
 +CHARACTER .BYT 0
 +NEXTBUMP .WOR 0
 +CURRENTBITS .BYT 0
 +RACK .BYT 0
 +BITMASK .BYT 0
 +MASK .WOR 0
 +INDEX .WOR 0
 +OFFSET .WOR 0
 +RETURN .WOR 0
 +COUNT .WOR 0
 +NEWCODE .WOR 0
 +OLDCODE .WOR 0
 +TEST .BYT 0
 +
 +TO DRIVE THE ML I WROTE THIS SMALL BASIC PROGRAM. NOTE THAT CHANNEL TWO IS
 +ALWAYS THE INPUT AND CHANNEL THREE IS ALWAYS THE OUTPUT. EX AND CO MAY BE
 +CHANGED TO SUIT WHATEVER LOCATIONS THE PROGRAM IS REASSEMBLED AT.
 +
 +1 IFA=.THENA=1:LOAD"LZW.ML",PEEK(186),1
 +10 EX=2500:CO=2503
 +15 PRINT"[E]XPAND OR [C]OMPRESS?"
 +20 GETA$:IFA$<>"C"ANDA$<>"E"THEN20
 +30 INPUT"NAME OF INPUT FILE";FI$:IFLEN(FI$)=.THEN30
 +40 INPUT"NAME OF OUTPUT FILE";FO$:IFLEN(FO$)=.THEN40
 +50 OPEN2,9,2,FI$+",P,R":OPEN3,9,3,FO$+",P,W"
 +60 IFA$="E"THENSYSEX
 +70 IFA$="C"THENSYSCO
 +80 END
 +
 +For those interested in learning more about data
 +compression/decompression I recommend the book 'The Data Compression
 +Book' written by Mark Nelson. I learned a great deal from reading this
 +book. It explains all of the major data compression methods. (huffman
 +coding, dictionary type compression such as LZW, arithmatic coding,
 +speech compression and lossy graphics compression)
 +
 +Questions or comments are welcome, they may be directed to me at :
 +
 +Internet   : Blucier@ersys.edmonton.ab.ca
 +Genie      : b.lucier1
 +
 +------------------------------------------------------------------------------
 +
 +begin 644 lzw.ml
 +MQ`E,N`P@GPJI@(WT#:D`C?,-(.L*C>T-J0"-[@T@ZPJ-[PT@6`NQ_L@Q_LG_
 +M\`ZQ_HWN#8BQ_HWM#4QH"J``N>L-D?[(P`70]N[K#=`#[NP-(/8*K>P-R1#0
 +M&JWK#<D`T!.I`HWM#:D!C>X-(/8*()\*3%T*K?$-S>P-T!ZM\`W-ZPW0%JD!
 +MC>X-J0&-[0T@]@KN\@T.\`TN\0VI`(WN#:WO#8WM#:60T`-,WPD@]@JI`8WN
 +M#:D`C>T-(/8*K?0-\`X@S/^B`R#)_ZWS#2#2_R#,_ZD#(,/_J0),P_^I"8WR
 +M#:D!C>P-J0.-ZPVI`HWQ#:D`C?`-J0"%_JDXA?^IG87\J1.%_:``J?^1_LB1
 +M_ABI!67^A?Z0`N;_I?S0`L;]QORE_07\T-Y@(,S_H@(@QO],S_^I`(WV#:D!
 +MKO(-R@HN]@W*T/F-]0VM]0T-]@W0`6"M]0TM[0V%`ZWV#2WN#04#\`FM\PT-
 +M]`V-\PU.]`VM]`W0&"#,_Z(#(,G_K?,-(-+_J0"-\PVI@(WT#4[V#6[U#4P+
 +M"ZD`C?@-K>\-"B[X#4WM#8WW#:WX#4WN#8WX#=`1K?<-T`RB`8[Y#<J.^@U,
 +MEPLXJ9WM]PV-^0VI$^WX#8WZ#2#C"Z``L?[(,?[)_]`#H`!@R+'^V>L-T`C(
 +MP`70]*``8#BM]PWM^0V-]PVM^`WM^@V-^`TI@/`1&*F=;?<-C?<-J1-M^`V-
 +M^`U,EPNM]PV%_JWX#87_!OXF_P;^)O\8K?<-9?Z%_JWX#67_A?\8J0!E_H7^
 +MJ3AE_X7_H`!@F(W]#:(D&&E4A?R&_:D`C?X-K?@-\!X@XPN@!+'^H`"1_*`"
 +ML?Z-]PW(L?Z-^`T@W`U,)@R@`*WW#9'\[OT-T`/N_@U@J0"-]@V-^PV-_`VI
 +M`:[R#<H*+O8-RM#YC?4-K?4-#?8-\#NM]`T0!B#K"HWS#:WS#2WT#?`2K?4-
 +M#?L-C?L-K?8-#?P-C?P-3O8-;O4-3O0-K?0-T`6I@(WT#4QT#&"I`(WS#:F`
 +MC?0-()\*(%D,K?P-C0(.K?L-C>\-C0$.R0#0"JW\#<D!T`-,NPT@S/^B`R#)
 +M_ZT!#B#2_R!9#*W[#8W_#:W\#8T`#LD!T!BM_PW)`=`&[O(-3/,,R0+PJ\D`
 +MT`-,NPTXK?\-[>L-A0.M``[M[`T%`Y`6K>\-C50DK0$.C?<-K0(.C?@-H`'0
 +M#JW_#8WW#:T`#HWX#:``(!0,H`"Q_(WO#>;\T`+F_2#,_Z(#(,G_K?X-#?T-
 +M\`T@R`V@`+'\(-+_3&T-K>L-C?<-K>P-C?@-(.,+H`*M`0Z1_LBM`@Z1_LBM
 +M[PV1_N[K#=`#[NP-K?\-C0$.K0`.C0(.3/,,(,S_J0(@P_^I`TS#_Z7\T`+&
 +M_<;\K?T-T`/._@W._0U@YOS0`N;][OT-T`/N_@U@````````````````````
 +*````````````````
 +`
 +end
 +
 +crc32 for lzw.ml = 2460116527
 +
 +begin 644 lzw.bas
 +M`0@<"`$`BT&R+J=!LC$ZDR),6E<N34PB+#DL,0`P"`H`15BR,C4P,#I#3[(R
 +M-3`S`%`(#P"9(I,219)84$%.1"!/4B`20Y)/35!215-3/R(`;`@4`*%!)#J+
 +M022SL2)#(J]!)+.Q(D4BIS(P`)<('@"%(DY!344@3T8@24Y0550@1DE,12([
 +M1DDD.HO#*$9))"FR+J<S,`##""@`A2).04U%($]&($]55%!55"!&24Q%(CM&
 +M3R0ZB\,H1D\D*;(NIS0P`.L(,@"?,BPY+#(L1DDDJB(L4RQ2(CJ?,RPY+#,L
 +M1D\DJB(L4"Q7(@#["#P`BT$DLB)%(J>>15@`"PE&`(M!)+(B0R*GGD-/````
 +`
 +end
 +
 +crc32 for lzw.bas = 100674089
 +
 +===============================================================================
 +</code>
 +===== THREE-KEY ROLLOVER for the C-128 and C-64. =====
 +<code>
 +by Craig Bruce  <csbruce@neumann.uwaterloo.ca>
 +
 +1. INTRODUCTION
 +
 +This article examines a three-key rollover mechanism for the keyboards of the
 +C-128 and C-64 and presents Kernal-wedge implementations for both machines.
 +Webster's doesn't seem to know, so I'll tell you that this means that the
 +machine will act sensibly if you are holding down one key and then press
 +another without releasing the first (or even press a third key while holding
 +down two others).  This is useful to fast touch typers.  In fact, fast typing
 +without rollover can be quite annoying; you get a lot of missing letters.
 +
 +Another annoying property of the kernel keyscanning is joystick interference.
 +If you move the joystick plugged into port #1, you will notice that some junk
 +keystrokes result.  The keyscanners here eliminate this problem by simply
 +checking if the joystick is pressed and ignoring the keyboard if it is.
 +
 +The reason that a 3-key rollover is implemented instead of the more general
 +N-key rollover is that scanning the keyboard becomes more and more unreliable
 +as more keys are held down.  Key "shaddows" begin to appear to make it look
 +like you are holding down a certain key when you really are not.  So, by
 +limiting the number of keys scanned to 3, some of this can be avoided.  You
 +will get strange results if you hold down more than three keys at a time, and
 +even sometimes when holding down 3 or less.  The "shift" keys (Shift,
 +Commodore, Control, Alternate, and CapsLock) don't count in the 3 keys of
 +rollover, but they do make the keyboard harder to read correctly.
 +Fortunately, three keys will allow you to type words like "AND" and "THE"
 +without releasing any keys.
 +
 +2. USER GUIDE
 +
 +Using these utilities is really easy - you just type away like normal.  To
 +install the C-128 version, enter:
 +
 +BOOT "KEYSCAN128"
 +
 +and you're in business.  The program will display "Keyscan128 installed" and
 +go to work.  The program loads into memory at addresses $1500-$17BA (5376-6074
 +decimal), so you'll want to watch out for conflicts with other utilities.
 +This program also takes over the IRQ vector and the BASIC restart vector
 +($A00).  The program will survive a RUN/STOP+RESTORE.  To uninstall this
 +program, you must reset the machine (or poke the kernel values back into the
 +vectors); it does not uninstall itself.
 +
 +Loading the C-64 version is a bit trickier, so a small BASIC loader program is
 +provided.  LOAD and RUN the "KEYSCAN64.BOOT" program.  It will load the
 +"KEYSCAN64" program into memory at addresses $C500-$C77E (50432-51070 decimal)
 +and execute it (with a SYS 50432).  To uninstall the program, enter SYS 50435.
 +The program takes over the IRQ and NMI vectors and only gives them back to the
 +kernel upon uninstallation.  The program will survive a RUN/STOP+RESTORE.
 +
 +Something that you may or may not know about the C-64 is that its keys can be
 +made to repeat by poking to address 650 decimal.  POKE650,128 will enable the
 +repeating of all keys.  POKE650,0 will enable only the repeating of the SPACE,
 +DELETE, and CURSOR keys.  POKE650,64 will disable the repeating of all keys.
 +An unusual side effect of changing this to either full repeat or no repeat is
 +that holding down SHIFT+COMMODORE (character set shift) will repeat rapidly.
 +
 +To see the rollover in action, hold down the "J" key for a while, and then
 +press "K" without releasing "J" "K" will come out as expected, as it would
 +with the kernal.  Now, release the "J" key.  If you are on a C-128, you will
 +notice that the "K" key will now stop repeating (this is actually an important
 +feature - it avoids problems if you don't release all of the keys you are
 +holding down, at once).  Now, press and hold the "J" key again without
 +releasing the "K" "J" will now appear.  It wouldn't using the Kernal key
 +scanner.  You can also try this with 3-key combinations.  There will be some
 +combinations that cause problems; more on this below.
 +
 +Also, take a spaz on the joystick plugged into port #1 and observe that no
 +garbage gets typed in.  This was an annoying problem with the kernel of both
 +the 64 and 128 and has lead many different games to picking between joystick
 +#1 and #2 as the primary controller.  The joystick in port #2 is not a problem
 +to either Keyscan-128/64 or the Kernal.
 +
 +3. KEYBOARD SCANNING
 +
 +The Kernal scans the keyboard sixty times a second to see what keys you are
 +holding down.  Because of hardware peculiarities, there are multiple scanning
 +techniques that will give different results.
 +
 +3.1. SCANNING EXAMPLE
 +
 +An example program is included to demonstrate different keyboard scanning
 +techniques possible.  To run it from a C-128 in 40-column (slow) mode, enter:
 +
 +BOOT "KEYSHOW"
 +
 +On a C-64, you must:
 +
 +LOAD "KEYSHOW",8,1
 +
 +and then:
 +
 +SYS 4864
 +
 +The same program works on both machines.  Four maps of the keyscanning matrix
 +will be displayed on the 40-column screen, as scanned by different techniques.
 +The leftmost one is scanned from top to bottom "quickly" The second from the
 +left scans from bottom to top "quickly" The third from the left scans the
 +keyboard sideways, and the rightmost matrix scans the keys from top to bottom
 +"slowly".
 +
 +The mapping of keyscan matrix positions to keys is as follows:
 +
 +ROWS: \             COLUMNS:    peek($DC01)
 +poke   \
 +$DC00     128      64      32      16      8                   1
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-1  | DOWN  |   F5  |   F3  |   F1  |   F7  | RIGHT | RETURN| DELETE|
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-2  |LEFT-SH|                             |
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-4  |                                 |
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-8  |                                 |
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 + 255-16  |                                 |
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 + 255-32  |   ,         :                       |
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 + 255-64  |   /           |RGHT-SH|  HOME |   ;           |
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 +255-128  | STOP  |     |COMMODR| SPACE |     |CONTROL|         |
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 +
 +The following table contains the additional keys which must be scanned on the
 +C128 (but which are not displayed by the example scanning program).
 +
 +ROWS: \               COLUMNS:    peek($DC01)
 +poke   \
 +$D02F     128      64      32      16      8                   1
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-1  |                  TAB  |          HELP |
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-2  |             | ENTER |   LF  |          ESC  |
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-4  |NO-SCRL| RIGHT |  LEFT |  DOWN |   UP  |          ALT  |
 +         +-------+-------+-------+-------+-------+-------+-------+-------+
 +
 +These tables are presented on page 642 of the Commodore 128 Programmer's
 +Reference Guide.  The scan codes that are stored in location 212 on the C128
 +and location 197 on the C64 are calculated based on the above tables.  The
 +entry in the "1" bit position of the first line of the first table (DELETE)
 +has a scan code of 0, the "2" entry (RETURN) has a scan code of 1, etc., the
 +entry on the second scan line in the "1" position ("3") has a scan code of 8,
 +etc., all the way down to code 63.  The scan codes for the 128 go all the way
 +to 87, continuing in the second table like the first.
 +
 +You will notice some strange effects of the different scanning techniques when
 +you hold down multiple keys.  More on this below.  Also try pushing joystick
 +#1 around.
 +
 +3.2. SCANNING HARDWARE
 +
 +To scan the 128 keyboard, you must poke a value into $DC00 (CIA#1 port A) and
 +$D02F (VIC chip keyboard select port) to select the row to be scanned.  The
 +Data Direction Register for this port will be set to all outputs by the
 +Kernal, so you don't have to worry about it.  Each bit of $DC00 and the three
 +least significant bits of $D02F are used for selecting rows.  A "0" bit means
 +that a row IS selected, and a "1" means that a row IS NOT selected.  The poke
 +value to use for selecting among the various rows are given in the two tables
 +in the previous section.
 +
 +Using one bit per row allows you to select multiple rows at the same time.  It
 +can be useful to select all rows at one time or no rows.  To read the row that
 +has been selected, simply peek at location $DC01 (CIA#1 port B).  Each bit
 +will tell you whether the corresponding key is currently being held down or
 +not.  Again, we have reverse logic; a "0" means that the key is being held
 +down, and a "1" means that the key is not held down.  The bit values
 +corresponding to the keys are given as the column headings in the tables in
 +the previous section.  Since there is no such thing as a perfect mechanical
 +switch, it is recommended that you "debounce" each key row read in the
 +following way:
 +
 +again:
 +   lda $dc01
 +   cmp $dc01
 +   bne again
 +
 +So, to scan the entire keyboard, you simply select each scan row in some
 +order, and read and remember the keys held down on the row.  As it turns out,
 +you have to be a bit careful of exactly how you "select" a row.  Also, there
 +is a shortcut that you can take.  In order to find out if any key is being
 +held down in one operation, all you have to do is select all rows at once and
 +see if there are any "0" bits in the read value.  If so, there is a key being
 +held down somewhere; if not, then there is no key being held down, so you
 +don't have to bother scanning the entire keyboard.  This will reduce our
 +keyscanning significantly, which is important, since the keyboard will be
 +scanned every 1/60 of a second.
 +
 +As mentioned above, joystick #1 will interfere with the Kernal reading the
 +keyboard.  This is because the read value of joystick #1 is wired into CIA#1
 +port A, the same place that the keyboard read is wired in.  So, whenever a
 +switch in the joystick is pushed, the corresponding bit of the keyboard scan
 +register will be forced to "0", regardless of which keys are pressed and
 +regardless of which scan row is selected.  There's the catch.  If we were to
 +un-select all scan rows and still notice "0"s in the keyboard read register,
 +then we would know that the joystick was being pushed and would interfere with
 +our keyboard scanning, so we could abort keyboard scanning and handle this
 +case as if no keys were being held down.
 +
 +It still would be possible but unlikely that the user could push the joystick
 +in the middle of us scanning the keyboard and screw up our results, so to
 +defend against this, we check for the joystick being pushed both before and
 +after scanning the keyboard.  If we find that the joystick is pushed at either
 +of these times, then we throw out the results and assume that no keys are held
 +down.  This way, the only way that a user could screw up the scanning is if
 +he/she/it were to press a switch after we begin scanning and release it before
 +we finish scanning.  Not bloody likely for a human.
 +
 +You get the same deal for keyboard scanning on the 64, except you only need to
 +use $DC00 for selecting the scan rows.  Also note that you will not be able to
 +play with keyboard scanning from BASIC because of the interrupt reading of the
 +keyboard.  You must make sure that interrupts are disabled when playing with
 +the keyboard hardware, or interrupt scanning can come along at any time and
 +change all of the register settings.
 +
 +3.3. SCANNING SOURCE CODE
 +
 +The four keyboard scanning techniques of the example program are presented
 +below.  The declarations required for all of them are:
 +
 +pa = $dc00        ;row select
 +pb = $dc01        ;column read
 +ddra = $dc02      ;ddr for row select
 +ddrb = $dc03      ;ddr for column read
 +scanTable .buf 8  ;storage for scan
 +mask = $03        ;work location
 +
 +The code is as follows, in Buddy format.  Each routine scans the keyboard and
 +stores the results in the "scanTable" table.
 +
 +------------------+------------------+------------------+------------------+
 + Row forward fast | Row backward fast| Column right     | Row forward slow
 +------------------+------------------+------------------+------------------+
 +  sei              sei              sei              sei
 +  ldx #0          |  ldx #7          |  lda #$00        |  ldx #0
 +  lda #$fe        |  lda #$7f        |  sta ddra        |  lda #$fe
 +  sta pa          |  sta pa          |  lda #$ff        |  sta mask
 +  nextRow = *      nextRow = *      sta ddrb        |  nextRow = *
 +- lda pb          |- lda pb          |  ldy #7          |  lda mask
 +  cmp pb          |  cmp pb          |  lda #$7f        |  sta pa
 +  bne -            bne -            sta mask        |- lda pb
 +  eor #$ff        |  eor #$ff        |  nextCol = *      cmp pb
 +  sta scanTable,x |  sta scanTable,x |  lda mask        |  bne -
 +  sec              sec              sta pb          |  eor #$ff
 +  rol pa          |  ror pa          |- lda pa          |  sta scanTable,x
 +  inx              dex              cmp pa          |  sec
 +  cpx #8          |  bpl nextRow      bne -            rol mask
 +  bcc nextRow      cli              ldx #$ff        |  inx
 +  cli              rts              stx pb          |  cpx #8
 +  rts                              |  eor #$ff        |  bcc nextRow
 +------------------+------------------+  ldx #7          |  cli
 +                                     |- asl              rts
 +The forward "quick" scanning stores  |  rol scanTable,x +------------------+
 +the scan row selection mask into      dex             |
 +the row selection register and        bpl -           |
 +shifts the "0" bit one position      |  sec             |
 +"forward" for each row, directly,    |  ror mask        |
 +using a "rol $dc00" instruction.      dey             |
 +This would probably be the obvious    bpl nextCol     |
 +solution to an optimizing assembler  |  lda #$ff        |
 +programmer.  However, for some        sta ddra        |
 +reason not quite understood by this  |  lda #$00        |
 +author, there are "shadowing"        |  sta ddrb        |
 +problems with this approach.  If      cli             |
 +you were to hold down the two keys    rts             |
 +"H" and "K" at the same time, you    +------------------+
 +would notice that these two keys
 +are on the same column of two successive rows.  If you hold them both down,
 +you will see the two positions become active, but so will the same column of
 +all successive rows after the "H" and "K", even though these other keys are
 +not actually held down.  You will get an inaccurate reading if bad keys are
 +held down simultaneously.  You will notice the use of the term "active" above.
 +This is because although the hardware returns a "0" for active, the routine
 +converts that into a "1" for easier processing later.  I am not sure if
 +everyone will get this same result, but if your keyboard is wired the same as
 +mine, you will.
 +
 +The backward "quick" scanning operates quite similarly to the forward
 +scanning, except for the direction of the scan and the direction of the
 +"shadow"; the shadow goes upwards.  You might think that ANDing together the
 +results of the forward and backward scan together would eliminate the shadow,
 +but this will not work since any rows between the rows containing the two keys
 +held down will be incorrectly read as being active.
 +
 +The columnwise right scanning is the most complicated because the rows must be
 +converted into columns, to allow the scan matrix to be interpreted as before.
 +Also, the Data Direction Registers have to be changed.  You might think that
 +combinging row-wise scanning with columnwise scanning would give better
 +results, and it probably would, if it weren't for a bizarre hardware problem.
 +If you hold down two or more keys on the same scan row, say "W" and "E", some
 +of the keys will flicker or disappear, giving an inaccurate reading.
 +
 +The forward "slow" scanning is the best of the bunch.  Incidentally, it is
 +what the Kernal uses (as near as I can figure - their code is extremely
 +convoluted).  This technique is the same as the forward "quick scan," except
 +that the row selection mask is shifted in a working storage location and poked
 +into the CIA register, rather than being shifted in place.  I don't know why
 +this makes a difference, but it does.  There is still a problem with this
 +technique, but this problem occurs with all techniques.  If you hold down
 +three keys that form three "corners" of a rectangle in the scanning matrix,
 +then the missing corner will be read as being held down also.  For example, if
 +you hold down "C", "N", and "M", then the keyboard hardware will also think
 +that you are holding down the "X" key.  This is why this article implements a
 +"three-key" rollover rather than an "N-key" rollover.  Many three-key
 +combinations will still be interpreted correctly.  Note, however, that shift
 +keys such as SHIFT or CONTROL will add one more key to the hardware scanning
 +(but will not be counted in the three-key rollover), making inaccurate results
 +more likely if you are holding down multiple other keys at the same time.
 +
 +4. THE C-128 KEYSCANNER
 +
 +This section gives the source code for the C-128 implementation of the
 +three-key rollover.  The forward "slow" key matrix scanning technique is used,
 +extended to work with the extra keys of the 128.  It was a bit of a pain
 +wedging into the Kernal, since there is not a convenient indirect JMP into
 +scanning the keyboard, like there are for decoding and buffering pressed keys.
 +A rather lengthy IRQ "preamble" had to be copied from the ROM, up to the
 +point where it JSRs to the keyscanning routine.  This code in included in
 +the form of a ".byte" table, to spare you the details.
 +
 +Before scanning the keyboard, we check to see if joystick #1 is pushed and if
 +a key is actually pressed.  If not, we abort scanning and JMP to the key
 +repeat handling in the ROM.  If a key is held down, we scan the keyboard and
 +then examine the result.  First we check for the shift keys (SHIFT, COMMODORE,
 +CONTROL, ALT, and CAPS LOCK), put them into location $D3 (shift flags) in bit
 +postitions 1, 2, 4, 8, and 16, respectively, and remove them from the scan
 +matrix.  The CAPS LOCK key is not on the main key matrix; it is read from the
 +processor I/O port.  This is good, because otherwise we could not abort
 +scanning if it were the only key held down.
 +
 +Then we scan the keymatrix for the first three keys that are being held down,
 +or as many as are held down if less than three.  We store the scan codes of
 +these keys into a 3-element array.  We also retain a copy of the 3-element
 +array from the previous scan and we check for different keys being in the two
 +arrays.  If the old array contains a key that is not present in the new array,
 +then the use has released a key, so we set a flag to inhibit interpretation of
 +keys and pretend that no keys are held down.  This is to eliminate undesirable
 +effects of having other keys held down repeat if you release the most recently
 +pushed key first.  PC keyboards do this.  This inhibiting will be ignored if
 +new keys are discovered in the next step.
 +
 +If there are keys in the new array that are not in the old, then the user has
 +just pressed a new key, so that new key goes to the head of the old array and
 +we stop comparing the arrays there.  The key in the first position of the old
 +array is poked into the Kernal "key held down" location for the Kernal to
 +interpret later.  If more than one new key is discovered at the same time,
 +then each of the new keys will be picked up on successive keyboard scans and
 +will be interpreted as just being pushed.  So, if you press the "A", "N", and
 +"D" keys all at the same time, some permutation of all three of these keys
 +will appear on the screen.
 +
 +When we are done interpreting the keys, we check the joystick once more and if
 +it is still inactive, we present the most recently pushed down key to the
 +Kernal and JMP into the ROM keyboard decoding routine.
 +
 +Unlike in previous issues, this source code is here in literal form; just
 +extract everything between the "-----=-----"s to nab the source for yourself.
 +The source is in Buddy assembler format.
 +
 +-----=-----
 +;3-Key Rollover-128 by Craig Bruce 18-Jun-93 for C= Hacking magazine
 +
 +.org $1500
 +.obj "@0:keyscan128"
 +
 +scanrows = 11
 +rollover = 3
 +
 +pa = $dc00
 +pb = $dc01
 +pk = $d02f
 +
 +jmp initialInstall
 +
 +;ugly IRQ patch code.
 +
 +irq = *  ;$1503
 +   .byte $d8,$20,$0a,$15,$4c,$69,$fa,$38,$ad,$19,$d0,$29,$01,$f0,$07,$8d
 +   .byte $19,$d0,$a5,$d8,$c9,$ff,$f0,$6f,$2c,$11,$d0,$30,$04,$29,$40,$d0
 +   .byte $31,$38,$a5,$d8,$f0,$2c,$24,$d8,$50,$06,$ad,$34,$0a,$8d,$12,$d0
 +   .byte $a5,$01,$29,$fd,$09,$04,$48,$ad,$2d,$0a,$48,$ad,$11,$d0,$29,$7f
 +   .byte $09,$20,$a8,$ad,$16,$d0,$24,$d8,$30,$03,$29,$ef,$2c,$09,$10,$aa
 +   .byte $d0,$28,$a9,$ff,$8d,$12,$d0,$a5,$01,$09,$02,$29,$fb,$05,$d9,$48
 +   .byte $ad,$2c,$0a,$48,$ad,$11,$d0,$29,$5f,$a8,$ad,$16,$d0,$29,$ef,$aa
 +   .byte $b0,$08,$a2,$07,$ca,$d0,$fd,$ea,$ea,$aa,$68,$8d,$18,$d0,$68,$85
 +   .byte $01,$8c,$11,$d0,$8e,$16,$d0,$b0,$13,$ad,$30,$d0,$29,$01,$f0,$0c
 +   .byte $a5,$d8,$29,$40,$f0,$06,$ad,$11,$d0,$10,$01,$38,$58,$90,$07,$20
 +   .byte $aa,$15,$20,$e7,$c6,$38,$60
 +
 +;keyscanning entry point
 +
 +main = *
 +   lda #0               ;check if any keys are held down
 +   sta pa
 +   sta pk
 +-  lda pb
 +   cmp pb
 +   bne -
 +   cmp #$ff
 +   beq noKeyPressed     ;if not, then don't scan keyboard, goto Kernal
 +
 +   jsr checkJoystick    ;if so, make sure joystick not pressed
 +   bcc joystickPressed
 +   jsr keyscan          ;scan the keyboard and store results
 +   jsr checkJoystick    ;make sure joystick not pressed again
 +   bcc joystickPressed
 +   jsr shiftdecode      ;decode the shift keys
 +   jsr keydecode        ;decode the first 3 regular keys held down
 +   jsr keyorder         ;see which new keys pressed, old keys released, and
 +                        ;  determine which key to present to the Kernal
 +   lda $033e            ;set up for and dispatch to Kernal
 +   sta $cc
 +   lda $033f
 +   sta $cd
 +   ldx #$ff
 +   bit ignoreKeys
 +   bmi ++
 +   lda prevKeys+0
 +   cmp #$ff
 +   bne +
 +   lda $d3
 +   beq ++
 +   lda #88
 ++  sta $d4
 +   tay
 +   jmp ($033a)
 +
 +   noKeyPressed = *     ;no keys pressed; select default scan row
 +   lda #$7f
 +   sta pa
 +   lda #$ff
 +   sta pk
 +
 +   joystickPressed = *
 +   lda #$ff             ;record that no keys are down in old 3-key array
 +   ldx #rollover-1
 +-  sta prevKeys,x
 +   dex
 +   bpl -
 +   jsr scanCaps         ;scan the CAPS LOCK key
 +   ldx #$ff
 +   lda #0
 +   sta ignoreKeys
 +
 ++  lda #88              ;present "no key held" to Kernal
 +   sta $d4
 +   tay
 +   jmp $c697
 +
 +initialInstall = *      ;install wedge: set restore vector, print message
 +   jsr install
 +   lda #<reinstall
 +   ldy #>reinstall
 +   sta $0a00
 +   sty $0a01
 +   ldx #0
 +-  lda installMsg,x
 +   beq +
 +   jsr $ffd2
 +   inx
 +   bne -
 ++  rts
 +
 +   installMsg = *
 +   .byte 13
 +   .asc "keyscan128 installed"
 +   .byte 0
 +
 +reinstall = *           ;re-install wedge after a RUN/STOP+RESTORE
 +   jsr install
 +   jmp $4003
 +
 +install = *             ;guts of installation: set IRQ vector to patch code
 +   sei                  ;  and initialize scanning variables
 +   lda #<irq
 +   ldy #>irq
 +   sta $0314
 +   sty $0315
 +   cli
 +   ldx #rollover-1
 +   lda #$ff
 +-  sta prevKeys,x
 +   dex
 +   bpl -
 +   lda #0
 +   sta ignoreKeys
 +   rts
 +
 +mask = $cc
 +
 +keyscan = *             ;scan the (extended) keyboard using the forward
 +   ldx #$ff             ;  row-wise "slow" technique
 +   ldy #$ff
 +   lda #$fe
 +   sta mask+0
 +   lda #$ff
 +   sta mask+1
 +   jmp +
 +   nextRow = *
 +-  lda pb
 +   cmp pb
 +   bne -
 +   sty pa
 +   sty pk
 +   eor #$ff
 +   sta scanTable,x
 +   sec
 +   rol mask+0
 +   rol mask+1
 ++  lda mask+0
 +   sta pa
 +   lda mask+1
 +   sta pk
 +   inx
 +   cpx #scanrows
 +   bcc nextRow
 +   rts
 +
 +shiftValue = $d3
 +
 +shiftRows .byte $01,$06,$07,$07,$0a
 +shiftBits .byte $80,$10,$20,$04,$01
 +shiftMask .byte $01,$01,$02,$04,$08
 +
 +shiftdecode = *         ;see which "shift" keys are held down, put them into
 +   jsr scanCaps         ;  proper positions in $D3 (shift flags), and remove
 +   ldy #4               ;  them from the scan matrix
 +-  ldx shiftRows,y
 +   lda scanTable,x
 +   and shiftBits,y
 +   beq +
 +   lda shiftMask,y
 +   ora shiftValue
 +   sta shiftValue
 +   lda shiftBits,y
 +   eor #$ff
 +   and scanTable,x
 +   sta scanTable,x
 ++  dey
 +   bpl -
 +   rts
 +
 +scanCaps = *            ;scan the CAPS LOCK key from the processor I/O port
 +-  lda $1
 +   cmp $1
 +   bne -
 +   eor #$ff
 +   and #$40
 +   lsr
 +   lsr
 +   sta shiftValue
 +   rts
 +
 +newpos = $cc
 +keycode = $d4
 +xsave = $cd
 +
 +keydecode = *           ;get the scan codes of the first three keys held down
 +   ldx #rollover-1      ;initialize: $ff means no key held
 +   lda #$ff
 +-  sta newKeys,x
 +   dex
 +   bpl -
 +   ldy #0
 +   sty newpos
 +   ldx #0
 +   stx keycode
 +
 +   decodeNextRow = *    ;decode a row, incrementing the current scan code
 +   lda scanTable,x
 +   beq decodeContinue
 +                        ;at this point, we know that the row has a key held
 +   ldy keycode
 +-  lsr
 +   bcc ++
 +   pha                  ;here we know which key it is, so store its scan code,
 +   stx xsave            ;  up to 3 keys
 +   ldx newpos
 +   cpx #rollover
 +   bcs +
 +   tya
 +   sta newKeys,x
 +   inc newpos
 ++  ldx xsave
 +   pla
 ++  iny
 +   cmp #$00
 +   bne -
 +
 +   decodeContinue = *
 +   clc
 +   lda keycode
 +   adc #8
 +   sta keycode
 +   inx
 +   cpx #scanrows
 +   bcc decodeNextRow
 +   rts
 +
 +;keyorder: determine what key to present to the Kernal as being logically the
 +;only one pressed, based on which keys previously held have been released and
 +;which new keys have just been pressed
 +
 +keyorder = *
 +   ;** remove old keys no longer held from old scan code array
 +   ldy #0
 +   nextRemove = *
 +   lda prevKeys,      ;get current old key
 +   cmp #$ff
 +   beq ++
 +   ldx #rollover-1      ;search for it in the new scan code array
 +-  cmp newKeys,x
 +   beq +
 +   dex
 +   bpl -
 +   tya                  ;here, old key no longer held; remove it
 +   tax
 +-  lda prevKeys+1,x
 +   sta prevKeys+0,x
 +   inx
 +   cpx #rollover-1
 +   bcc -
 +   lda #$ff
 +   sta prevKeys+rollover-1
 +   sta ignoreKeys
 ++  iny                  ;check next old key
 +   cpy #rollover
 +   bcc nextRemove
 +
 +   ;** insert new keys at front of old scan code array 
 ++  ldy #0
 +   nextInsert = *
 +   lda newKeys,       ;get current new key
 +   cmp #$ff
 +   beq ++
 +   ldx #rollover-1      ;check old scan code array for it
 +-  cmp prevKeys,x
 +   beq +
 +   dex
 +   bpl -
 +   pha                  ;it's not there, so insert new key at front, exit
 +   ldx #rollover-2
 +-  lda prevKeys+0,x
 +   sta prevKeys+1,x
 +   dex
 +   bpl -
 +   lda #0
 +   sta ignoreKeys
 +   pla
 +   sta prevKeys+0
 +   ldy #rollover        ;(trick to exit)
 ++  iny
 +   cpy #rollover
 +   bcc nextInsert
 ++  rts                  ;now, the head of the old scan code array contains
 +                        ;  the scan code to present to the Kernal, and other
 +                        ;  positions represent keys that are also held down
 +                        ;  that have already been processed and therefore can
 +                        ;  be ignored until they are released
 +
 +checkJoystick = *       ;check if joystick is pushed: un-select all keyboard
 +   lda #$ff             ;  rows and see if there are any "0"s in the scan
 +   sta pa               ;  status register
 +   sta pk
 +-  lda pb
 +   cmp pb
 +   bne -
 +   cmp #$ff
 +   lda #$7f             ;restore to default Kernal row selected (to the one
 +   sta pa               ;  containing the STOP key)
 +   lda #$ff
 +   sta pk
 +   rts
 +
 +;global variables
 +
 +scanTable  .buf scanrows        ;values of the eleven keyboard scan rows
 +newKeys    .buf rollover        ;codes of up to three keys held simultaneously
 +ignoreKeys .buf 1               ;flag: if an old key has been released and no
 +                                ;  new key has been pressed, stop all key
 +                                ;  repeating
 +prevKeys   .buf rollover+2      ;keys held on previous scan
 +-----=-----
 +
 +And that's all there is to it.  :-)
 +
 +5. THE C-64 KEYSCANNER
 +
 +The boot program for the C-64 keyscanner is as follows:
 +
 +10 d=peek(186)
 +20 if a=1 then 60
 +30 a=1
 +40 load"keyscan64",d,1
 +50 goto 10
 +60 sys 49152+5*256  : rem $c500
 +
 +It is very much like boot programs for other machine language programs that
 +don't load at the start of BASIC.  It will load the binary from the last
 +device accessed, and activate it.
 +
 +A listing of the C-64 keyscanning code is not presented here because it is so
 +similar to the C-128 listing.  The only things that are different are the
 +Kernal patches and the keyboard scanning (because the three extra rows don't
 +have to be scanned).  The IRQ had to be substantially copied from the ROM,
 +again, to get at the call to the key scanning.  Also, rather than taking
 +over the BASIC reset vector (since there isn't one), the NMI vector is
 +taken over to insure the survival of the key scanner after a RUN/STOP+RESTORE.
 +A bit of its preamble also had to be copied out of ROM to get at the good
 +stuff.  If you want a copy of the C-64 listing, you can e-mail me.
 +
 +6. UUENCODED FILES
 +
 +Here are the binary executables in uuencoded form.  The CRC32s of the four
 +files are as follows:
 +
 +crc32 = 3398956287 for "keyscan128"
 +crc32 = 2301926894 for "keyscan64.boot"
 +crc32 = 1767081474 for "keyscan64"
 +crc32 = 1604419896 for "keyshow"
 +
 +begin 640 keyscan128
 +M`!5,'A;8(`H53&GZ.*T9T"D!\`>-&="EV,G_\&\L$=`P!"E`T#$XI=CP+"38
 +M4`:M-`J-$M"E`2G]"01(K2T*2*T1T"E_"2"HK1;0)-@P`RGO+`D0JM`HJ?^-
 +M$M"E`0D"*?L%V4BM+`I(K1'0*5^HK1;0*>^JL`BB!\K0_>KJJFB-&-!HA0&,
 +M$=".%M"P$ZTPT"D!\`REV"E`\`:M$=`0`3A8D`<@JA4@Y\8X8*D`C0#<C2_0
 +MK0'<S0'<T/C)__`Z((D7D#\@<18@B1>0-R"W%B#L%B`L%ZT^`X7,K3\#A<VB
 +M_RRT%S`QK;47R?_0!J73\":I6(74J&PZ`ZE_C0#<J?^-+]"I_Z("G;47RA#Z
 +M(-T6HO^I`(VT%ZE8A=2H3)?&(%46J4^@%HT`"HP!"J(`O3D6\`8@TO_HT/5@
 +M#4M%65-#04XQ,C@@24Y35$%,3$5$`"!5%DP#0'BI`Z`5C10#C!4#6*("J?^=
 +MM1?*$/JI`(VT%V"B_Z#_J?Z%S*G_A<U,F!:M`=S-`=S0^(P`W(POT$G_G:87
 +M.";,)LVES(T`W*7-C2_0Z.`+D-E@`08'!PJ`$"`$`0$!`@0((-T6H`2^J!:]
 +MIA<YK1;P$KFR%@73A=.YK19)_SVF%YVF%X@0X&"E`<4!T/I)_RE`2DJ%TV"B
 +M`JG_G;$7RA#ZH`"$S*(`AM2]IA?P'*342I`22(;-ILS@`[`&F)VQ%^;,ILUH
 +MR,D`T.88I=1I"(74Z.`+D--@H`"YM1?)__`DH@+=L1?P&,H0^)BJO;87G;47
 +MZ.`"D/6I_XVW%XVT%\C``Y#5H`"YL1?)__`FH@+=M1?P&LH0^$BB`;VU%YVV
 +M%\H0]ZD`C;07:(VU%Z`#R,`#D--@J?^-`-R-+]"M`=S-`=S0^,G_J7^-`-RI
 +9_XTOT&``````````````````````````````
 +`
 +end
 +begin 640 keyscan64.boot
 +M`0@."`H`1++"*#$X-BD`'0@4`(L@0;(Q(*<@-C``)0@>`$&R,0`Z""@`DR)+
 +M15E30T%.-C0B+$0L,0!#"#(`B2`Q,`!@"#P`GB`T.3$U,JHUK#(U-B`@.B"/
 +)("1#-3`P````````
 +`
 +end
 +begin 640 keyscan64
 +M`,5,Q,5,$,9,ZL4@ZO^ES-`IQLW0):D4A<VDTT;/KH<"L=&P$>;/A<X@).JQ
 +M\XV'`JZ&`J7.28`@'.JE`2D0\`J@`(3`I0$)(-`(I<#0!J4!*1^%`2!9Q4Q^
 +MZJD`C0#<K0'<S0'<T/C)__`Y(%C'D#D@6,8@6,>0,2"-QB"[QB#[QJF!A?6I
 +MZX7VHO\L>,<P+:UYQ\G_T`>MC0+P(:E`A<NH;(\"J7^-`-RI_Z("G7G'RA#Z
 +M(+7&HO^I`(UXQZE`A<NH3";K(.K%H@"]U<7P!B#2_^C0]6!+15E30T%.-C0@
 +M24Y35$%,3$5$#0!XJ0F@Q8T4`XP5`ZDGH,:-&`.,&0-8H@*I_YUYQ\H0^JD`
 +MC7C'8'BI,:#JC10#C!4#J4>@_HT8`XP9`UA@2(I(F$BI?XT-W:P-W3`?(`+]
 +MT`-L`H`@O/8@X?_0#R`5_2"C_2`8Y2#JQ6P"H$QR_J+_H/^I_H7U3';&K0'<
 +MS0'<T/B,`-Q)_YUMQS@F]:7UC0#<Z.`(D.-@`08'!X`0(`0!`0($(+7&H`.^
 +M@<:];<<YA<;P%+F)Q@V-`HV-`KF%QDG_/6W'G6W'B!#>8*D`C8T"8*("J?^=
 +M=<?*$/J@`(3UH@"&R[UMQ_`<I,M*D!)(AO:F]>`#L`:8G77'YO6F]FC(R0#0
 +MYABERVD(A<OHX`B0TV"@`+EYQ\G_\"2B`MUUQ_`8RA#XF*J]>L>=><?HX`*0
 +M]:G_C7O'C7C'R,`#D-6@`+EUQ\G_\":B`MUYQ_`:RA#X2*(!O7G'G7K'RA#W
 +MJ0"->,=HC7G'H`/(P`.0TV"I_XT`W*T!W,T!W-#XR?^I?XT`W&``````````
 +*````````````````
 +`
 +end
 +begin 640 keyshow
 +M`!-,"Q,``````````*F3(-+_(#83H@`@%Q0@5A.B"B`7%"!T$Z(4(!<4(/03
 +MHAX@%Q0@3!1,$!-XH@"I_HT`W*T!W,T!W-#X2?^=`Q,X+@#<Z.`(D.I88'BB
 +M!ZE_C0#<K0'<S0'<T/A)_YT#$SAN`-S*$.Q88'BI`(T"W*G_C0/<H`>I?X4#
 +MI0.-`=RM`-S-`-S0^*+_C@'<2?^B!PH^`Q/*$/DX9@.($-VI_XT"W*D`C0/<
 +M6&!XJ0"-`MRI_XT#W*`'J7^%`Z4#C0'<K0#<S0#<T/BB_XX!W$G_H@<*/@,3
 +MRA#Y.&8#B!#=J?^-`MRI`(T#W%A@>*(`J?Z%`Z4#C0#<K0'<S0'<T/A)_YT#
 +M$S@F`^C@")#F6&"@!(8$A`6B`+T#$R`V%!BE!&DHA020`N8%Z.`(D.I@A0*@
 +6!T8"J0!I,)$$B!#U8````````*(`8```
 +`
 +end
 +================================================================================
 +</code>
 +===== In the Next Issue: =====
 +<code>
 +
 +Next Issue:
 +
 +Tech-tech - more resolution to vertical shift
 +
 +One time half of the demos had pictures waving horizontally on the width
 +of the whole screen. This effect is named tech-tech and it is done using
 +character graphics. How exactly and is the same possible with sprites ?
 +
 +THE DESIGN OF ACE-128/64
 +
 +Design of ACE-128/64 command shell environment (and kernel replacement).  This
 +will cover the organization, internal operation, and the kernel interface of
 +the still-under-development but possibly catching-on kernel replacement for
 +the 128 and 64.  The article will also discuss future directions and designs
 +for the ACE environment.  ACE has a number of definite design advantages over
 +other kernel replacements, and a few disadvantages as well.
 +</code>
magazines/chacking6.txt ยท Last modified: 2015-04-17 04:34 (external edit)