Saturday, 17 March 2018

Commodore 128 Assembly - Part 1

All the code and resource files in this post will be available on my Github page, because it's fun to share.  It's very W.I.P. though, so I will add it later when more stable.

I want to delve more into the Commodore 128. It's such a nice system. Some of the new and advanced features are not easy to program though. So I decided to create a system, a framework if you will, of re-useable parts once I had figured out the basics.


I use Kick Assembler, Sublime Text and VICE to develop on my PC.

Using more of Kick Assemblers power was on my to-do list and this is a start. I decided to create easy to use macro's that will generate specific object code, or make it easy to insert reusable subroutines into object code.

First thing to do was to modify the default basic startup macro to use $1c01, not $0801:

.macro BasicUpstart128(address) {
    .pc = $1c01 "C128 Basic"
    .word upstartEnd  // link address
    .word 10   // line num
    .byte $9e  // sys
    .text toIntString(address)
    .byte 0
    .word 0  // empty link signals the end of the program
    .pc = $1c0e "Basic End"

Then, some handy setup macros. For example:

.macro Go80 () {
lda MODE // are we in 80 columns mode?
bmi !+ // bit 7 set? then yes
jsr SWAPPER // swap mode to 80 columns

Isn't that nice? Entering Go80() beats remembering and typing in that code. I can now also use this macro:


Which will also switch to 80 or 40 column mode. This is the macro code for that:

.macro DoEscapeCode (code) {
lda #code

And it allows easy access to the other escape codes as well.

These are one-off calls. They accept parameters and will therefore generate specific code for specific situations, mostly during setup, and they can be added into the code when required. Some code is better added as subroutines though, for example: writing to a VDC register:

.macro WriteVDC () {
!: bit VDCADR
bpl !-

This can be added as a subroutine to object code like so:


It can then be called multiple times, like this:

ldx #26 // modify register 26, colour.
lda #010011 // left nybble is BG, right is FG

This will change the background and character colour on the 80 columns display.

Memory Management (Unit)

One of the first things to tackle when using the C128 is the use of the MMU. Especially when mixing Basic and machine code.

Check out this memory map overview. We will get back to it later:

This can be quite confusing, so I created a macro which will make selecing the proper bank configuration easier, as the values as used in the BASIC Bank command can be used, and some custom configuration when I do so choose to add them:

.macro SetBank(id) {
.if(id==0) {
lda #111111 // no roms, RAM 0
.if(id==1) {
lda #%01111111 // no roms, RAM 1
.if(id==12) {
lda #000110 // int.func. ROM, Kernal, IO, RAM 0
.if(id==14) {
lda #000001 // all roms, char ROM, RAM 0
.if(id==15) {
lda #000000  // all roms, RAM 0. default.
.if(id==99) {
lda #001110  // IO, kernal, RAM0. No basic

So, adding SetBank(15) to my source code will enable the default bank configuration.

First Test

This is the code for a first test I've made. It sets up some default stuff, and it then fills the 80 column display with a character, and changes the colour of the display.

#import "c128system.asm"
#import "c128macros.asm"

:BasicUpstart128(MAIN) // I like to use : to indicate a macro call.

:SetBank(15) // set bank 15

ldx #18 // register 18 = update address hi
lda #$00
jsr WRITE_VDC // write 0
inx // register 19 = update address lo
jsr WRITE_VDC // write 0

ldy #0 // byte counter
lda #8 // page counter
sta $fa

ldx #31 // we write to the data register
// data will be passed to address defined by 18-19
lda #$66 // we write this char to screen memory
bne !- // byte loop
dec $fa
bne !- // page loop

ldx #26 // modify register 26, colour.
lda #010011 // left nybble is BG, right is FG

// assemble sub routines.


That's it for now. I will keep trying new things and post about them.  At this time, I am not sure what I want to program on the C128, but I have a few ideas... 

Till next time!

Friday, 24 November 2017

C64 Reloaded MK2, Jiffydos, and the 1541 Ultimate II+

In a Jiffy!

Using the new Reloaded MK2 motherboard for the C64 in combination with the 1541 Ultimate II+ cartridge has made it ridiculously easy to use Jiffydos without the fuss of opening up hardware and lifting chips from sockets.

Jiffydos is great. It speeds up disk operations and adds easier to use commands to work with the disk drive and floppies. Technical details can be found here, and also instructions how to use it:

We need two kernal ROM images: the one for the C64 itself, and the ROM that needs to be used in the disk drive.  These can be acquired from various places.

Let's set those ROMs to work.

Upload kernal to the Reloaded MK2

The kernal ROM needs to be uploaded to the motherboard by using the USB file transfer method.

I use Linux Mint, with Minicom.  In order for xmodem file transfers over serial to work, and not report "failure executing protocol", an Y/Z modem needs to be installed. I do not know why this doesn't work out of the box, but entering sudo apt-get install lrzsz in a terminal window did the trick for me.  I love Google.

Sending the file to the board takes a few seconds:

NOTE: After uploading, the ROMs list does not show that an image has been uploaded to the board. So make a note on which ROM you put in which slot.

Activating the Jiffydos Kernal

Selecting M and then K gets you into the kernal ROM select menu:

Memory and ROM selection
C -   Select character ROM
E -   Select function of extra image
I -   Memory initialization pattern
K -   Select kernal ROM

X -   Exit menu
Select kernal ROM
D -   Use default kernal ROM
1 ==> Use custom kernal ROM image 1
2 -   Use custom kernal ROM image 2
3 -   Use custom kernal ROM image 3

X -   Exit menu

Select option 1 to select the kernal ROM we uploaded.

Make sure to use menu "W - Write settings" to save the settings. From now on, the MK2 will start with this kernal activated.

Now, the C64 to be restarted, or the menu item "Z - Reinitialize memory and reload ROM images from flash" needs to be executed. After restarting, my C64 shows that Jiffydos is loaded:

We can make selecting the kernal ROMs a bit easier though by changing the setting in the "K - Configure Keys (RESTORE and RESET/POWER)" menu. I changed the long press of the reset button to switch between custom image 1 and the default kernal ROM.

Configure long press RESET/POWER function
1 ==> Toggle between default kernal and custom image 1
2 -   Cycle through default kernal and custom image 1, 2
3 -   Cycle through default kernal and all custom images
C -   Toggle between default and custom character ROM
R -   Reset machine
S -   Toggle between SID-1 and SID-2 (when set to mono)

After performing this action (hold the reset button for at least 5 seconds) you need to reset the machine to activate the other kernal ROM. So hold, and then short press the reset button.

ROM upload to the 1541 Ultimate II+

Next, we need to set the 1541 Ultimate II+ to use the Jiffydos kernal ROM for its drive. This is done by first locating the ROM on the USB stick and selecting it.

This will open a menu, allowing you to use it as a drive ROM:

Selecting the ROM tells you what to do next:

Lets do what we're told: lets change the drive A ROM to Custom. This is done by going into the settings menu F2 and then selecting the drive A settings option:

Press ENTER and we're done.


We can now use Jiffydos in combination with the 1541 Ultimate II+. Really nice! Fastload without taking a cartridge slot, and handy shortcut commands when using diskettes:

Thursday, 23 November 2017

C64 Reloaded MK2 review and build

Some time ago I preordered the new motherboard for the C64: the Reloaded MK2. Let's play!

Product Overview

The C64 Reloaded MK2 board comes with a nice one-sheet giving an overview of the layout, connectors and features.

A more detailed explanation of the board can be found here:

After taking the board from the plastic wrapper, you are faced with this. I liked the coloured sockets of the MK1 board, but this is a sleek look, really nice!


I gathered a few donor machines when I ordered the MK2 board, and when I got the confirmation that the board was shipped I started desoldering the required chips:

2x CIA
1x 6510
1x SID

Having a few spare boards, I acquired a small collection of these so I could play around with them when needed. And it was needed.

The board is powered by 12V DC, at 1 or 2 amp. I had a spare power supply lying about, so no problem there.

As the case I was going to use is a C64C case, I also needed a keyboard, and the required clamps on which the keyboard rests. The clamps were pulled from a spare C64C. The keyboard came from an early C64, as I like the bright function keys.

Fitting the chips

Fitting chips is easy: lift the handle, drop in the chip, and move the handle down. The notch on the chip needs to face the handle.

Start up

When I powered up the board for the first time, I got no video, and the error led on the board was flashing.  As it does this on all errors this didn't tell me much, but the board has a great feature: USB access to a debugging and configuration menu!

I attached the cable to the USB port near the tape connector and started Minicom.

NOTE: It is important to turn off hardware flow control on the serial connection settings. If this is not done (it is enabled by default) you will not be able to send input to the board.

Pressing I to see the chip information. It shows the fitted chips. If anything is not recognised by what ever reason it can be seen here.

Two of the three SID chips I had acquired turned out to be faulty (they showed up as 'unidentified', and the one I have in there now (which works) has an issue when playing samples. They are too low in volume, and are barely audible. And yes, it is a 6581.

Also, as I got my chips from a collection of donor machines, I had to play around with the mixing of the cpu and cia chips. In the end it all turned out ok.

The chipset (menu item I) information is now:

SID 2 showing up as Unknown is because there is no chip in the second socket until I can get more replacements.

This menu does not show the CIA chips, which I think is a bad omission as I had to replace these as well. Why does it not show them? Hopefully a future update will add more chipset information.

Fitting the board

A long, long time ago I bought a transparant C64C case. The idea had always been to add a new board to this case, so now was the time to finally do it!

Adding the keyboard was a bit of an issue. After adding the keyboard I noticed that the power button was touching the underside of the hole in the case, causing the power button to stay in the on position.  You can also see that the joystick ports are almost touching the bottom case holes as well:

It turned out that the left keyboard bracket needed a little adjusting, as it was touching joystick port 2:

Some bracket parts were bent to make sure that the bracket would rest correctly on the motherboard. Your mileage may vary.

After closing everything up I had a perfect fit:

So far I am really pleased. The MK2 looks great, has nice hardware features, and has options that will enable us to do things with the C64 we could not do without hardware hacks and soldering.

I'll be posting some more when I get to play with custom ROMs, and using other hardware to see what kind of neat things we can do with this motherboard.

See you next time!

Saturday, 9 September 2017

Review 1541 Ultimate II+

Recently I bought the new version of the 1541 Ultimate II cartridge. It is the best investment you can do when you enjoy your Commodore 64 and want to play around with games, programming, music playback and hacking.

The cartridge came with the 3.1 firmware installed, which is not available for download on the official site. Also included are a serial cable and a branded USB stick. Nice.

The case is a bit bigger than the first U2, which is needed to house more USB ports, audio connectors, a network port and micro USB power connector.  The case is a really nice quality, with a big logo on top and smooth finish.

The blue USB3 port is not a USB3 port in the traditional sense, but used for the Tape connector.


A lot of features have stayed the same as the Ultimate 2. For an overview of the capabilities of the earlier version of the cartridge, see my series of posts, starting here.


I was really happy to see the KCS Power Cartridge in the list of supported images. I (and many Dutch people with me) used this cartridge, because of the monitor and the floppy disk turbo load features.  I never used the freeze capabilities, as I found it cumbersome.

Another new cartridge is the GeoRAM. It was a memory expansion, usable with Geos. It is slower than the REU, and its use is limited. I'll probably never use it.

Amiga Mod Audio Playback

This is really fun, and it surprised me.

A new feature is the playback of Amiga mod files. All you need to do is load up the .mod file to your USB stick, and select "play MOD" from the context menu. You must connect the audio out connector to a amplifier though.  But it's good fun to play classic mod files on the C64. You can press the function keys to enable/disable voices.

Now I need to find some classics! Any pointers?

Network Features

It is now possible to operate the U2+ through the network. The network port is configured, by default, to use DHCP. When you connect a RJ45 cable to the cartridge, you will see the IP address you need to connect to in the root of the file system (the network is displayed the same as a USB stick).

Use a telnet client to do this, and it will look like this:

It's handy to be able to browse the cartridge file systems for a new file to play while the SID or MOD file keeps playing as you browse.

What I did find is that the ESC key (close a configuration screen for example) does the same as pressing RUN-STOP on the C64, but the screen does not reflect this. Pressing an arrow key afterwards does refresh the telnet screen.

I have not tried to FTP files to the cartridge using the network as this is REALLY slow.

Gideon mentions that the FPGA in the U2+ is not (yet, hopefully) running on full speed, and is even slower than the U2.

I did not get any further than this:

Stand Alone Mode

As the cartridge can be powered using a micro USB cable, it is now possible to run it without a C64.  Also connect it to the network and you can Telnet to it, but there is not much else you can do in stand alone mode as audio playback does not work (You need a C64 to run the module or SID playback programs)

Quality of Life stuff

It is now possible to select a folder on the file system and set it as a HOME directory. The cartridge can go to this folder on start up, and/or you can go to it by pressing the Home key. Really handy if you are tired of scrolling through the HSVC folders to your favourite musician :)

It is also possible to set the device ID that is used by DMA load. This can REALLY help with multi-load games.


Lots of new additions made this cartridge even better than the already impressive original 1541 Ultimate 2.

The hardware features promise more exciting possibilities further down the road. I really hope RR-net functionality is coming, because BBSes baby! And also using the turbo assembler cartridge with it.

If you like your C64, and you use it on a regular basis, then this cartridge is a GREAT addon to enjoy it even more. Highly recommended!!

Thursday, 25 May 2017

1541 Ultimate II Guide 6 : D71 and D81 images

When you want to use your 1571 or 1581 drive with your C64 or C128 it's nice to be able to use the hardware to the fullest. That means using double sided floppies and enormous 800k disk images!

Later 1541 Ultimate 2 (1541U2) firmware levels support reading D71 and D81 images in the cartridge interface, but it cannot mount the images as it emulates a 1541. Duh!

It would be neat though if you could use the 1541U2 cartridge to write the D71 and D81 images to physical floppies.

I've written a program to do just that:

D71 and D81 images can be written using this C64 program, which is available on CSDB:

You can find the source here:

The latest version is 1.1. This program works with 1541U2 firmware 2.6 and up.

You need to prepare some things on the 1541U2 before you can use it though:

1) Enable the REU in the 1541U2 cartridge, with at least a size of 1MB.
2) Enable the command interface in the 1541U2
3) Disable any cartridge in the 1541U2 as it can interfere with the REU functionality and the command interface
4) Format the floppy before using this program
Optional) When running the program on a C128 (in C64 mode) you need to modify the 1541U2 option CPU addr valid after PHI2 to 100ns

Place your D71 or D81 file somewhere on the USB or SD card and run the program. Follow the instructions, it is self explanatory. Writing a disk takes quite some time (11 to 20 minutes) so make sure you have a cup of coffee nearby.

Now this version is released, I am concentrating on creating a C128 native version which will use the burst commands for faster writing.

Until then, have fun!

Thursday, 2 February 2017

Tetris in 6502 Assembler - part 14

Hello there and welcome to another post in this epic series.

The end is in sight!  We are now adding the final touches to the game to make it feel better to the player and add some form of customization to the screen colors.  The player interface with the game is very important and can make or break the experience.

So now we introduce some sound fixes, input modifications and graphic options.

As usual, the code can be found on my GitHub page for you to see and play with.

Sound Fixes

The music and sound effects were played at the end of the game play loop, and because of that we were having issues with the timing of the audio. As the time required to run the game loop can change each frame the sound would slow down sometimes. Highly annoying.

So a raster interrupt was introduced to run BEFORE the game loop to avoid any interference. This setup route is called when the game starts:

lda #<irq1
sta $0314
lda #>irq1
sta $0315
lda #$1b
sta $d011
lda #$80
sta $d012
lda #$81
sta $d01a
lda #$7f
sta $dc0d
sta $dd0d

lda $dc0d
lda $dd0d
lda #$ff
sta $d019

lda #$ff
sta $d019
jmp $ea31

I stole this routine from the Kick Assembler example files, and adapted it slightly. Instead of changing the IRQ vector at $FFFE, I use the vector in $0314, as I want kernal IRQs to continue. We are depending on those for keyboard scans, etc.

The above screenshot shows the moment when the various program parts are called. The top white border color change is when the music plays and the bottom color change is when the game code runs. All screen updates are done in the game update code, as you know, while the raster bar is at the bottom of the screen so we will have no flicker in the screen buildup or strange artifacts. This was already the case.

Also, to stop sound effects from overlapping and blocking each other out, a timer was needed to introduce a delay before a new sound can be played. I added a table with the delays for each sound. Playsound can be called when a sound effect or music is required:

// set accumulator before calling this
// it will not play when the sounddelayCounter is not 0
ldx sounddelayCounter
bne !skip+
lda sounddelay,x
sta sounddelayCounter
ldx #0
ldy #0
        jsr music.init

.byte 0

//       0  1  2  3  4  5  6  7  8 9
.byte 10,10,10,35,35,25,25,10,1,1

The sounddelayCounter value is changed in the play loop in play.asm, once per frame. It counts down to zero:

lda sounddelayCounter
beq !skip+
dec sounddelayCounter

Simple as that.

Color Changer

Some people have good taste, and like the color combination I've put in. Some may want to wander from perfection and change the colors. This can now be done by pressing F3 (screen colors) and F5 (character colors)

This is achieved by scanning for these key presses in the main loop.
Changing the screen color is easy:

inc $d020
inc $d021

Changing the character color is a bit more involved: only changing the color RAM is not enough. When you print something to the screen, this is done in the current cursor color and has nothing to do with the screen RAM values. We need to change the cursor color to the correct value and we can to that by printing an ASCII code.

The ASCII table of the C64 is a bit weird, but there is a table in the Programmers' Reference Guide. These ASCII values don't line up AT ALL with the colour values that are in the color RAM, so we need a little table to map it to the color RAM values:

//            0   1 2  3   4   5  6  7   8   9   10  11  12  13  14  15
.byte 144,5,28,159,156,30,31,158,129,149,150,151,152,153,154,155

When the color RAM is changed, the appropriate CHR$ is also printed:

ldx #0
lda charColor
sta $d800,x  // store in color ram
sta $d900,x
sta $da00,x
sta $db00,x
inx  // increment counter
bne !loop-  // continue?
ldx charColor
lda chrColorCodes,x     // get correct chr$ code


Input Fixes

The keyboard input was a bit wonky (as was the joystick) so changes were needed to make it feel as it should. There is nothing worse than a game that does not react in the way you expect.  I moved the keyboard and joystick scan to the main loop and removed all scanning from the game modes. This means that the keyboard and joystick are read ONCE per frame resulting in stable results.

This helps stability of the read input as reading the keyboard buffer can only be done once each frame. Any subsequent query will result in no input detected. Not good.

This is the new keyboard scanning code:

lda keyPressed // get held key code
cmp previousKey // is it a different key than before?
bne !skip+ // yes. dont use key delay

// key is the same. update delay counter
dec keyDelayCounter
beq !skip+
sta inputResult
// restore key delay counter
stx keyDelayCounter
// save key code for next update
sta previousKey

cmp #NOKEY
bne !skip+
lda #NOINPUT // yes
sta inputResult
cmp #DOWN
bne !skip+

// if we press down, the delay is shorter
ldx #4
stx keyDelayCounter
sta inputResult // store input result

This also solves an issue when pressing DOWN. The times between the down movement were changing all the time, now it is stable. Adding a little section to have a shorter delay when pressing DOWN makes a manual drop behave like it should.

To make the time delays between inputs more stable, I also added the same structure and a separate delay counter for the joystick so there is no interference.



The hardest part of game development is finishing the project and while this series has been a long time in the making, I was determined to finish it and THEN move on to other challenges.

For me, this has been a great exploration of the Commodore 64. I hope you enjoyed it as well, and that you now have a better understanding of game development on the C64, with assembly. A lot of things in the C64 we did not even talk about, like sprites, multi-colour, raster interrupts, scrolling, border manipulation etc.  but these are all things that were not necessary for this game.  It would be great to play with that in a future project though! :)

Thanks for reading and showing an interest in this great little machine that gave (and keeps giving) so much pleasure the world over.

Happy coding and see you next time!

Saturday, 28 January 2017

Tetris in 6502 Assembler - part 13

Hi there and welcome to a new part of this series. We are going to look at hiscores!

As usual, the code can be found on github.  Warning! The code can be a bit unstable at the moment because the final bits are tweaked to get rid of the minor annoyances that still remain, and I am tweaking all kinds of stuff at this moment.

Here is a screenshot of the old Tetris code, as seen in VisAss in VICE, and the code being ported to kick assembler.

Lots of code follows! :)  I decided to not add new all code to this post as it is already a very long one. The complete files can be found on GitHub as I said. I have posted the most interesting bits here and it already is quite a lot.

Hiscore Table

As Tetris is a score attack game it is essential that progress is saved. A hiscore table is mandatory for this type of game. It's even better if it is saved and re-loaded as well. So let's get to it.

The table itself is very simple. It is a data area, and each entry has a score length of 3 bytes (as we use decimal mode for scoring, we can have a score up to 999999) and then we have a name length of 7. Why? Seven bytes fit in the window that holds the score display. Here is the table definition:

.const TABLE_ENTRIES = 3


We add an extra entry at the back of the table to ensure we have a buffer when moving data. This might not be required, but at least it guarantees we do not overwrite any code or data we put behind it.  As you might have guessed, this moving entries code might require a rewrite to ensure only valid data is moved. We'll get to that later on, I don't like loose ends. Loose ends in assembly code tend to have consequences!! :)

Initializing the table goes as follows: We go through the data area, and we add a default entry for as often as defined by TABLE_ENTRIES:

sta current_entry
ldx #0
ldy #0
lda default_table_entry,y
sta hiscore_table_start,x
bne !loop-
dec current_entry
bne start_entry_reset

.byte $00,$12,$06
.text "WDWBEST"

.byte 0

Printing the table requires quite a few steps. We are re-using the level select screen when printing the hiscore table, so when that is done, we add the table information over the data in that screen.  We only need to print the scores and the names, as the level select screen already has an empty table in it with an index.

We set the x and y registers to the correct screen location and call this routine:

// save coordinates
stx hiscore_table_position
sty hiscore_table_position+1
jsr PLOT

lda #1
sta current_entry

// reset table data offset
ldy #0

ldx #TABLE_SCORELENGTH // this amount of bytes in score
lda hiscore_table_start, y
pha // store value
lsr // shift right 4 times
adc #$30 // add $30 to get a screen code
jsr PRINT // print it
pla // retrieve original value
and #001111  // get rid of leftmost bits
adc #$30
dex // dec number counter
bne !loop-

lda #$20

// print the name

lda hiscore_table_start,y
bne !loop-
lda current_entry
beq !exit+

inc current_entry

// save memory pointer offset

// go one line down
inc hiscore_table_position

// position cursor
ldx hiscore_table_position
ldy hiscore_table_position+1
jsr PLOT

  // restore memory pointer offset
jmp print_table_entry

// x and y positions for PRINT_HISCORE_TABLE
.byte 0,0

Game Over And New Hiscore?

Looking back at the code from 23 years ago, I quite liked the approach. Instead of using sorting methods, like bubble sort, an entry comparison is done on all the bytes in each table entry.

Each byte is checked against the new score and the difference is logged in a corresponding flag. This flag is set to $ff if the new score was lower, $00 if it was the same and $01 if it was higher. Depending on the byte checked a new hiscore is detected or rejected. If a new score is detected a new entry is inserted at the current location, else the next entry is tested:

// start with no hiscore
lda #0
sta new_hiscore
// reset entries counter
lda #1
sta current_entry
  // reset table data offset
ldy #0

// reset byte compare flags for this entry
lda #0
sta compare_flags
sta compare_flags+1
sta compare_flags+2
//reset the compare flag counter
ldx #0

// check each byte in the new_score with current entry
// set compare flag accordingly
lda new_score,x
cmp hiscore_table_start,y
beq byte_compared // score is same. skip
bpl byte_is_higher // score is higher
dec compare_flags,x // score is lower
jmp byte_compared
inc compare_flags,x
iny // inc data counter
inx // inc byte counter
bne byte_compare_loop

// lets see if new_score was higher than the table entry
// this is fixed to a score length of 3 bytes !!
lda compare_flags
beq !skip+ // this byte was the same, check next
bpl found_hiscore // higher! :)
jmp no_hiscore // lower :(
lda compare_flags+1
beq !skip+ // same, check 3rd byte
bpl found_hiscore   // :)
jmp no_hiscore      // :(
lda compare_flags+2
bmi no_hiscore // last byte is lower. so no new hi
jmp found_hiscore // all 3 digits the same or last higher. new hiscore!
// new_score was lower than this entry.
// check the rest of the entries if not yet all done.
inc current_entry
ldx current_entry
beq all_entries_compared // all entries compared
// so no hiscore at all! exit

// goto start of next table entry
jsr GET_ENTRY_OFFSET    // this uses X register.
ldy entry_offset        // get offset to beginnig of next entry
jmp entry_compare // do the next entry


// hiscore found and its position is in current_entry
ldx current_entry

// add the score to the entry
ldy entry_offset
lda new_score
sta hiscore_table_start,y
lda new_score+1
sta hiscore_table_start+1,y
lda new_score+2
sta hiscore_table_start+2,y

// clear the name. add dots
lda #$2e
sta hiscore_table_start+3,y
bne !loop-

// mark that a new hiscore has been detected at this entry.
lda current_entry
sta new_hiscore

Some data manipulation is required to insert a new entry in the list:

// first we need to move the data down.
// point memory offset to end of table.
ldy #hiscore_table_end - hiscore_table_start
// move data until we're on the wanted offset.
lda hiscore_table_start - ENTRY_LENGTH,y
sta hiscore_table_start,y
cpy entry_offset
bpl !loop-   // keep going

ldy entry_offset
lda #$01    // insert some values
ldx #0
sta hiscore_table_start,y
bne !loop-

When this is all done we print the level select screen and we overwrite the level select text with a happy or sad message to indicate a new hiscore or a loser attempt:

ldy #0
ldx #17
lda hiscoremessage1,y
sta $0400+52,y
lda hiscoremessage2,y
sta $0400+52+40,y
bne !loop-

ldy #0
ldx #17
lda noHiscoremessage1,y
sta $0400+52,y
lda noHiscoremessage2,y
sta $0400+52+40,y
bne !loop-

.text " a new hiscore!! "
.text " enter your name "
.text "   too bad  :(   "
.text "    game over    "

Code to enter the name is also added, in the file controlled_input.asm. It uses the kernal routine at $FFE4 (GETIN) to scan for keyboard entry and only the accepted characters are added to the name buffer:

ldy input_len
beq !exit+ //input
sta input_buffer,y
inc input_len

lda #0

lda #1

The label input_done is jumped to when the user presses the return key. A flag is set so the main program can exit this game mode, or keep waiting for characters. The complete code for the input routes can be found in controlled_input.asm.


Load and Save

Saving and loading data on the C64 is relatively easy.  It is memory based, so you need to know which memory parts need to be written to the open device. The open device is #8 (the disk drive)

First, we need to open the device and set the file name to load:

.label SETLFS = $ffba
.label SETNAM = $ffbd
.label LOAD = $ffd5

// set logical device
lda #0
ldx #8
ldy #0

// get length of file name
lda #filename_end - str_filename
ldx #<str_filename
ldy #>str_filename

.text "filename here"
.byte 0


Then we point to the memory address we want to load the data before calling the load kernal routine:

// set memory destination and load
lda #0
ldx load_destination
ldy load_destination+1
jsr LOAD

.byte 0,0

load_destination must point to hiscore_table_start.

Don't forget to close the file afterwards. We need to do this as we might save several times during one play session.

Saving is done as loading, except we also have to set the end memory address. This is easy, as we labeled it in the source: data_start should point to hiscore_table_start and data_end should point to hiscore_table_end.

// set pointers to the memory block to save
lda #<data_start
sta startsave
lda #>data_start
sta startsave+1

// save up until to the end address
lda #<startsave
ldx #<data_end
ldy #>data_end
jsr SAVE


Adding a hiscore to a game is not trivial. It requires quite a bit of code, certainly more than I expected when I started this addition. But it gives the game an important feature and it is appreciated by everyone who plays the game, we can be sure of that.

Next time we will start adding the final touches to the game. We will be adding more robust sound features, some colour options and tweaks to try and finalize the game.

Remember: to view the full code --this post contains only excerpts-- visit my github page linked at the top of this post.

Happy coding and see you later!!

Commodore 128 Assembly - Part 1 All the code and resource files in this post will be available on my Github page, because it's fu...