Fixing the Rambo First Blood Part II C64 tape loader
The game Rambo First Blood part II for the Commodore 64 was released with an Ocean type tape loader that featured an amazing Martin Galway tune, but this particular tape loader has a tendency to fail on some systems. In this article the problem is investigated and fixed, and full instructions are provided on how to make a fixed TAP file.
Table of contents:
- The issue
- Failed investigations
- The real explanation
- The fix
- Making a fixed TAP file
When the loader works, the process of loading the game is as follows:
- The standard Kernal loader finds "RAMBO" and loads the fast loader code and other data.
- The screen starts flashing with very thin lines for about 6 seconds
- The screen stops flashing and keeps the border and background colors, for about 18 seconds
- The border turns black, the background yellow, music starts playing and the loader picture starts to be drawn
- After a few minutes the tune stops while the picture is still visible
- A little later the screen starts to flash again with thin lines for about 30 seconds
- The game starts
When the loader doesn't work, the loader stays at step 2 and keeps flashing the screen forever with slightly thicker lines.
On my setup with a PAL C64 breadbox and a Tapuino with software v2.10.0 the loader would never work after a fresh start of the C64. I tried TAP files from many different sources with the same result. But if I started the computer with a Super Snapshot cartridge enabled (using a Kong Fu Flash cartridge), it would typically work. But not always. Very strange. The TAP files all worked fine in the Vice C64 emulator.
My first suspicion was that there might be differences in the timing of the tape pulses generated by the Tapuino compared to a real datasette. I clearly remember being able to load the game reliably on the exact same C64 using a real datasette back in the day, so why wouldn't it work now with the Tapuino?
To investigate if there was a marginal timing issue I made a couple of TAP files with the length of all the pulses scaled up or down, by various amounts, to speed up or slow down the entire thing. (This was done with a tool I developed: c64taptool). To my surprise it didn't change a thing. They all crashed in the exact same way or didn't work at all if the timing was too much off.
The Tapuino doesn't seem to emulate the inertia when starting and stopping the tape motor, so potentially the tape could be a little bit ahead after the "LOADING RAMBO" prompt when the motor starts. To investigate this theory I tried inserting various pauses in the TAP file. It didn't seem to change anything.
I also suspected it could be an issue with the Tapuino device not being able to keep up and make an uninterrupted stream of pulses. Perhaps my particular SD-card was extra slow or had issues. To try to remedy such an issue I modified the Tapuino code to use a larger data buffer and I made optimizations in the pulse generation code to improve performance. The optimization was mainly to change certain floating point operations to fixed point integer math, as floating point is exceptionally slow on an Atmel AVR that's used in the Tapuino. But all of that made no difference. The loader kept crashing the exact same way.
The real explanation
Getting more and more weirded out by this mystery and simultaneously more excited to solve it, I dived into the topic of debugging the loader code as it's running on the C64, with a cartridge. To my great luck Robin of the 8-bit show and tell channel had just released a video on how to crack a tape loader using the Super Snapshot cartridge, which explained a lot about how these loaders work and provided some techniques on how to analyze them.
Armed with this information I figured out what was going on in the beginning of the loading process and to make a dump of the loader code in a situation where it crashed, and in a situation where it worked. There was an obvious difference:
Memory dump and disassembly of a part of the loader when it works (left) and fails (right)
Notice the screen code READY. characters at the right, at address $41B8. In the disassembly it's obvious that machine code has been messed up by those characters. This is what makes the loader fail!
To understand how this could happen, a bit of an explanation is required. In the first step of the load process the Kernal loader loads various data including the fast loader code into memory such that the loader code actually ends up in the screen memory: $400 before it's moved to its intended location: $4000. But before that happens, the Kernal manages to print "READY." on the screen, messing up the code.
And to explain that, we must look at the method applied by the loader to take over code execution as the Kernal load finishes. It's done by overwriting certain function addresses in the area starting at location $0316 with the wanted address of execution: $0363.
Here is a table of these addresses, how they are set by the Rambo loader, and to compare, how they are set by the Miami Vice loader, which is similar loader by Ocean that works reliably:
|Function||Location||Default value||Rambo FBPII loader||Miami Vice loader|
|Address of BRK service routine||$0316-$0317||$FE66||$0363||$0363|
|Address of non-maskable ISR||$0318-$0319||$FE47||$0363||$0363|
|Address of OPEN||$031A-$031B||$F34A||$F34A||$F34A|
|Address of CLOSE||$031C-$031D||$F291||$F291||$F291|
|Address of CHKIN||$031E-$031F||$F20E||$F20E||$F20E|
|Address of CHKOUT||$0320-$0321||$F250||$F250||$F250|
|Address of CLRCHN||$0322-$0323||$F333||$F333||$F333|
|Address of CHRIN||$0324-$0325||$F157||$0363||$0363|
|Address of CHROUT||$0326-$0327||$F1CA||$F1CA||$0363|
|Address of STOP||$0328-$0329||$F6ED||$F6ED||$F6ED|
|Address of GETIN||$032A-$032B||$F13E||$F13E||$F13E|
|Address of CLALL||$032C-$032D||$F32F||$F32F||$F32F|
The Miami Vice loader overwrites the CHROUT vector also, they missed that in the Rambo loader! And that allowed the Kernal to write "READY." on the screen.
I extracted the portion loaded by the Kernal loader into a prg file (I call this the preload) and modified the CHROUT function address from $F1CA to $0363, to resemble the Miami Vice loader. The result is here: preload-fixed.prg. Using this preload does indeed fix the issue.
Woa, the beautiful music is cut short?
It turns out the last 5 - 10 seconds of the tune is cut off at the end of the loading process ... This has always been a characteristic of the loader as far as I can tell. It seems the speed of the tape determines how much is cut. If the tape runs faster the load process finishes earlier and more of the tune is cut. If it runs slower, less is cut, indicating that the music playback speed is independent of the loading process which just stops the music at some point.
For the fixed TAP file, I decided to slow down the game data just enough that the tune can be heard to the end. With the exact tape speed Tapuino uses at least.
Making a fixed TAP file
The following is required:
- prg2tap - get it here
- c64taptool - get it here
- Hollywood_Presents_Tape_4_Side_2.tap - A TAP file containing the original RAMBO FBP-II tape loader. This file can be found at archive.org here
Note that these instruction are intended for a Linux command line.
Extract the Rambo part of the Hollywood_Presents_Tape_4_Side_2.tap file (there are two games in that file):
c64taptool -i Hollywood_Presents_Tape_4_Side_2.tap --crop-end 743072 -o HP_Rambo.tap
Extract the game data that comes after the preload:
c64taptool -i HP_Rambo.tap --crop-start 81395 -o HP_Rambo_Data.tap
Make a tap of the fixed preloader:
prg2tap -n "RAMBO" preload-fixed.prg preload-fixed.tap
To save time during the boring part of the loader, let's remove a bit from the start of the preload tap and speed it up slightly:
c64taptool -i preload-fixed.tap --crop-start 10000 --scale 0.975 -o preload-fixed-faster.tap
Slow down the game data so the load takes a little longer. This fixes the issue of the loader music being cut short:
c64taptool -i HP_Rambo_Data.tap --scale 1.03 -o HP_Rambo_DataSlower.tap
Assemble the tap files
c64taptool -i preload-fixed-faster.tap --append HP_Rambo_DataSlower.tap -o "Rambo fixed.tap"
And now you have a
Rambo fixed.tap ready to use.
I hope you enjoyed this content!
Comments powered by Talkyard