Briley Witch RPG Diary – 29/10/2017
A while ago I came across some info about cartridges on the C64. Now, as my game was disk based, I had dismissed the idea, thinking cartridges were tiny, like 8K or 16K. Somehow I hadn’t come across the info about cartridges much bigger. I did a bit of research and came across info about cartridges being as large as 512K! Wow! Now that’s much bigger than a (170K) disk! So after a bit of agonising deliberation, I decided to research further…
Cartridges For The Win!
This was not an easy decision! I weighed up the pros and cons of cart vs disk, and cart has so many, many advantages, so many I was giddy just thinking of the idea… First, VICE fully supports them, so I was fine on an emulator front. Instead of giving away a disk image, I could give away a cart image. And as I discovered, there are carts for real C64s with flash memory to hold cart images.
I’m sorry to say, the only advantage of disk was for people who could make a real floppy disk to run on real hardware. I’m still thinking of making a disk version in the future; since the code is still file-based, it can still support that (with some alterations). And there is always the possibility of using disk turbo software to speed up loading.
But with cart I have the advantage of speed, plus more storage. There are some issues, namely save-games, but I’ve got a solution for that…
My Cart System
I use a custom build system named SJASM, with my 6502 assembler built-in. One feature of my assembler is to generate code overlay files. Code overlays are files that are split off from the main code, destined to be loaded later; this is how my cutscenes are generated. All the cutscenes are assembled at once as part of the main code, with full access to all symbols. With the speed of modern PCs, I can rebuild all the code in seconds.
In the past when I sectioned up code, I had to use jump tables so that multiple code blocks had a common way to communicate with each other. A bit ugly and clunky, but it worked. My new system is much more flexible, and I just love it! My build system can generate the code overlays, and auto-compress them using PUCrunch.
Now going back to cartridges… A cartridge consists of lots of ROM, broken up into banks of either 8K or 16K (depending on the cartridge). Typically they use 8K banks. Early cartridges occupied the memory range $8000-$9FFF for an 8K cart, and optionally $A000-BFFF. The cart format I’m using has an 8K bank located at $8000. Switching banks is easy: just access the memory from $DE00 onwards to select the required bank. Bank 0 is the default, and contains the cold/warm vectors, plus an identifying code: CBM80. When the C64 detects the CBM80 code, it knows there’s a cart attached and thus calls the code pointed at by the cold-start vector.
I decided to use the cart storage like a disk drive, complete with a “directory” structure, making conversion much easier! All my files are smaller than 8K (with the exception of the code… which was a bit of a pain at first). I had to change my build tool to create a cart image, but that turned out to be relatively easy.
[image error]
SJASM, my build tool, uses a project list, a collection of files to use; each entry has a source and destination path, plus a set of flags describing the entry. The project list can hold either disk files or cart files.
For cart files I use a number for the destination filename, and this corresponds to an entry in the cart directory. I store the directory as the last 1K of the first 8K bank, a known fixed location so I can access it easily. The directory structure is simple: it just needs to know the bank number of the file, plus the start address within the bank. I also store the file length (but that’s not really needed as the PUCrunch decompressor knows the length of the original file). The decompressor also knows the destination address to load the file, so no need to store that either!
To store all the files, I add them in blocks of 8K. When a file is added to a new 8K block, the remaining list of files is searched for the largest file that will fit the remaining space. This continues until no file will fit. When this happens, the current 8K block is committed to the cart image, and the process repeated with the next 8K block until all files are added. I’ve checked the output with a hex editor, and I can say there’s not a lot of wasted space!
Dealing With Code
Now, my game uses quite a bit of code, far too much to fit into one 8K bank. I had to come up with a neat way of dividing up the code…
Turned out to be not too hard…
My cutscene system used a 2K area of RAM, but with the advantage of a cart, I could increase that to 4K, thus off-loading some of the code from main RAM and into a cutscene overlay file. With some tweaks, I had my code size down to 17K. Still too much for two 8K banks, let alone one! But with file compression, I knew I could get the code size down when stored on the cart. With that in mind I decided to split the code into two halves: one half I designated as Engine Code, the other half Game Code. I could load my engine code at $0800, the game code at $3000. That gives me 10K for the engine, 12K for the game code. Compressed, the two halves easily fit within 8K. I used my assembler’s code overlay function to split out the game-side code into a separate code overlay.
Okay, so I had two 8K banks for the code. To load everything, I created a small program I call a bootstrap, sitting at the start of ROM bank 0. The bootstrap copies itself up to $C000, where it can run the code. Once run, the code selects the bank containing the Engine Code and decompresses it to $0800 before running the code. The Engine Code then loads and decompresses the Game Code before proceeding to run the game.
To load a file, instead of calling my “load_file” function, it now instead calls “load_rom_file”. The ROM loader takes a file number, accesses the directory in bank 0 to find the info, swaps to the specified bank, and then uses the decompressor to “load” the data into RAM. In the code I use a list of symbols that equate to the specified file number to make it more readable. E.g: ROMF_home_gfx = 20
One problem I did encounter was a weird case of data corruption; all my fonts and data were partially corrupted. Looking at a memory dump it seemed to include bits of the BASIC ROM… Seems the decompressor didn’t like decompressing the data through the ROM and into the RAM underlying the BASIC ROM.
The solution turned out to be easy: switch in the ROM only when the decompressor needs to read data, then switch it out again before storing the data. That fixed it 100% (to my absolute delight and relief).
Save Games
As for save games… I modified my build system so it could build for both disk and cart. When the emulator is run, it checks to see if a cart image exists and uses that; if it also finds a disk image (one used for save games), it auto-attaches that was well, so I can use the disk image for save games. Right now I’m only using one save file, but I’ll soon be allowing more; it’ll help with testing to have multiple save files at different places.
And that’s about it! I now have a system for building the game for cartridge. And the results? Well, everything loads so much faster!
Now to get back to coding more of the game…


