User Tools

Site Tools



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

Link to this comparison view

magazines:chacking4 [2015-04-17 04:34]
magazines:chacking4 [2015-04-17 04:34] (current)
Line 1: Line 1:
 +                   ########
 +             ##################
 +         ######            ######
 +      #####
 +    #####  ####  ####      ##      #####   ####  ####  ####  ####  ####   #####
 +  #####    ##    ##      ####    ##   ##   ##  ###     ##    ####  ##   ##   ##
 + #####    ########     ##  ##   ##        #####       ##    ## ## ##   ##
 +#####    ##    ##    ########  ##   ##   ##  ###     ##    ##  ####   ##   ##
 +#####  ####  ####  ####  ####  #####   ####  ####  ####  ####  ####   ######
 +#####                                                                    ##
 + ######            ######        Volume 1, Issue #4
 +   ##################             October 5, 1992
 +       ########
 +====== Editor's Notes ======
 +by Craig Taylor (
 +  My apologies about this issue being posted later than was mentioned in a
 +  preview post on comp.sys.cbm newsgroup. Due to some problems with coding,
 +  school and Murphy's law the issue had to be delayed until now.
 +  I have asked the system admin's at my site concerning a mail-server but they
 +  said they did not have enough man-power (go figure) to get somebody to run it.
 +  I will be implementing a mail-server system in my account in the near
 +  future for retrieval of programs and back-issues. I'll post descriptions of
 +  how to use it it the next issue of C= Hacking as well as on the newsgroup
 +  comp.sys.cbm when I finish writing it.
 +  In this issue of C= Hacking we also start on an ambitious task: Developing
 +  a game for both the C128 and C64 modes that includes all of the features
 +  found in commercial games. Take a look in the Learning ML Column for more
 +  information.
 +  Also, The article concerning the 1351 mouse has _again_ been delayed due
 +  to time constraints. Rest assured that it will be in the next issue of
 +  C= Hacking.
 +  If you are interested in helping write for C= Hacking please feel free to
 +  mail (or We're always
 +  looking for new authors on almost any subject, software or hardware.
 +  Also note that this issue and prior ones are available via anonymous ftp from
 + under pub/rknop/HACKING.MAG.
 +  NOTICE: Permission is granted to re-distribute this "net-magazine", in 
 +  whole, freely for non-profit use. However, please contact individual 
 +  authors for permission to publish or re-distribute articles seperately.
 +====== In this issue ======
 +Learning ML - Part 4
 +  In the next issue we'll embark on a project of making a space invaders style
 +game for the C=64/128 from scratch using custom characters, interrupt-driven
 +music, animation, using the joystick, mouse or keyboard. The C64 and C128 
 +versions will be developed con-currently, each program taking advantage of 
 +the machine's capabilities. This is the first in a series - written by 
 +Craig Taylor.
 +The Demo Corner: FLI - more color to the screen
 +All of us have heard complaints about the color constraints on C64.
 +FLI picture can have all of the 16 colors in one character position.
 +What then is this FLI and how it is done ? Written by Pasi 'Albert' Ojala.
 +RS-232 Converter
 +  This article details plan for a User port TO RS232 connector using just ONE
 +IC and 4 capacitors. The circuit is included, and suggestions on alternative
 +chips and parts are examined.  Written by Warren Tustin
 +Introduction to the VIC-II
 +  This article examines the VIC-II chip in detail and provides an explanation
 +of the various registers associated with the chip. Written by Pasi 'Albert'
 +LITTLE RED READER: MS-DOS file reader for the 128 and 1571/81 drives.
 +This article presents a program that reads MS-DOS files and the root directory
 +of MS-DOS disks.  This program copies files from disk to disk so two disk
 +drives are required to use it (or a "virtual" drive).  This scheme imposes no
 +limit on the maximum size of a file to be transferred.  The user-interface
 +code is written in BASIC and presents a full-screen file selection menu.  The
 +grunt-work code is written in assembly language and operates at maximum
 +velocity.  Complete, explained code listings are included.  By Craig Bruce.
 +====== Learning Machine Language - Part 4 ======
 +by Craig Taylor (
 +                     +---------------------------+
 +                     | Space Invasion - Part 1   |
 +                                               |
 +                     | Programming: Craig Taylor |
 +                     | Graphics   : Pasi Ojala   |
 +                     | Music/Sound:              |
 +                                               |
 +                     +---------------------------+
 +I. Introduction
 +   ------------
 +  In this and future Learning Machine Language's we will develop a game called
 +  Space Invasion. The game will be similair to Space Indvaders and will run on
 +  the Commodore 64 or the Commodore 128 in 80 columns. It will feature all the
 +  "features" and "parts" that are found in commercial games with interrupt-
 +  driven music, custom character definitions, 100% machine language, multi-level
 +  game play, and input from the keyboard, joystick or mouse.
 +  Note | I am looking for someone to help aid music composition that will
 +  -----+ be introduced in a later issue. Programming of the 6502 is helpful
 +  but not a requirement. Please email me at if
 +  you are interested.
 +  Many thanks to Pasi Ojala for his work with the graphics in this program.
 +  Also please note: This entire program has been assembled sucessfully with 
 +  the Buddy-128 assembler for both the C=128 and C=64 version. Due to the
 +  length of the source files (over 1,500 lines) I'm not sure if Buddy-64 will
 +  handle it. Thus if you get errors during assembly, all I can say is: sorry.
 +  If this is the case then the next issue will handle dividing the program and
 +  data up into segments which can then each be loaded seperatly.
 +II. Machine Notes
 +    -------------
 +  The Commodore 64 and 128 programs for Space Invasion will differ slightly,
 +  mostly in the following areas:
 +       - custom character definition
 +       - memory initialization / setup
 +       - sound / music
 +  Because the actual game play and the changes nesscesary between the areas 
 +  listed above, we will use the Buddy Assembler notation for conditional 
 +  assembly to allow the development of only one file containing the source
 +  code. In addition to conditional assembly most of the routines will be
 +  written as one with jumps to subroutines containing the C64 or 128 direct
 +  code as the algorithims are usually the same for each.
 +  In addation there will be several source files and some miscellaneous include
 +  files for graphics and sound. For those of you who are or will be converting
 +  the assembly source over to a different assembler the conditional 
 +  assembly directives .if (condition) will only be true if the condition is
 +  non-zero. Ie: if the symbol computer is defined as 128 then the following
 +  example illustrates it:
 +       computer = 128
 +       .if computer-64         ; non-zero answer so therefore
 +       ; 128 code goes here
 +       .else
 +       ; 64 code goes here
 +       .ife                    ; end the .if condition.
 +  Also note that for much of the program we will _not_ be using the computer
 +  routines and instead be developing our own.
 +  In addition the program will show you how to use IRQ interrupts to simplify
 +  programming. We will be using them to play music in the background on three
 +  voices (sound effects will temporarily pre-empt the third voice from playing).
 +  Also animation of characters will be done via the IRQ. A little background
 +  on interrupts for those of you who are a bit hazy on what they are or have
 +  never seen them before (Also try taking a look at Rasters: What they are and
 +  how to use them in C= Hacking #3 - While this does not necessarily cover
 +  what we are going to be using interrupts for it does describe them quite 
 +  well.) Basically the computer generates an interrupt every 1/60th a second
 +  from a timer on the computer (usually from the CIA chip or the screen for 
 +  those of you who are curious). The computer will save all the registers, jump
 +  to a subroutine - perform the instructions there (usually updating time,
 +  scanning the keyboard etc...) and then recall all the registers and return
 +  to the user program. This is an interrupt. An IRQ interrupt describes an
 +  interrupt that we can allow to be "turned on" and "turned off" - ie: we
 +  can temporarily disable it if we have to. A NMI interrupt describes an
 +  interrupt which we can _not_ temporarily disable -- we will not be using
 +  NMI interrupts in this program.
 +III. The Process
 +     -----------
 +  Part of what this series of articles is focused at is the development of being
 +  able to analyze programming tasks and break them down into smaller workable
 +  problems. Once these problems or subroutines are completed your original
 +  problem is solved.
 +  Let's take this approach to Space Invasion:
 +  Problem Statement: Build a Space Invader program called Space Invasion.
 +  -----------------
 +  Usually, given a problem you have to re-work the problem statement to 
 +  encompass all of what you want. Let's try again:
 +  Problem Statement: Develop a Space Invader program called Space Invasion
 +  -----------------  utilizing the 64 or 128 screen with interrupt driven
 +                     music / sound, and allowing input from the keyboard, 
 +                     joystick or mouse.
 +  Hmmm... The problem statement listed above is better but it has no real order;
 +  we have no clear idea of where to start and what we need to do. It does 
 +  however tell us that we have the following sections:
 +       - 64 / 128 Screen Handling
 +       - Music / Sound
 +       - Input Handling
 +       - Game Driver (implied)
 +  Let's think a bit more about each of these sections and what each will
 +  involve:
 +  128 / 64 Screen Handling:      - Putting characters on screen.
 +  ------------------------       - Initializing the Screen / Registers.
 +                                 - Setting up the Custom Characters.
 +                                 - Handling any Animation.
 +  Music / Sound:                 - Setting up the Sound Chip Registers.
 +  -------------                  - Playing a note read from Memory.
 +                                 - Executing a Sound Effect.
 +  Input Handling:                - Device Selection (keyboard, mouse, joystick).
 +  --------------                 - Keyboard Scanning.
 +                                 - Mouse Scanning.
 +                                 - Joystick Scanning.
 +  Game Driver:                   - Title Screen.
 +  -----------                    - Initialization of Memory.
 +                                 - Level Setup.
 +                                 - Movement of Aliens.
 +                                 - Movement of Missles.
 +                                 - Movement of Player.
 +                                 - Collision Checking.
 +                                 - Collision Handling.
 +                                 - End of Level.
 +                                 - Score Updating.
 +                                 - End - Game handling.
 +                                 - High Score Update.
 +  Shrew! Long list 'eh? - Now you may have thought of some not listed above,
 +  and we may have possibly overlooked some crucial routines -- that's fine --
 +  the above is just intended as a building block - a place to start coding from.
 +  If we think of these as subroutines we can build a skeleton outline of the 
 +  program - yet we need some order in how we call them. Obviously we aren't
 +  going to move the player until we scan the input and that requires prior 
 +  device selection etc... 
 +  Hmm... Taking order into account we can re-state the problem as:
 +  Problem Statement: Develop a game similair to Space Invaders called Space
 +  -----------------  Invasion by initializing memory, the display device,
 +                     setting up Custom Characters, setting up the Music
 +                     Registers and displaying the title screen. From there,
 +                     select the input device and after that setup the current
 +                     level. Next, while playing music in the background and
 +                     scanning the input device, move the aliens, missles and
 +                     player checking for collisions and taking appropriate
 +                     action as required (player dies, score increases etc or
 +                     what-not). After each level display if the player is dead,
 +                     or set-up for the next level and repeat. When the game has
 +                     ended update the high score if necessary.
 +  Try saying that five times real fast! :-) But that problem statement is a
 +  whole lot better than the one we had at the beginning which simply said to 
 +  develop a game.
 +IV. Not All At One Time - What We're Doing This Time
 +    ------------------------------------------------
 +  Now this program is too complex, (as seen by the problem statement above) to
 +  have in one article so this issue we'll concentrate on the basic main loop
 +  and the initialization of the Custom Characters and the title screen. 
 +  Originally, I was planning on updating and listing the revised code in each
 +  issue. However, due to space limitations and the enormity of the program 
 +  currently (1,500+ lines!!) it will be placed for anonymous ftp at 
 + under the directory: pub/rknop/HACKING.MAG.
 +V. The Main Loop
 +   -------------
 +  What is a main loop? Basically it's where everything gets done. It calls other
 +  subroutines and keeps repeating until certain criteria are met - usually when
 +  the player requests to exit the game. However, inside you'll find inner loops
 +  for level play etc. 
 +  Our main loop for this program will be:
 +;; * Main Loop - This should be the last section in the source code.
 +; Main Loop
 +main'loop = *
 +               jsr memory'setup        ; Set-Up memory.
 +               jsr display'setup       ;  display.
 +               jsr char'setup          ;  "  custom character display.
 +               jsr music'setup         ;  music chip.
 +               jsr title'screen        ; Display the title screen.
 +               jsr select'input        ; Select Input Device.
 +level'loop = *
 +               jsr play'music          ; Start the music playing.
 +               jsr setup'level         ; Setup the current level.
 +             - jsr alien'move          ; Move aliens
 +               jsr missle'move         ;   missles
 +               jsr player'move         ;   player
 +               jsr check'collision     ; Check for collisions
 +               ldx collision'flag      ; Check collision flag.
 +               beq -
 +               dex                     ; Decrease .X by 1 so if X was 1 then
 +               beq player'die          ;    it's now 0 so we know player died.
 +               dex                     ; Decrease .X again so if X was 2 then
 +               beq alien'die'sound     ;    it's now 0 so we make alien death.
 +               jsr end'level           ; If we got here - than end of level.
 +               jsr wait'next           ; Wait for next keypress.
 +               jsr increase'level      ; increase level #.
 +               sec                     ; And go back....
 +               bcs level'loop             
 +alien'die'sound = *
 +               jsr make'alien'sound    ; make alien sound.
 +               sec                     ; set carry 
 +               bcs -                   ; and jump back.
 +player'die     jsr show'player'die     ; Show it on-screen.
 +               lda lives               ; Check # of lives.
 +               beq end'of'game         ; If 0 the end-of-game.
 +               bne level'loop          ; go back and re-start level.
 +               brk                     ; If we get here - than an error.
 +end'of'game    jsr end'game'screen     ; Show end-of-game screen.
 +               jsr high'score'update   ; Update the high score if need-be.
 +               jsr wait'next           ; Wait for next-game selection.
 +               lda quit
 +               beq +
 +               jsr setup'level'      ; Set-Up first level.
 +               sec
 +               bcs level'loop          ; and start playing it.
 +             + jmp quit'game
 +; End of Main Loop
 +  Some of the routines listed above we will later replace with actual code. It's
 +  much easier to see:
 +               inc level 
 +  than to see a 
 +               jsr increase'level
 +  and try to hunt down the code. I've included them in for now so that we can
 +  have a better idea of what is going on.
 +  In the file: invasion.src most of the statements above are commented out. 
 +  Once we write the routines we'll un-comment them. For now, this serves to
 +  still remind us of the routines we need to write.
 +  Also there are a couple of programming tricks that I used in the main loop 
 +  that probably need some clarifying.
 +  When handling the collisions the .X register is loaded with the result of the
 +  collision checking - $00 = no collisions, $01 = player died, $02 = alien died,
 +  $03 = end of level. Anytime a load to a register is done the flags are 
 +  automatically set as if you had compared it to 0 - hence we can ldx the 
 +  collision flag and immediately branch if equal to zero for no collisions. In
 +  addition to the load anytime the .X or .Y registers are incremented or 
 +  decremented an implicit comparison to zero is performed. So if the .X register
 +  is 1 previously, we decrement it then it will be zero and our BEQ instruction
 +  will branch. If it's two then it will be one and we can continue like this.
 +    [NOTE: Technically it's not a real comparison to zero but calling it a
 +    comparison to zero servers our purpose here. The only significant difference
 +    would be in the effect of the carry flag which is insignificant in our 
 +    code segment here.]
 +  Also in several locations are the two instructions:
 +               sec
 +               bcs [label]
 +  What these are doing are simply programming style - they could be substituted
 +  with JMP [label] - however they offer advantages over JMP.  They take up the
 +  a larger amount of execution time, however they are relocatable so any mucking
 +  around / moving sections of code during debugging will be less likely to
 +  crash. Using other flags are also valid -- the use of which flag (I prefer
 +  the carry flag) is usually dependent on the programmer. Geos defines a
 +  similair macro called BRA (branch always) which is equivlent to:
 +               clv
 +               bvc [label]
 +  Note that the above is just programming style, held over from my programming
 +  in assembly days. The use of JMP is probably preferable in terms of 
 +  execution and also in being able to branch more than 127 bytes away (the 
 +  branch instructions only have a range of +128/-127).
 +VI. Custom Characters
 +    -----------------
 +  Since we're writing for each of the seperate modes (64 mode, 128 mode) we have
 +  to take a look at the differences between the VIC chip (64 mode) and the 8563
 +  chip in the 128.  
 +  The Vic-Chip
 +  ------------
 +  The character sets in the VIC chip are defined as in the example below of 
 +  the character code $00 "@" (all references are to screen "poke" codes - not
 +  print codes).
 +         .byt #%00111100    Try holding the page (or moving away from the
 +         .byt #%01100110    screen) and taking a look at the patterns the 1's
 +         .byt #%01101110    and 0's make. Each character is thus defined as 
 +         .byt #%01101110    eight bytes who's bit patterns define it.  Having a
 +         .byt #%01100000    total of 256 characters available makes it
 +         .byt #%01100010    neccesary to set aside a total of 2,048 bytes. 
 +         .byt #%00111100
 +         .byt #%00000000
 +  Now, instead of designing all 256 character sets we'll just take advantage of
 +  the fact that the letters and numbers we want will already be there -- we'll
 +  just copy them from the ROM set into RAM, modify some of the other characters
 +  to reflect what we want and then tell the VIC chip to look at RAM to get the
 +  character set definitions.
 +  There are some problems with copying the 'system' characters, however. The
 +  Commodore 64 usually masks out the character set and typically it is only
 +  available to the VIC chip so that more space can be present for user programs
 +  and such.  It also takes up the section of memory that the I/O block in 
 +  $d000-$dfff does so that switching it in while interrupts are enabled is sure
 +  to result in a crash. 
 +  We're also going to be doing a few things that you may not expect -- instead
 +  of copying all 256 characters - we're gonna _just_ copy the first 128. This
 +  will give us all of the normal characters as the last 128 are the reverse-
 +  video counterparts to the first 128 characters. We're doing this to conserve
 +  space and because we really don't need that many characters defined.
 +  Also location $01 contains what $d000-$dfff holds and we will have to modify
 +  bit 2 to switch the character ROM in. Hence, the following program code is
 +  used to copy the character set:
 +copy'chars = *                 ; must be run w/ interrupts disabled
 +               lda $01         ; register 1 = the control to switch in the char.
 +                               ; rom.
 +               pha             ; save it as we'll later need to sta' it back.
 +               and #%11111011  ; Bit 2 controls it - clear it to switch it in.
 +               sta $01         ; and make it so we can read it in.
 +               lda #>$3000     ; move chars to $3000
 +               sta dest+1
 +               lda #>$d800     ; from $d800 (start of char set) (lower-case)
 +               sta src+1
 +               ldy #$00        ; lo-bytes of both src, dest = $00.
 +               sty src
 +               sty dest
 +               ldx #$10        ; copy 2k of data.
 +             - lda (src),    ; copy byte.
 +               sta (dest),y
 +               iny
 +               bne -           ; continue until .Y = 0.
 +               inc src+1       ; increase source & dest by 256
 +               inc dest+1
 +               dex             ; decrease .X count.
 +               bne -           ; if non-zero then continue copying, else
 +               pla             ; restore value of $01 
 +               sta $01         ; and put back.
 +               lda $d018       ; set VIC-chip address.
 +               and #$f1        ; to show char set.
 +               ora #$0c       
 +               sta $d018       ; and finally tell VIC where the char set is...
 +               rts             ; and return.
 +  Note that we still need to change the actual characters we're gonna be using.
 +  That will be handled in the section after next: Changing the Characters as 
 +  there is a great deal of similarity between the 128 and 64 implementations.
 +  The 8563 Chip
 +  -------------
 +  The 8563 80-Column chip usually has 16k or 64k Ram attatched to the chip 
 +  which the CPU does not have direct control over. It has to direct the 8563 
 +  to store and retrieve values to that memory. What makes control over that 
 +  memory all the more difficult is the fact that the 8563 only has two lines
 +  or addresses that the CPU can control.  
 +  The 8563 has a character set in much the same way the VIC chip does, save
 +  one exception - each character set can have up to 16 lines. Normally, the last
 +  eight lines are filled with $00 and are not shown. (Provisions can be made to
 +  have 8x16 characters but it is not needed for this game and thus, will not be
 +  shown - For more information See C= Hacking Issue #2: 8563: An In-Depth Look.)
 +  Thus the algorithim is similair to the C=64 but 8 zero-bytes will need to be
 +  written at the end of every eight bytes read.
 +  However, the 8563 does make things easier for us! - When the computer is first
 +  turned on a copy of the Character Set from ROM is copied into the 8563. The
 +  8563 has no ROM Character Set associated with it and thus we are able to just
 +  simply modify the character set that is in the 8563 memory instead of copying
 +  it over.  Because of this no routine will be presented to copy a character
 +  set into the 8563 memory, rather the discussion of copying individually 
 +  defined characters will take place in the next section. The C=128 also makes
 +  life even easier for us at the end when we will exit the program, 
 +  modifying the character set back to the "standard" Commodore character set
 +  by a routine in the KERNAL that will copy the characters back. We'll take a
 +  look at it closer when we write the exit routine.
 +  Also note that since the 8563 chip supports the 80 column screen we will
 +  be defining two characters that can be placed side by side for each alien
 +  so that the playing field will be similair to the C64 version. However, for
 +  the title screen we will be switching the 8563 into a "40 column" mode
 +  to make programming easier, in addition to expanding the character bit-mapped
 +  logo.
 +  Changing the Characters
 +  -----------------------
 +  A lot of the times you'll find yourself re-using subroutines and code that
 +  you have previously created, gradually, over a period of time building up
 +  a library of routines. When thinking through the purpose and intent of this
 +  routine I thought about possibly building it so it would read a table and
 +  change the character set based on that table. The 64/128 character sets
 +  would be the same - this routine would automatically generate the eight 
 +  additional bytes needed by the 8563 if need-be and it would call the 
 +  appropriate storage routine - store to either the 8563 or the computer
 +  memory. 
 +  Now you may be asking why would you want to store to the computer memory
 +  in 128 mode? Why not just have two seperate versions? - Yes - that could
 +  be possible but I'm implementing it this way because in the future I may
 +  see a need to define custom characters in 128 mode for the 40 column screen.
 +  This way I can just extract the routine, pop it into my program and I've got
 +  that section of the code complete. 
 +  This is what I was thinking of for the data table:
 +      .byt 1 = 8563, 0 = comp. memory.
 +      .word address ; address base of char-set in computer or 8563 memory.
 +      .byte char #  ; (to start)
 +      .byte # of chars to define
 +      .byte # of characters to define
 +      .byte data,data,....,data8 ; character data.
 +      .byte data,data,....,data8 ; character data. etc....
 +      . . .
 +  Entrance into the routine will consist of .AY holding the location of the 
 +  table.  We will keep the address of the table and keep incrementing it as
 +  we go along in z-page locations.
 +       install'char = *
 +               sta zp1                 ; save .ay in table address
 +               sty zp1+1
 +               ldy #$00                ; read computer mode.
 +               jsr get'byte
 +               sta mode
 +               jsr get'byte            ; get address base.
 +               sta adr
 +               jsr get'byte
 +               sta adr+1
 +               jsr get'byte            ; get number of characters to copy.
 +               sta numb            
 +               jsr get'byte            ; get next character #.  
 +               sta wrk                 ; save in temp. location.
 +               lda #$00
 +               sta wrk+1
 +               asl wrk                 ; shift left x3 times = *8
 +               rol wrk+1
 +               asl wrk
 +               rol wrk+1
 +               asl wrk
 +               rol wrk+1
 +               lda mode                ; if for 8563 then multiply 1 more time.
 +               beq +      
 +               asl wrk
 +               rol wrk+1
 +             + lda adr                 ; add character address in.
 +               clc
 +               adc wrk
 +               sta wrk
 +               lda adr+1
 +               adc wrk+1
 +               sta wrk+1               ; address now calculated
 +               jsr setadrs             ; set address in proper chip
 +  loop'install ldx #$08                ; copy 8 bytes.
 +             - jsr get'byte
 +               jsr writebyte           ; write out byte.
 +               dex
 +               bne -
 +               lda mode                ; if 128 then fill out 8 more $00 bytes.
 +               beq +
 +               lda #$00
 +               ldx #$08
 +             - jsr writebyte
 +               dex
 +               bne -
 +             + dec numb
 +               bne loop'install
 +               rts       
 +  What? We have thre subroutines : writebyte, setadrs, and get'byte that we
 +  haven't examined yet. These are going to be the routines that are dependant
 +  on the computer type. Also, writebyte will require that .XY not be disturbed;
 +  setadrs requires that .Y not be disturbed hence the following:
 +       setadrs tya             ; save .yx
 +               pha
 +               txa
 +               pha
 +               lda mode        ; check computer type.
 +               beq +           ; if C=64, then jump ahead.
 +               ldx #18         ; VDC register - current memory address hi
 +               lda wrk+1       ; get address hi
 +               jsr wr'vdc
 +               ldx #19         ; VDC register - current memory address lo
 +               lda wrk         ; get address lo
 +               jsr wr'vdc
 +             + pla             ; restore .XY
 +               tax
 +               pla
 +               tay
 +               rts             ; and return.
 +  Note that we really don't need a setadrs for the C=64 -- we can just index 
 +  off (wrk) in the writebyte routine which follows:
 +  writebyte    sta temp        ; save as we need it later.
 +               txa             ; Save .XY
 +               pha
 +               tya
 +               pha
 +               lda mode        ; now check computer type.
 +               beq +           ; if c64 jump ahead
 +               lda temp        ; recall temp.
 +               jsr wr'vram 
 +               sec
 +               bcs ++          ; jump ahead
 +             + ldy #$00        ; C64 / y-index = $00
 +               lda temp        ; get value
 +               sta (wrk),    ; store
 +               inc wrk         ; now increase address
 +               bne +
 +               inc wrk+1
 +             + pla             ; now return after recalling .XY
 +               tay
 +               pla
 +               tax
 +               rts             ; and return.
 +  Note that the following routine is fairly short but it is called numerous
 +  times within the routines that use data tables such as install'char, 
 +  write'txt and write'col. 
 +  get'byte = *
 +               lda (zp1),y
 +               iny
 +               bne +           ; if zero then increase zp1 hi
 +               inc zp1+1
 +             + rts
 +  Not bad 'eh?  A quick note: The instructions: PLA, TAY, PLA, TAX, PHA, etc.. 
 +  are routines that Push or Pull (pha,pla) the .A onto the stack. The TAY, TAX,
 +  TXA, TYA are instructions that transfer a register to another (ie: the TAY
 +  transfers the A register to .Y, TXA transfers .X to .A etc...) By using the
 +  combination of these with the stack we can save the registers and later
 +  re-call them so that they are the same when we entered the routine. The
 +  stack is usually a "mystery" item to new programmers of the 6502 series. 
 +  Basically it's just like any other stacks in the real world - the last item
 +  thrown (I'm non-practicing perfectionist so I throw stuff.. ;-) ) or pushed
 +  on the stack will the first item removed or pulled from the stack. For 
 +  example I've got a stack of books sitting near me :
 +               Mapping the Commodore 128
 +               128 Internals
 +  and I'm holding Mapping the Commodore 64 in my hands. If I push (or toss)
 +  the book onto the stack (and hopefully hit the stack instead of the floor)
 +  I'll have the following stack:
 +               Mapping the Commodore 64
 +               Mapping the Commodore 128
 +               128 Internals
 +  and it should be easy to see that if I "pull" the next book off the stack
 +  that I'll get the Mapping the Commodore 64 book. The next book to be "pull"ed
 +  after that would be the Mapping the Commodore 128 book. This idea can be
 +  applied to the 6502 stack -- It will keep storing values (up to 256) when you
 +  "push" them on (via the PHA instruction) and will retrieve the last value
 +  stored when you "pull" them off (via the PLA instruction). Another PLA
 +  instruction would return the next value that had been stored.
 +  The Character Bitmaps
 +  ---------------------
 +  Pasi Ojala is to be credited with all the graphics and many thanks go out
 +  to him.
 +  The game logo is made up of 120 custom defined characters that will be 
 +  printed in the following manner (on the 128 screen they will be centered).
 +  (in reverse video)...
 +           ABCDEFGH . . . [up to 40 characters]
 +           IJKLMNOP
 +           QRSTUVWX    
 +  and everything will line up.
 +  So that it will look like a "mini-bitmap" We could have used bitmap mode
 +  and made a very nice looking title screen but that would have involved 
 +  switching and allocating memory for the bitmap, etc . . . On both the
 +  8563 and the VIC that involves a bit more work and so custom characters
 +  will be used for the title screen. The regular letter and numeric characters
 +  will be available so that we can display credits and game instructions
 +  below the logo.
 +  Now - in the program listing we could list them as binary #'s and that would
 +  make editing them very easy but we're gonna use their decimal representation
 +  in the program listing.
 +  The characters are defined similair to the logo except they are treated as
 +  single characters. In the 128 version due to the 80 column screen we are
 +  going to use two characters side by side to simulate one alien so that the
 +  playing field will be similair to the C64 version. In addation, during the
 +  main loop we will modify the character sets to support animation of the 
 +  aliens. In the data listing there is a reference to "frames" - for each of
 +  the aliens there are 8 differant frames.
 +  Oh! - There will be more characters defined in the future. Right now I'
 +  mainly interested in getting some base characters down so you can see how
 +  custom characters are implemented.  When we start setting up different levels
 +  and such we'll add more characters then. Currently the custom characters
 +  are not used - only the characters for the logo. For those of you who are
 +  curious try installing the characters via install'char and taking a look
 +  at the aliens.
 +VII. Title Screen
 +     ------------
 +  The title screen is usually a lead-in to the actual game and it's aim is
 +  to tell the player how to play the game, any available options and p'haps
 +  present a nice graphic or two to "wow" the user into playing. In addation,
 +  the main musical theme can be introduced here to unify the game-playing.
 +  The discussion below does not take into account color but rest-assured we
 +  will be using varying colors in the title screen. The format for the color
 +  data will be almost identical to the title screen format except it will
 +  be structured via the following:
 +       .word address
 +       .byte num_of_chars to put color ($00= end of data)
 +       .byte color_value
 +  The routine (color'text) can be found in the source listings at the end of
 +  this article. Because of the similarity between it and write'text it is
 +  not discussed in this article.
 +  Title Screen BackGround
 +  -----------------------
 +  The title screen I envisoned as a bordered screen (using the normal C= 
 +  character set - ie: C= A,S,Z,X on the keyboard) with our bitmap in the middle
 +  and under-neath it a short description of the game and game-play instructions.
 +  Now this is my idea of the screen layout (rough drawing as we're not using
 +  the actual screen dimensions):
 +     +-------------------------------------------------------------+
 +     | -LOGO ----------------------------------------------------- |
 +     | --------------x 3 lines------------------------------------ |
 +     | ----------------------------------------------------------- |
 +                                                                 |
 +                       Space Invasion C64/128                    |
 +                         Programming : Craig Taylor              |
 +                         Graphics    : Pasi Ojala                |
 +                         Sound       : ????????????              |
 +                                                                 |
 +     | ----------------------------------------------------------- |
 +     | To Play:                                                    |
 +        Use joystick in port 2, mouse in port 1 or keyboard:     |
 +                A - Left, Z - Right   Space - Fire               |
 +                  F1 - Restart                                   |
 +                                                                 |
 +     +-------------------------------------------------------------+
 +  Title Screen Formatting
 +  -----------------------
 +  We come into a problem here -- the screen is some 1000 characters on the C64,
 +  and 2000 characters for the C=128.  It would be extremely wasteful to store
 +  that many characters in memory just to reproduce a title screen - and most of
 +  them consisting of spaces at that!!  
 +  What we'll do is to just specify the address on screen, the # of characters
 +  and then list the characters. It will be similair to our custom character
 +  table driver above but will be different enough that a new routine is 
 +  warrented. We will however use the two subroutines writebyte and setadrs
 +  that were developed in the previous routine. The data will look like the
 +  following:
 +       .word address
 +       .byte num_of_chars ($00= end of data)
 +       .ascii "text"
 +       .byte address .... etc.... 
 +  and we'll enter with .AY containing the address of the table.
 +  So basically we come up with the following:
 +       write'txt = *
 +               sta zp1         ; save .ay in table address
 +               sty zp1+1
 +               ldy #$00
 +   loop'w'text = *
 +               jsr get'byte   ; set address.
 +               sta wrk
 +               jsr get'byte
 +               sta wrk+1
 +               jsr get'byte    ; get # of chars to write out.
 +               cmp #$00
 +               beq +           ; if zero then exit.
 +               tax
 +               jsr setadrs     ; set address to wrk,wrk+1
 +             - jsr get'byte
 +               jsr writebyte   ; write out byte.
 +               dex
 +               bne -
 +               sec
 +               bcs loop'w'text ; this is an absolute jump to loop 
 +             + rts             ; return.
 +  This is similair to our previous routine, and was in fact copied and modified 
 +  from the previous routine. 
 +VIII. Debugging
 +      ---------
 +  Now, not all programs are perfect, and during the development of this 
 +  portion of the game there were several errors found. Tracing an error in
 +  Machine/Assembly-Language is like trying to find a grammatical error in a
 +  language you don't know. ;-) But seriously, there are several ways to track
 +  down errors in your code.
 +  1 - Try tracing it through by hand playing "What if I were the computer" and
 +      following what each register does.
 +  2 - Are you switching the LoHi order of variables? Ie: is it lda #< or 
 +      lda #>?? 
 +  3 - Set BRK points and run the program / subroutine within a machine language
 +      monitor and make sure the registers / memory locations contain the values
 +      that they should. If not, find out why.
 +  4 - Try to simplify your code in terms of programming ease - Make the 
 +      assembler do the work for you - it's a lot less likely to make errors 
 +      than you are.
 +  5 - Think logically!!!
 +  6 - Change something at random and pray.
 +  I can't stress numbers 3 and 5 enough. During the writing of the install'char 
 +  routine there were numerous bugs that were eventually tracked down by 
 +  setting a BRK instruction further along in the code and seeing exactly what
 +  the register / memory locations were. Also the use of temporary load and
 +  store instructions into "safe" regions of memory helped me monitor what some
 +  of the values were.
 +  For example, at one point I had a section of code similair to the following:
 +             clc
 +             lda value
 +             adc data
 +             bne +
 +             inc data+1
 +           + [.... ]
 +  And it's purpose was to add value to data. Now I've found simple errors are
 +  usually found last, after complex errors. And not until a set a break point
 +  like:
 +             clc
 +             lda value
 +             adc data    <-----Missing Instruction after here-------------+
 +             bne +                                                        |
 +             inc data+1                                                   |
 +           + BRK                                                          |
 +             [.... ]                                                      |   
 +                                                                          |
 +  did I actually figure out that I was missing the STA DATA instruction --+
 +  So, when writing, modifying, and trying to debug code try to take your time
 +  and isolate every possible problem. Also don't be afraid to stop the code
 +  mid-stream as in the above with use of the BRK. You can always remove it
 +  (and probahly should) in the final code and it serves as a very valuable 
 +  debugging tool with the aid of a machine-language monitor.
 +IX. Memory Map Considerations
 +    -------------------------
 +  Before you start a program it's a good idea to consider where in memory you
 +  will have everything. Now we've already started some of the program above
 +  and just blindly picked numbers at random it seemed like $3000 for the 
 +  character set for the C=64 etc... We didn't - I'm introducing the Memory
 +  Map Considerations here to show the example of what if we didn't think
 +  about how memory was going to be organized.
 +  The C=64 only has 64k of memory of which typically the range $0800-$a000
 +  is available and $c000-$cfff is also.  If we had blindly picked numbers
 +  all over the place to store our code then we would have a disorganized
 +  program that would most likely accidentally use one subroutines storage
 +  as temporary data for another. It's like shooting randomly in Laser Tag
 +  not checking to see if there is a target there or not first... The end
 +  result: Chaos.
 +  Currently we're not following the rule for "temporary variables" but as
 +  we gradually fade out of the normal C-64/128 default mode and write our
 +  own routines / interrupt handlers we'll switch things over. Also, on the
 +  C=128 instead of using Bank 0 with the I/O block enabled we're currently
 +  using the BANK 15 configuration as the program doesn't extend past $4000
 +  yet ($0000-$4000 is common memory in the normal C=128 configuartion).
 +  64 Considerations
 +  -----------------
 +  The 64 will have free memory in the following areas: $0800-$a000, and
 +  $c000-$cfff. However, if we disable the Basic Rom we can have the whole
 +  area from $0800-$cfff free for our program. Because we don't need the
 +  Basic Rom we will do just that (in the listing now we currently won't but 
 +  it will be done in a future issue). Therefore having the character set
 +  at $3000-$5000, the music data at $5000-$8000, the program will have the
 +  area free from $8000-$cfff. $0800-$3000 will be available if needed for
 +  routines who need temporary storage. 
 +  Temporary Storage is going to be defined as follows. Each routine that needs
 +  temporary storage will be assigned a "level" number. The lower levels will
 +  be assigned level 1 on up to level 3. The range $0800-$3000 will be 
 +  broken down into the following sub-ranges.
 +       Level 1: $0800-$1000
 +       Level 2: $1000-$1800
 +       Level 3: $1800-$3000
 +  This way when writing the sub-routines we can be assured that a section of
 +  memory is not overwritten by a subroutine we call. When we actually start
 +  programming we'll decide where in each sub-range the routine will have 
 +  access to.
 +  128 Considerations
 +  ------------------
 +  The 128 has two "banks" of 64k each. Normally for large programs we would 
 +  think about using both banks - (from the idea: Hey! - We got it, why not
 +  flaunt it?) but we won't be using both banks.
 +  Free memory on the C128 typically consists of the range $0400-$09ff
 +  (where we'll be overwriting the 40 column screen (which we're gonna blank
 +  anyway) and the Basic run-time stack.) Also the area from $0b00-$0fff
 +  is free (overwriting the tape area, the rs-232 buffers,l and the sprite
 +  definition area). Also $1300-$cfff will be free. 
 +  Now, the C=128 has different memory maps it can configure itself to - 
 +  Bank 15 is the standard mode under most basic programs and allows the
 +  programmer to directly "sys" to calls. The MMU (memory management unit -
 +  the chip that does everything) sees memory in a slightly differant way
 +  than from basic. We'll cover it in more detail when we examine the mem_init
 +  routine. For now, we're just gonna set up in the program and not in the
 +  coding segments. The explanation of what we're doing will be "revealed"
 +  in a future issue. 
 +  We will use Bank 0 of memory and from $1300+ will be the program. The ranges
 +  of $0400-$09ff and $0b00-$0fff will be used in a similair mannar as the C64
 +  ranges were for Temporary Storage. We will also have the I/O section from
 +  $d000-$dfff swapped in. This is not a standard "basic BANK #" but when we
 +  cover the init'memory routine we'll see how we can do this. Music data
 +  will be from $a000-$d000. 
 +X.  Looking Forward / Back
 +    ----------------------
 +  Hopefully through the listing and the discussion of the routines you have
 +  started to understand the basic concept of programming: breaking down problems
 +  into smaller solvable steps. Try looking back over the code asking yourself
 +  why that instruction is there. What would happen if you switched the order?
 +  Is there an easier, better way to do the same thing? Why? Better yet, how?
 +  Examine the code, mess with it, muck it up so it doesn't work and then figure
 +  out exactly why. The only way to learn is by experimentation. (BTW, muck up
 +  a _copy_ of it - not the original ... *grins*)
 +  Take a look at the different sections of code and analyze them to see how
 +  they do what they do. Take a look at how the code was organized in terms
 +  of simplification. Trace through each subroutine so that you're able to 
 +  know what the return values will be. In other words: Study, Study, Study!!
 +  I'm in school and so I know I just used the dreaded 'S' word but that'
 +  what you're going to have to do if you're interested in learning 65xx/85xx
 +  machine language. The only way to learn it (easily) is to study other 
 +  people's code and try to understand why they did what they did.
 +  Next time we will take a look at the input routines for the mouse, joystick
 +  and keyboard scanning. In addition we will also allow the player to move
 +  the ship around on the screen to test the input drivers. 
 +  In addation, I am still looking for an individual to help with music and
 +  sound composition for this program. A knowledge of the SID chip and 
 +  programming is helpful but not required. If you're willing to help then
 +  please email me at
 +XI. Listings
 +    --------
 +  Because of the enormity of the program listing (some 1,500+ lines) it will 
 +  not be listed in this article but will instead be available via anonymous
 +  ftp at under pub/rknop/HACKING.MAG as invas1.sfx. 
 +  For those of you on the mailing list who would like to recieve it, a Mail-
 +  Server will be set up soon to handle requests and information will be
 +  sent to you concerning information about using it as soon as it's completed.
 +  In the invasion1.sfx file there are the following files:
 +       invasion.src - the main file
 +       graphics.src - handles all graphics routines
 +       logo.dat     - custom character logo
 +       chars64.dat  - alien custom characters for C=64
 +       chars128.dat - alien custom characters for C=128
 +       titletxt.dat - text data for title screen
 +       titlecol.dat - color data for title screen
 +       invasion-128 - executable version of Space Invasion so far for C=128
 +       invasion-64  - executable version of Space Invasion so far for C=64
 +  Note: For the Commodore 128 it's recommended that you do a run/stop-restore
 +  and then a "BANK15:SYS7168" to execute the program. For the Commodore 64 it's
 +  recommended the border be changed via: "POKE53280,0:POKE53281,0:SYS 32768" to
 +  run the program.
 +====== The Demo Corner ======
 +===== FLI - more color to the screen =====
 +by Pasi 'Albert' Ojala ( or
 +                        Written on 16-May-91   Translation 01-Jun-92
 +(All timings are in PAL, although the principles will apply to NTSC too)
 +All of us have heard complaints about the color constraints on C64. One 8x8
 +pixel character position may only carry four different colors.  FLI picture
 +can have all of the 16 colors in one char position.  What then is this FLI
 +and how it is done ?
 +In the normal multicolor mode can one character position (4x8 pixels) have
 +only four different colors and one of them is the common background color.
 +Color codes are stored in half bytes (nybbles) to the video matrix memory
 +(anywhere video matrix pointer points at, normally $0400) and to the color
 +memory ($D800-$DBFF). In multicolor mode the color of each pixel is
 +determined by two bits in the graphics memory. Bit pair 11 will refer to
 +color memory, background color is the color for bit pair 00, and video
 +matrix will define the colors for bit pairs 01 and 10.
 +_What happens in the VIC ?_
 +VIC (Video Interface Controller) fetches color information from memory on
 +each bad line. This will steal time from processor, because VIC needs to use
 +processor's bus cycles. Bad line is a curse in the C64 world. Fortunately
 +VIC's data bus is 12 bits wide and so the color data fetch for each character
 +position will take only one bus cycle. Color memory is physically wired to
 +the VIC databus lines D8-D11.
 +How does VIC know where to fetch the graphical information ? Some of you know
 +the mystical formulas needed to mess with the pixels in the hires screen.
 +How are these functions obtained ? Are they just magic ? No, there are some
 +internal counters in VIC. They always point to the right place in graphics
 +memory and the address is determined like this:
 +A13  A12 A11 A10 A9  A8  A7  A6  A5  A4  A3  A2  A1  A0
 +CB13 VC9 VC8 VC7 VC6 VC5 VC4 VC3 VC2 VC1 VC0 RC2 RC1 RC0
 +Address bits A15 and A14 change according to the selected video bank.
 +Address bit A13 is CB13, which may be found in VIC register $18. It
 +selects the right side of the video bank to be the bitmap memory. With
 +these bits you can set the bitmap to eight different places in memory.
 +However, some of them are useless because of the character ROM images and
 +zero page/stack. Rest of the bits come from the internal counters.
 +VC9-VC0 (Video Counter) forms the address bits 12-3. The counter rolls
 +through all 1000 character positions, 0-39 on the first eight lines, 40-79
 +on the second eight lines and so on. The lowest three bits come from the row
 +counter, RC2-RC0. This is another VIC counter and it counts the scan lines
 +from zero to seven.
 +_A software graphics mode - FLI_
 +VIC will systematically go through every byte in the bitmap memory, but how
 +does it know where and when to get the color information ? This is where
 +the main principle of FLI (Flexible Line Interpretation) lies. Color data
 +is fetched (and this means it is a bad line), when the line counter matches
 +with the vertical scroll register. VC9-VC0 defines where the color data is
 +inside the video matrix and color memory.
 +If we change the vertical scroll register, we can fool VIC to think that
 +every line is a bad line, so it will fetch the color information on every
 +line too. Because VIC will fetch the colors continuously, we can get
 +independent colors on each scan line. We just have to change colors and VIC
 +will handle the rest. Unfortunately the result is the loss of 40 processor
 +cycles per line (see the Missing Cycles article for more information about
 +VIC stealing cycles).
 +_Doing it in practice_
 +In practice there is no time to change color memory, but in multicolor
 +mode VIC uses video matrix for color information too. We have just enough
 +time to change the video matrix pointer, $D018. Now VIC will see a
 +different video matrix on each scan line, different block of memory. With
 +the four upper bits in the register we select one of the 16 video memories
 +in the video bank. Just remember that the register also selects the position
 +of the graphics memory (bitmap) inside the video bank.
 +Because we have to keep the bitmap in the same video bank, we only have half
 +of the bank free for video matrices. Fortunately, that's all we need to get
 +individual multicolor colors for each line and character position.
 +VIC will fetch the color data from the eight video matrices and then it will
 +roll on to the next 40 bytes. After eight lines and matrices we will select
 +the first video matrix again. (See picture 1)
 +Usually it is not necessary to use the whole screen for a FLI picture,
 +especially if you want to have a scroller or some other effects. You just
 +have to make sure that VIC is foolable in the usual way. The timing is also
 +very important, even one cycle variations in the routine entry are not
 +allowed. There is many ways to do the synchronization. One way is to use a
 +sprite, as in the previous article. (See C= Hacking, Vol. 1, Iss. 3, The
 +Demo Corner: Missing Cycles).
 +_Not much time_
 +Because a bad line will steal 40 cycles, there is only 23 cycles left on
 +each scan line. It is enough for changing the video matrix and background
 +color. There is not a moment to lose, because you must change the vertical
 +scroll register, video matrix pointer and the background color. This is why
 +you can't have sprites in front of a FLI picture.
 +With FLI we get two selectable colors for each character position and line,
 +each scan line can have it's own background color and each character position
 +still has its own character color from color memory. In theory each character
 +position could have 25 different colors, unfortunately VIC only has 16.
 +_A little feature_
 +VIC does not like it when we change the vertical scroll register ($D011),
 +and is a bit annoyed. It 'sees' code 255 (light gray) in video matrix
 +and 9 (brown) in the color memory instead of the correct values stored there.
 +Actually the color value seems to be the lower nybble of the data byte
 +currently on the data bus (accessed by the processor (LDA#=$A9)).
 +Unfortunately there is no chance to do the register change in the border
 +and thus the three leftmost character columns are a bit useless, because
 +the colors are fixed.
 +However, this doesn't mean that you can't use those three columns. FLI
 +editors may not support the fixed colors though, so it may be hard to use
 +_What to do with FLI ?_
 +Because FLI will eat up all the available processor time (no Copper :-),
 +it is not suitable for any action-games. Each FLI picture takes about 17 kB
 +of memory: not so many pictures fit on one floppy. So, the only place for FLI
 +is demos, intros, board-type games and maybe a GIF viewer..
 +Picture 1: From which matrix VIC fetches the multicolor values
 +          ___________________________________________________________
 +|      ... | Matrix0       | Matrix0       | Matrix0       |
 +|      ,  .|____3__________|____4__________|____5__________|  ...
 +|       U .| Matrix1       | Matrix1       | Matrix1       |
 +|       s .|____3__________|____4__________|____5__________| .
 +|Char    | Matrix2       | Matrix2       | Matrix2       | .
 +|Line    |____3__________|____4__________|____5__________| .
 +|Zero    | Matrix3       | Matrix3       | Matrix3       |
 +|        |____3__________|____4__________|____5__________|
 +|       s, | Matrix4       | Matrix4       | Matrix4       |
 +|          |____3__________|____4__________|____5__________|
 +|        | Matrix5       | Matrix5       | Matrix5       |
 +|        |____3__________|____4__________|____5__________|
 +|        | Matrix6       | Matrix6       | Matrix6       |
 +|        |____3__________|____4__________|____5__________|
 +|        | Matrix7       | Matrix7       | Matrix7       |
 +|_      n  |____3__________|____4__________|____5__________|
 +        s  | Matrix0       | Matrix0       | Matrix0       |
 +           |___43__________|___44__________|___45__________|
 +           | Matrix1       | Matrix1       | Matrix1       |
 +           |___43__________|___44__________|___45__________|
 +           |
 +             ...
 +           | .
 +           | .
 +           | .
 +_Additional reading_
 +If you have an Amiga you might want to get your hands into my conversion
 +programs in C64GFX1.lha. The packet also includes FLI viewer for PAL C64's
 +and some documentation about the FLI file format. It also has the same
 +utilities for Koala format pictures.
 +Available from:
 +                        C64Gfx1.0
 +           A C64 graphics format conversion package
 +           )1991,1992  Pasi 'Albert' Ojala
 +           E-mail:
 +This package contains programs which are used to convert portable
 +pixmap (ppm) files to C64 graphics formats (FLI and koala) under
 +AmigaOS. The package includes C source codes for the programs, so
 +it is possible to port the programs to another environment. C64GFX1.1
 +includes Unix-compilable sources.
 +In addition to this package you need e.g. PBMPlus to convert Amiga
 +ilbm files to ppm first. And of course some way to transfer files
 +between the machines.
magazines/chacking4.txt ยท Last modified: 2015-04-17 04:34 (external edit)