Sunday 13 August 2023

VIC20 Key-Quest Multi-Region Game Conversion

Another post from the archive of Patreon exclusive posts during the development of the Penultimate +2 Cartridge.

This is the story of how I made an NTSC only VIC20 game multiregion by only changing 1 byte.

Or rather, that was the plan.

The Plan

Key-Quest is quite a nice maze game. Things move around quite quickly and it's very playable.

That is on NTSC systems, when played on PAL, you loose some or all of the left hand column, depending on your TV / monitor.

Some games allow you to adjust the screen using arrow keys or joystick before starting the game, but there is no mechanism for that here.

This is a common issue with the VIC20, the VIC chip needs to be programmed for screen size and offset, and that is different for PAL and NTSC, hence why some NTSC games appear stuck in the top left on PAL systems, and PAL games appear stuck bottom right on NTSC systems.

This is set by two registers on the VIC, at addresses $9000 and $9001.

On a PAL system, the standard screen uses $0C and $26.

On an NTSC system, the values are $05 and $19.

The Key-Quest game is hard coded to set these values to $05 and $19, the standard NTSC settings.

This code copies values for all 16 VIC registers from a table in the cartridge ROM and writes them to the VIC chip.

Y starts at $0F, and each time around it is decremented and BPL tests to see if the value is positive signed value (i.e. bit 8 is set). It still goes around the loop for $00, then when it is decremented once more, it goes to $FF and the BPL test now fails and it moves on to the next line of code.

A quick test with the emulator, and changing the first value to 0C gave a horizontally centered game.

Everything is now visible, but it is stuck at the top of the screen. But that would be good enough I guess, I then thought I had an easy solution.

Plan A

The default values for the horizontal positioning would suit this, so I reasoned that I could change the BPL to a BNE. That would mean it would stop at $00 rather than at $FF, so it would not set register $9000, and that would retain the default value.

Fantastic, 1 byte change and it is now multi-region.

Only one slight problem with my cunning plan. This was originally a cartridge game, so the VIC isn't actually initialized with default values, so all that happens is address $9000 is left unset and the screen way off to the left.

OK. Nice idea, but it didn't work. It wasn't ideal anyway as it didn't fix the vertical centering.

(as a side note, changing the initial value of Y from $0F to $0D and adding 2 to both addresses would have done a $900F down to $9002 copy, which would have worked if $9000 and $9001 been initialised)

Plan B

The second plan involved changing two bytes.


LDA $A659,Y



Rather than reading the table of VIC settings from the Key-Quest ROM, this should now read the VIC settings table from the KERNAL ROM and program the VIC with the standard settings.

These are the values in the Key-Quest ROM.

The version in the KERNAL ROM is different depending on region. NTSC has the same values for the screen positioning.

But PAL has different values.

There are a few other differences. The two $FF bytes at $9008 and $9009 are values read from the paddle controllers, so it does not matter what they are initialised to. However $900E sets the volume and auxiliary colour, and $900F sets the screen and border colours.

The problem is $9005, the address for the video and character ROM. The standard is $C0, $1000 and $8000. However Key-Quest uses CE, video at $1000, character ROM at $1800, so that wasn't going to work.

That's the sort of screen you sometimes see during a reset operation, when the VIC addresses have been reset, but the video RAM has not yet been cleared.

Plan C

Looks like the best option at this point is going to be creating a patched PAL version of the ROM to go alongside the original NTSC one.

I changed only the first two bytes of the initialisation table in the cartridge ROM so that it would set the screen up correctly for PAL.

That all seemed to be working.

I was testing to see if any of the other settings needed changing when I noticed there was some tearing as the screen moved in an out.

This hadn't been visible before, and is caused by the redraw happening at the wrong time in the refresh cycle. Although the VIC20 does not have a vertical refresh interrupt that you can use for timing redraws, there is a frame line counter register on the VIC that can be read to see where it is currently up to in drawing the screen.

This is at $9004, and is actually the top 8 bits of a 9 bit counter, so instead of going up to 312 lines for a PAL frame, it only goes to 156 (131 on an NTSC system).

There is code that checks that, and starts the drawing after line 64 ($40) has been passed.

Once it is past line $40, it moves everything in the game window one character to the left.

Plan D

OK, I am going to have to change one more byte. A value of $50 should be suitable for a PAL system.

Having to duplicate the whole 8K ROM is a little annoying for such a small change. Three bytes in the end, horizontal and vertical centering values and the scan line test value above.

That is the version that has gone into the first official release of the Penultimate +2 Cartridge. Shipping now.

Plan E

As I come to write this up (edit: for the Patreon post back in June), I can see a way that it could be done. Maybe.

If I change the code to copy all the values from the VIC table in the KERNAL ROM, that will set the screen positioning correctly, but the other values will still need to be changed. However, there are now 16 bytes free in the cartridge ROM, is that enough space for some code to make any corrections?

This table sits directly above the code that uses it. I have recreated the source that would build that portion of the ROM.

It looks like I can change the code that calls it to call at $A659 instead of $A669, giving me 16 extra instructions to play with.

My new code just need to continue on with the

LDA #$0F

STA $10 


and it will be seamless.

So what do I need to do?

First thing, is change the video memory address location


STA $9005

OK, so that is 5 bytes. What next? Well, the values of $900E and $900F also need to be changed. That's another 5 bytes each.

That's 15 bytes of the 16 bytes available. Hmm not enough space?

Let's see if the rest will be possible anyway.

I need to change the raster line test from $40 to $50 depending on the system. That is tricky as I can't change the video in the code on the fly.

What if I change the code to compare against a zero page memory location rather than a literal value, so from

CMP #$40



Both are two byte instructions. The # means a literal value, $40, 40 in hex or 64 in decimal. The second version does not have the # symbol, and instead means read from zero page memory, address 00F0. (hands up anyone that has stared for too long at code where they have missed out the # and are reading from zero page instead of using the literal intended)

The zero page instruction takes 3 cycles as opposed to 2 for the literal, but it is in a wait loop anyway, so it should not matter.

Now all I have to do is get to $40 on an NTSC system or $50 on PAL and store it in address $F0 in the zero page (I checked, it does not appear to be used).

I can base that on the value of $9000, $05 on NTSC, $0C on PAL.

I tried a simple comparison, but it looked like it was going to take too many instructions (12).

    LDX #$40            ; start with 40

    LDA $9000           ; compare A (9000) with 05

    CMP #$05            ;

    BEQ SKIP            ; skip next if it is

    LDX #$50            ; not 05, so change to 50


    STX $F0             ; F0 contains 40 or 50

OK, I have all the code I need to fit in there, but it is 27 bytes and I have only 16 bytes free. I do not see any obvious space in the ROM, so lets see if I can optimise the code down to 16 bytes,

The copy routine at the start completed with loading the value for address $9000 and then writing it to $9000, so I can actually get rid of the

LDA $9000 

if I put the comparison code just after that.

That saved me 3 bytes.

I did some testing, and it turns out the code writes to address $900E each time, so it does not need to be initialised in this code - that saved me another 5 bytes.

Getting there, now 19 bytes to fit in the space on 16.

Ah, the code after starts with a load $0F. That is the value I need to write to $900F, so if I can combine those and save another 2 bytes.

17 bytes, and there is space for 16.

The code that checks the region and sets $F0 to $40 or $50 is 9 bytes. I wonder if I can do it with some bit manipulation in place of the comparison?

The value at $9000 will be $05 on NTSC, $0C on PAL. I wrote those out in binary, along with the values I wanted to achieve and worked out a sequence of events that would got from A to B.

I am starting with $05 or $0C, the value stored at $9000.

If I then shift that left, I get $0A or $18.


If I AND that with $10, I get $00 or $10

AND #$10

If I OR that with $40, I get $40 or $50

OR #$40

I can then store that in the location in zero page


That works out as just 7 instruction.

Ha! now down to 15 instructions, I have one spare. I will just leave a NOP there in case I need it for anything.

This is the completed patch:

Here is the disassembly of the original section:

And the modified version:

You can see the code from $A678 onwards is the same and continues on as before.

The only other changes were the code which called this code changed from

JSR $A669


JSR $A659

and the comparison as discussed previously changes from

CMP #$40



Slightly more than the 1 byte change I initially thought, but it all seems to fit in there and work nicely.

I didn't get this done in time to go into the Penultimate +2 initial release, that has the original NTSC version and the patched PAL version, however that means when that is next updates I can replace both of those with the new multi-region version and I have 8K for a new game.


Penultimate +2 Cartridge

The Penultimate +2 Cartridge, containing Key-Quest for PAL and NTSC, and many other games in stock at The Future Was 8 bit:

More info in a previous post:

See also a great video from Robin, 8 bit show and tell:


You can support me via Patreon, and get access to advance previews of posts like this and behind the scenes updates. These are often in more detail than I can fit in here. This also includes access to my Patreon only Discord server for even more regular updates.