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,​X ​    ; 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,​X ​     ; Clear the scroll
 +        DEX
 +        BPL LOOP3
 +        STA POINTER ​    ; Initialize the scroll pointer
 +        LDX #7
 +        STX COUNTER
 +LOOP10 ​ STA CHRSET,​X ​   ; 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,​X ​    ; Count a pointer for each text char and according
 +        AND #7          ;  to it fetch a y-position from the sinus table
 +        STA YPOS,​Y ​     ;   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,​X ​    ; Determine the position in video matrix
 +        TXA
 +        ADC LINESL,​Y ​   ; 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,​X ​     ; What character to plot ? (source)
 +        STA ZP2         ; ​ (char is already multiplicated by eight)
 +        LDA X16,X       ; Destination low byte
 +        ADC YPOS,​X ​     ;  (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,​Y ​   ; Move the text one position to the left
 +        STA CHAR,​Y ​     ;  (Y-register is initially zero)
 +        INY
 +        CPY #AMOUNT
 +        BNE LOOP12
 +        LDA POINTER
 +        AND #63         ; Text is 64 bytes long
 +        TAX
 +        LDA SCROLL,​X ​   ; 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,​Y ​   ; 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,​Y ​     ; Take a half of a sinus and mirror it to make
 +        STA TECH,​Y ​     ;  a whole cycle and then copy it as many times
 +        STA TECH+32,​Y ​  ; ​  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,​Y ​     ; Do the x-shift
 +        STA $D016
 +FIRST   LDX IMAGE0,​Y ​   ; 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/​a ​      ​A ​
 +       ​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),​Y ​    ; THE STRINGCODE AND CHARACTER ​ IN THE DICT.
 +         ​INY ​            ; AND SETS $FE/$FF POINTING TO IT. IF THE ENTRY
 +         AND ($FE),​Y ​    ; HAS TWO 255 IN IT THEN IT IS EMPTY AND SHOULD
 +         CMP #255        ; BE ADDED TO THE DICTIONARY.
 +         BEQ ADDTODICT
 +             LDA ($FE),​Y ​    ; 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     ​X ​ BITS     ​NEXTCODE HAS Y PHRASES
 +         BNE CHECKEOF ​       ;        -------- ​    ​-----------------------
 +         LDA #>​BUMPCODE ​     ;           ​9 ​                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),​Y ​  ; 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),​Y ​    ; 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       ​4 ​      ​2 ​      1
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-1  | DOWN  |   ​F5 ​ |   ​F3 ​ |   ​F1 ​ |   ​F7 ​ | RIGHT | RETURN| DELETE|
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-2  |LEFT-SH| ​  ​E ​  ​| ​  ​S ​  ​| ​  ​Z ​  ​| ​  ​4 ​  ​| ​  ​A ​  ​| ​  ​W ​  ​| ​  ​3 ​  |
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-4  |   ​X ​  ​| ​  ​T ​  ​| ​  ​F ​  ​| ​  ​C ​  ​| ​  ​6 ​  ​| ​  ​D ​  ​| ​  ​R ​  ​| ​  ​5 ​  |
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-8  |   ​V ​  ​| ​  ​U ​  ​| ​  ​H ​  ​| ​  ​B ​  ​| ​  ​8 ​  ​| ​  ​G ​  ​| ​  ​Y ​  ​| ​  ​7 ​  |
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 + ​255-16 ​ |   ​N ​  ​| ​  ​O ​  ​| ​  ​K ​  ​| ​  ​M ​  ​| ​  ​0 ​  ​| ​  ​J ​  ​| ​  ​I ​  ​| ​  ​9 ​  |
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 + ​255-32 ​ |   , ​  ​| ​  ​@ ​  ​| ​  : ​  ​| ​  ​. ​  ​| ​  ​- ​  ​| ​  ​L ​  ​| ​  ​P ​  ​| ​  ​+ ​  |
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 + ​255-64 ​ |   / ​  ​| ​  ​^ ​  ​| ​  ​= ​  ​|RGHT-SH| ​ HOME |   ; ​  ​| ​  ​* ​  ​| ​  ​\ ​  |
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 +255-128 ​ | STOP  |   ​Q ​  ​|COMMODR| SPACE |   ​2 ​  ​|CONTROL| ​  ​_ ​  ​| ​  ​1 ​  |
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 +
 +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       ​4 ​      ​2 ​      1
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-1  |   ​1 ​  ​| ​  ​7 ​  ​| ​  ​4 ​  ​| ​  ​2 ​  ​| ​ TAB  |   ​5 ​  ​| ​  ​8 ​  ​| ​ HELP |
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-2  |   ​3 ​  ​| ​  ​9 ​  ​| ​  ​6 ​  | ENTER |   ​LF ​ |   ​- ​  ​| ​  ​+ ​  ​| ​ ESC  |
 +         ​+-------+-------+-------+-------+-------+-------+-------+-------+
 +  255-4  |NO-SCRL| RIGHT |  LEFT |  DOWN |   ​UP ​ |   ​. ​  ​| ​  ​0 ​  ​| ​ 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,​y ​      ;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,​y ​       ;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)