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.
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 (C:$0391)
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
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 (C:$0393)
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 (C:$1043)
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 (C:$038d)
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.
Breakdown
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 | Address | Instruction | Jump |
Initial Loader | $1136 | RTS – $60 | $1100 |
Part 2 | $1040 | RTS – $60 | $1010 |
Infinite Lives | $123 | ORA – $05 | |
Infinite Air | $118F | ORA – $05 | |
Start Game | $361 | $361 |
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
.start
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
;
.loader
LDX #$01 ; Set LBS to 1 - Cassette
LDY #$FF
JSR $FFBA ; initialise the LBS
LDA #$00 ; Set length of Program Filename
JSR $FFBD ; Set Filename length
LDX #$FF
LDY #$FF
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
LDY #$FF
JSR $FFBA
LDA #$00
JSR $FFBD
LDX #$FF
LDY #$FF
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 .0247 (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.
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: https://github.com/muckypaws/RetroCheats
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.
Donate