Skip to content

MegaVault Hack – Part 2 – Creating a Loader

Welcome back to part two of the VIC20 hacking tutorial, if you haven’t read Part 1, click on the link below.

To recap, in Part 1 – Back to Basics we learned how to hack a VIC-20 game to find infinite lives and air, and for the 20’s most will stop there, after all we’re unlikely to use an original cassette and hardware, preferring the convenience of emulation.

Back in the 80’s creating cheat loaders for games with some limited form of protection wasn’t straight forward, especially if you couldn’t afford monitoring cartridges and other tools. Nope, I had to do this the hard way PEEKing and POKEing memory using look up tables. I’ll spare you the pain, it’s 2022 and we’ll continue to use the emulator VICE to understand the loading mechanism and how to circumvent the protection the game authors designed to thwart us!

Don’t want to read the article? then follow along with this handy video, pay attention as we’ve included mistakes too! See if you’re paying attention.

Getting Started

Start up VICE for the VIC-20, and attach the MegaVault cassette, follow the instructions below to get the games loader program into memory.

Click Tape -> Attach Image
Now select the MegaVault TAP File, Click Attach/Load (DO NOT AUTOSTART)
Type LOAD”” and then press Play on Tape

The screen will clear, the loader code will be in memory, if you type LIST you’ll see the following:-

Loading the Game Loader Code

Although 24 blocks loaded into memory, there’s only one BASIC line of code. SYS4352, this was common practice, game code would be written in Assembler Language typically using a BASIC STUB to start the code. On an unexpanded VIC-20, BASIC loads at Address $1000, we can verify this by going to the monitor and looking at the contents of memory.

(C:$e5ec) m $1000 $10FF
>C:1000  00 0b 10 0a  00 9e 34 33   ......43
>C:1008  35 32 00 00  00 49 90 a9   52...I.�
>C:1010  68 8d 0f 90  20 00 40 4c   h... .@L
>C:1018  14 20 00 ff  00 ff 02 ff   . .�.�.�

For background, VIC-20 BASIC is tokenised to preserve memory, a 00 Byte is usually an end of line marker, double zero is end of BASIC program. We can see the bytes $00 $0B $10 $0A $00 $9E $34 $33 $35 $32 $00 $00

Discard the first $00 as that’s end of line, the next two bytes $0B $10 represents the memory address of $100B (Remember Low Byte, High Byte – Little Endian Notation). This is the address of the next line number after the current line.

$0A $00 is the line number $000A or 10 Decimal.

$9E is the tokenised version of SYS command, followed by the ASCII Characters of the address to call 4352 which happens to be $1100 in hex.

Additionally for background, we locate the Tape Buffer Address at $B2/$B3, which is typically points to address $033C

(C:$1060) m $b2 $b3
>C:00b2  3c 03                      <.

The tape buffer is 192 bytes, and as we’ll see in this example the buffer was manipulated to contain “Extra Code”, more later.

Let’s take a look at the code from $1100

(C:$00b4) d $1100
.C:1100  A9 80       LDA #$80
.C:1102  8D 02 90    STA $9002
.C:1105  A9 FD       LDA #$FD
.C:1107  8D 05 90    STA $9005
.C:110a  A9 20       LDA #$20
.C:110c  8D 0E 90    STA $900E
.C:110f  A9 68       LDA #$68
.C:1111  8D 0F 90    STA $900F
.C:1114  A2 00       LDX #$00
.C:1116  BD 00 13    LDA $1300,X
.C:1119  29 0F       AND #$0F
.C:111b  9D 00 97    STA $9700,X
.C:111e  BD 00 13    LDA $1300,X
.C:1121  4A          LSR A
.C:1122  4A          LSR A
.C:1123  4A          LSR A
.C:1124  4A          LSR A
.C:1125  9D 00 96    STA $9600,X
.C:1128  BD 00 1C    LDA $1C00,X
.C:112b  9D 00 1E    STA $1E00,X
.C:112e  E8          INX
.C:112f  D0 E5       BNE $1116
.C:1131  A9 96       LDA #$96
.C:1133  8D 02 90    STA $9002
.C:1136  4C 7A 03    JMP $037A
.C:1139  8D 02 90    STA $9002
.C:113c  4C 7A 03    JMP $037A

We see some initialisation code related to screen parameters, followed by a loop to set up the UDGs (User Defined Graphics), Copy data to the screen to show the splash screen, before finally jumping execution to address $37A. Note the two JMP instructions? Yep caught me out on the video, we need to patch $1136 when creating our loader with an RTS instruction.

Wait? What? Jump to $37A? I thought you said the game code loaded at address $1000?

You’re absolutely right, remember what I said about the 192 byte Tape Header? Turns out the game authors manipulated the header to contain additional Assembly language instructions to continue loading the game, let’s take a look!

(C:$116c) d $37a $390
.C:037a  A2 01       LDX #$01
.C:037c  A0 FF       LDY #$FF
.C:037e  20 BA FF    JSR $FFBA
.C:0381  A9 00       LDA #$00
.C:0383  20 BD FF    JSR $FFBD
.C:0386  A2 FF       LDX #$FF
.C:0388  A0 FF       LDY #$FF
.C:038a  20 D5 FF    JSR $FFD5
.C:038d  00          BRK
.C:038e  20 20 20    JSR $2020

There is indeed some loader code, buried in the cassette header which usually contains information like load address, filename etc. The First Byte $03, States the code is not relocate-able, the next two bytes are the load address, followed by a $00, then the filename which includes control characters to clear the screen. The section highlighted in red is additional loader code “hidden” in the header.

(C:$0391) M $33C
>C:033C  03 00 10 00  20 93 4D 45   .... .ME
>C:0344  47 41 2D 56  41 55 4C 54   GA-VAULT
>C:034C  20 20 20 11  20 20 20 20      .    
>C:0354  7B D1 D1 D1  D1 D1 D1 D1   {�������
>C:035C  D1 D1 D1 D1  D1 D1 D1 D1   ��������
>C:0364  D1 D1 D1 D1  D1 D1 D1 D1   ��������
>C:036C  D1 D1 D1 D1  D1 D1 D1 D1   ��������
>C:0374  D1 D1 D1 D1  D1 D1 A2 01   �����Ѣ.
>C:037C  A0 FF 20 BA  FF A9 00 20   �� ���. 
>C:0384  BD FF A2 FF  A0 FF 20 D5   ������ �
>C:038C  FF 00 20 20  20 20 20 20   �.      
>C:0394  20 20 20 20  20 20 20 20           
>C:039C  20 20 20 20  20 20 20 20           
>C:03A4  20 20 20 20  20 20 20 20           
>C:03AC  20 20 20 20  20 20 20 20           
>C:03B4  20 20 20 20  20 20 20 20           
>C:03BC  20 20 20 20  20 20 20 20           
>C:03C4  20 20 20 20  20 20 20 20           
>C:03CC  20 20 20 20  20 20 20 20           
>C:03D4  20 20 20 20  20 20 20 20           
>C:03DC  20 20 20 20  20 20 20 20           
>C:03E4  20 20 20 20  20 20 20 20    

The calls to $FFBA set up the Logical Block (Cassette), $FFBD sets the Filename Length, A Zero means the next program on tape, before $FFD5 is called to load the program. Notice something odd? The BRK instruction. How can the game continue to load if we’re asking the processor to BRK?

We’ll set a breakpoint at address $38D using the break command, then exit the monitor to resume the emulator and type RUN on the VIC20

(C:$0391) break $38d
BREAK: 1  C:$038d  (Stop on exec)
(C:$0391) X
Now run the program, we’ll go back into the monitor when the breakpoint has been hit.
Spot something Odd?

The game code has shown the splash screen and halted execution at our break point, however the instruction has mysteriously changed from BRK to JMP $1010.

(C:$038d) d $37a $390
.C:037a  A2 01       LDX #$01
.C:037c  A0 FF       LDY #$FF
.C:037e  20 BA FF    JSR $FFBA
.C:0381  A9 00       LDA #$00
.C:0383  20 BD FF    JSR $FFBD
.C:0386  A2 FF       LDX #$FF
.C:0388  A0 FF       LDY #$FF
.C:038a  20 D5 FF    JSR $FFD5
.C:038d  4C 10 10    JMP $1010
.C:0390  20 20 20    JSR $2020

The authors messing with the Tape Header again, injecting new code that overwrote the original, we have a new JMP address too, at $1010 We’ll take a look.

(C:$0393) d $1010 $1040
.C:1010  A9 02       LDA #$02
.C:1012  8D 1E 91    STA $911E
.C:1015  A9 FF       LDA #$FF
.C:1017  8D 05 90    STA $9005
.C:101a  A9 07       LDA #$07
.C:101c  8D F7 93    STA $93F7
.C:101f  A2 07       LDX #$07
.C:1021  A9 00       LDA #$00
.C:1023  9D 00 1D    STA $1D00,X
.C:1026  CA          DEX
.C:1027  10 FA       BPL $1023
.C:1029  A9 FF       LDA #$FF
.C:102b  8D FE 8F    STA $8FFE
.C:102e  A9 6F       LDA #$6F
.C:1030  8D 0E 90    STA $900E
.C:1033  A9 2C       LDA #$2C
.C:1035  8D 0F 90    STA $900F
.C:1038  20 70 13    JSR $1370
.C:103b  A9 00       LDA #$00
.C:103d  8D 5C 96    STA $965C
.C:1040  4C 7A 03    JMP $037A

Here we can see further initialisation code before JMPing back into the loader routine at $37A. We’ll need to patch address $1040 with an RTS instruction with our custom loader.

We’ll leave the breakpoint at play again at $38D and continue execution to complete the loading of the game,

(C:$1043) x
#1 (Stop on  exec 038d)  141/$08d,   8/$08
.C:038d  4C 61 03    JMP $0361      - A:00 X:00 Y:20 SP:f6 ..-.....  757364747

This time we see the JMP instruction has changed again, weirdly into the Tape Header Address Buffer Area,

(C:$038d) d $361
.C:0361  A9 00       LDA #$00
.C:0363  85 02       STA $02
.C:0365  85 03       STA $03
.C:0367  8D 1C 91    STA $911C
.C:036a  A9 02       LDA #$02
.C:036c  8D 20 91    STA $9120
.C:036f  A9 CA       LDA #$CA
.C:0371  8D 0F 90    STA $900F
.C:0374  20 8C 16    JSR $168C
.C:0377  20 35 17    JSR $1735
.C:037a  A5 C5       LDA $C5
.C:037c  C9 40       CMP #$40
.C:037e  F0 FA       BEQ $037A
.C:0380  A9 5F       LDA #$5F
.C:0382  8D 0E 90    STA $900E
.C:0385  A9 28       LDA #$28
.C:0387  8D F7 93    STA $93F7
.C:038a  4C 6F 18    JMP $186F
.C:038d  4C 61 03    JMP $0361

On closer inspection, the loader code has been completely replaced with new initialisation code before finally launching the game at Address $186F.


We now have a better understanding of the Games Loader, a BASIC STUB to Call Assembler Language which Jumps into hidden code provided by the Cassette Header, the Cassette Header code is overwritten twice, the final time being completely new code, which makes patching the loader an impossible task as it stands, since what ever code we try to inject into the game loader, will be overwritten by the contents of the tape header.

Could we change the location of the Tape Header Buffer? Great question, except we don’t have any spare memory to relocate this data block too.

The game utilises all areas of the unexpanded VIC-20 Memory too, there’s just no spare bytes to add our intercept code.

My initial thought was to use the lower stack area in Page 1: $100-$1FF. That was thwarted as the developer loaded additional code into this location too. Remember the POKE for infinite Lives? That’s at address $123. How did that code get there you ask…

When the second program loads from cassette, one of the initialisation routines copies code from main memory to the STACK area.

After some head scratching, the only place available was to use the BASIC workspace at Page 2: $0200-$02FF, the first 10 bytes are used for floating point conversions, and given we’re writing a BASIC program to create the cheat as we would back in the 80’s it was a gamble that paid off.

How to create the Loader?

We know the following :-

Description AddressInstructionJump
Initial Loader$1136RTS – $60$1100
Part 2$1040RTS – $60$1010
Infinite Lives$123ORA – $05
Infinite Air$118FORA – $05
Start Game$361$361
Key Memory Addresses Discovered During our Reconnaissance

Sadly the version of VICE I’m using doesn’t support in-line labels at Assembly time, I’ll provide an annotated version of code and the version you’ll need to use in the monitor program.

We need to create our own loader code since we’re unable to re-use existing code as the author created self-modifying code.

; Custom Loader for MegaVault - Jason Brooks 2022
    A $210
	JSR loader		; Load the initial game code (BASIC Stub)
	LDA #$60		; Set A to the RTS OP-Code
	STA $1136		; POKE $1136,$60
	JSR $1100		; Call the Game Loader initialisation Code 
	JSR loader		; Splash Screen Displayed, Load the next part
	LDA #$60		; Set A to the RTS Op-Code
	STA $1040		; POKE $1040,$60 - Return Control To Me!
	JSR $1010		; Call the Games Second initialisation routines
	JSR loader		; Load the final part of the Game Code.
	LDA #$05		; Set A = Op Code ORA $
	STA $123		; POKE $123,$05
	STA $118F		; POKE $118F,$05
	JMP $361		; Start the Game
; Reusable Loader Stub to load the next program from cassette
	LDX #$01		; Set LBS to 1 - Cassette
	JSR $FFBA		; initialise the LBS	
	LDA #$00		; Set length of Program Filename
	JSR $FFBD		; Set Filename length
	JMP $FFD5		; Load the program at the address specified in the header

The code you can copy paste into the VICE Monitor is :-

    A $210
	JSR $234
	LDA #$60
	STA $1136
	JSR $1100
	JSR $234
	LDA #$60
	STA $1040
	JSR $1010
	JSR $234
	LDA #$05
	STA $123
	STA $118F
	JMP $361
	LDX #$01
	LDA #$00
	JMP $FFD5		
(C:$eb0d) A $210
.0210  JSR $234
.0213  LDA #$60
.0215  STA $1136
.0218  JSR $1100
.021b  JSR $234
.021e  LDA #$60
.0220  STA $1040
.0223  JSR $1010
.0226  JSR $234
.0229  LDA #$05
.022b  STA $123
.022e  STA $118F
.0231  JMP $361
.0234  LDX #$01
.0236  LDY #$FF
.0238  JSR $FFBA
.023b  LDA #$00
.023d  JSR $FFBD
.0240  LDX #$FF
.0242  LDY #$FF
.0244  JMP $FFD5
(C:$0247) X

To test the loader code, type SYS528 at the BASIC Prompt and press play on tape, you’ll find the game loads and the cheats enabled.

Finalising the Cheat for a Type In.

We’re not expecting a user to type this in every-time, we’ll create a basic program to poke the bytes into memory that can be saved to tape and re-used time and again.

Easiest way to achieve this is a memory dump of the program we created

(C:$0261) m D $210 $246
>C:0210  032 052 002 169    4.�
>C:0214  096 141 054 017   `.6.
>C:0218  032 000 017 032    .. 
>C:021c  052 002 169 096   4.�`
>C:0220  141 064 016 032   .@. 
>C:0224  016 016 032 052   .. 4
>C:0228  002 169 005 141   .�..
>C:022c  035 001 141 143   #...
>C:0230  017 076 097 003   .La.
>C:0234  162 001 160 255   �.��
>C:0238  032 186 255 169    ���
>C:023c  000 032 189 255   . ��
>C:0240  162 255 160 255   ����
>C:0244  076 213 255       L��

Notice the extra D command? This forces the dump command to use decimal in it’s output. We can simply scrape this data and create a BASIC Stub loader.

10 rem megavault cheat
20 rem jason brooks 2022
30 rem infinite lives
40 rem infinite air
50 restore : for i=528 to 582
60 read p : poke i,p : next
65 print chr$(147),chr$(19)
70 print "insert megavault tape"
80 print "   and press play"
90 sys528
100 data 32,52,2,169
110 data 96,141,54,17
120 data 32,0,17,32
130 data 52,2,169,96
140 data 141,64,16,32
150 data 16,16,32,52
160 data 2,169,5,141
170 data 35,1,141,143
180 data 17,76,097,3
190 data 162,1,160,255
200 data 32,186,255,169
210 data 0,32,189,255
220 data 162,255,160,255
230 data 76,213,255

You’re wondering why everything is lowercase for the basic program? The VIC20 lowercase characters show as UPPER case when typed (It’s a commodore thing). Thankfully VICE enables you to copy and paste the text directly into BASIC without the need to type it all by hand.

Finally saving to tape and there you have it. An old school cheat that works on original hardware and original cassette tape.

How easy was that?

If you see strange symbols/characters on the screen, this is because the text pasted was in UPPER Case ASCII. Although it shows Upper Case on the VIC-20 you must paste the code as all lower case.

You can now SAVE the program to Tape.

As time permits, I’ll add cheats I’ve found for Retro Games on my GitHub page here:

I hope you enjoyed following this article, if there are errors or better ways to achieve the cheats why not drop a line in the comments below?

Like this Blog? It’s fuelled by Caffeine, lots and lots of Caffeine. For the price of Costa/Starbucks you’ll help a starving developer get through the next hour.

How many Coffee’s would you like to donate?


Or enter a custom amount


Your contribution is appreciated.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: