How the Final Cartridge III Freezer Works

Jun 14, 2025 - 20:15
 0  0
How the Final Cartridge III Freezer Works

by Daniël Mantione

Daniël contributed the commented disassembly of the FC3 freezer functionality to the reverse engineering effort at github.com/mist64/final_cartridge. Thanks to Eric Schlaepfer for his input on 6502 timing.

Freezer cartridge theory

One key reason why the Commodore 64 was so successful in the 80s was that it was able to do things it wasn’t designed for. Freezer cartridges, which allowed stopping any running program or game, applying cheat codes and resuming, or saving the complete computer’s state to disk so it could be continued from later, were one of those clever innovations: They were possible on the Commodore 64, but not on many other computers. A Commodore 64 with a good cartridge was a significantly more capable computer than a Commodore 64 without, this did contribute to the longetivity of the computer and is one reason why the Commodore 64 could remain in production for more than a decade.

However, because the Commodore 64 wasn’t designed at all to support freezing, a cartridge has to exploit quirks in the hardware in order to achieve its functionality. The C64 feature that is essential to freezer cartridges is the so-called Ultimax mode.

The Commodore Ultimax or Max machine was a games console that uses the same custom chips as the Commodore 64: It has a VIC-II and SID. The Ultimax had 2 KB of RAM and no ROM. Instead, the ROM on the cartridge is mapped into memory. The Ultimax was a commercial flop: Commodore only sold it in Japan, and it wasn’t a success there either. However, it somehow happened that the Commodore 64 was designed to be compatible with game cartridges developed for the Ultimax.

If an Ultimax cartridge is inserted, the Commodore 64 disables most of its RAM. 4 KB of RAM remains active and the C64 maps the cartridge ROM into memory, just like the Ultimax. This ROM is mapped from $E000..$FFFF, the place in memory where you will normally find the KERNAL.

Ultimax mode is activated when the cartridge pulls the GAME pin on the cartridge port low. It mode can be activated and deactivated in real-time. This means that if a cartridge activates Ultimax mode, it forces cartridge ROM memory into the C64’s memory map. Software on the C64 cannot prevent this from happening.

The ROM is mapped into the KERNAL memory region, and this means that the 6502 interrupt vectors at the end of memory will be read from cartridge ROM. A freezer cartridge will use an NMI interrupt to take control of the C64: Software cannot prevent an NMI interrupt from happening. So by combining NMI and Ultimax, a freezer cartridge can make the C64 execute code from cartridge ROM when the freeze button is pressed.

Doing the freeze correctly

In order to successfully freeze the Commodore 64, there is still a challenge that needs to be solved: Once the 6510 CPU receives an NMI interrupt signal, it will complete the current instruction before starting handling the interrupt. Because the C64 deactivates most of its RAM memory in Ultimax mode, if a cartridge immediately activates Ultimax mode as soon as the freeze button is pressed, the instruction may read or write from/to deactivated memory. This could prevent successful unfreezing.

On the Final Cartridge III, the freeze button controls both the NMI and GAME lines. If you press the freeze button, NMI is pulled down immediately, however. For GAME, there is a counter on the cartridge. This counter waits 7 cycles before GAME is pulled down. Both lines are released as soon as you release the button.

This is what the 6510 processor does when an NMI happens:

  • interrupt propagation cycle
  • interrupt propagation cycle
  • complete the current instruction (0-6 cycles)
  • internal cycle
  • internal cycle
  • store PC(hi) to the stack (1 cycle)
  • store PC(lo) to the stack (1 cycle)
  • store P to the stack (1 cycle)
  • fetch PC(lo) from $FFFA (NMI vector lo)
  • fetch PC(hi) from $FFFB (NMI vector hi)

Because the stack at $0100 remains visible in Ultimax mode, there some flexibility in which clock cycle Ultimax mode is activated. This flexibility period is 5 cycles, after which the NMI vector is read.

This means that the freeze process of the Final Cartridge III is not fully reliable: 6502 instructions can take up to 7 cycles. If a 7 cycle instruction is started during the propagation cycles, Ultimax mode is activated before the current instruction has finished. Because 7 cycles instructions relatively rare, the freezer function appears to be reliable, but if you have bad luck it fails.

This is shown in the following photo. The C64 was connected to a HP 1650A logic analyzer, which was set to trigger on NMI. We can see in each cycle what happens. The C64’s memory was filled with $FE, which results in the instruction INC $FEFE,x, which is a 7 cycle instruction. It was excuting this in an endless loop. Then after trying a few times, I got this:

We can cleary see that Ultimax mode is activated before the INC instruction has finished, so the two memory writes are written to memory that is disabled, and thus the writes have no effect.

Another issue with the freeze circuit is the risk of button bounces. When a button is pressed, there is often some noise on the line for a few tens of microseconds. While the hardware does use Schmitt-triggers on the button, there is no proper debounce circuit. This means that there is a risk of multiple NMI interrupts or Ultimax mode flipping on and off before the firmware pulls them low via the register.

(I have addressed these flaws in my Final Cartridge III 101% hardware. Once a digital low is received, a flip flop is set, indicating freeze is active. Instead of counting cycles, the FC3 101% waits for 3 consecutive writes before it pulls down GAME. (The 6510 writes flags and PC to the stack during these cycles.) NMI and GAME are not released by releasing the button, but the hardware waits for a write to the FC3 register before resetting the freeze flip-flop. This way the FC3 101% can guarantee that NMI and GAME will not bounce and GAME is always pulled down in the correct cycle.)

NMI and GAME can also be pulled down through a register on the Final Cartridge III. One of the first things the NMI handler in the FC3 firmware does, is to pull down NMI and GAME through the register. This means NMI and GAME remain low as long as the C64 is frozen.

Initializing the freezer

After the Final Cartridge III has taken control of the C64, the computer is in an unknown state and all of the memory of the C64 might be in use, therefore the cartridge cannot touch any memory. The cartridge takes some freedom, it assumes there is some free space on the stack available, but otherwise has to operate without using any memory.

Displaying the freezer menu and doing the actual freezer functionality is going to require memory, so something needs to be done, since unlike the Action Replay, the Final Cartridge III does not contain any additional RAM.

The freezer uses two slices of memory: The first slice is 103 bytes in size and is used for register backups and temporary variables, the second has a size of 87 bytes and contains the unfreeze routine. Once the freezer has taken control of the C64 and saved the most essential state on the stack, it starts to scan the C64’s memory for memory that can be RLE compressed, i.e. it looks for 103 and 87 bytes of continuous memory with the same value. Once found, it remembers the value, and that memory can now be safely overwritten.

What if no memory can be found? Well, if no memory can be found, the FC3 firmware will check the location of the screen RAM and use that as temporary memory. The hope is that the program that was frozen will restore this memory itself. I have never seen this happen in practice, it looks like such a situation is really rare and under normal situations, there is always some memory that can be compressed.

The zero page from $0070 to $00D6 is backed up into the first piece memory, the unfreeze routine is installed into the second piece of memory. You might wonder why the unfreeze routine is stored in C64 RAM. Why not use cartridge ROM and use less C64 memory? The reason is the backup functionality. Backups can be loaded from disk or tape without any Final Cartridge installed. Therefore, it should be possible to unfreeze and continue a program, without reliance on cartridge ROM.

After the unfreeze routine has been written to memory, the stack is set up in such a way that an RTS instruction is enough to exit the freezer, unfreeze the program and continue normal execution.

The CIA registers are completely backed up into the freed up zero page memory at $0070. Now, not all CIA registers can be read, therefore, their values are retrieved with some tricks. For example, if you read the timers, you read their current value, not their period. However, if you load $10 into the CIA timer control register at $DC0E/$DC0F/$DD0E/$DD0F, you load the timer without starting it, so that way, you can retrieve the timer period. In order to read the status of the interrupt bits, the timers are being run for one period with interrupts disabled and reading the interrupt flags of the CIA. Note that the NMI line is still held low by the cartridge hardware, as far as the 6510 CPU is concerned, the computer is servicing an NMI interrupt, so NMI interrupts are effectively disabled and the firmware can successfully discover the values of the interrupt bits on CIA 2.

No attempt is made to discover the value of the real time clock alarm interrupt bits, the serial shift register interrupt bits and the FLAG interrupt bit.

Next the VIC-II registers are stored into freed up memory at $0090. In order to use as little space as possible, the freezer only saves the values of sprite 2..5 (that it will use itself) and $D010..$D028. It will also need to write sprite pointers in memory, so these memory locations are saved as well. Just like with the CIA timers, the firmware will monitor the VIC-II interrupt status bit for one frame to discover the raster line where a raster interrupt is triggered.

Regarding the SID, it has the unfortunate property that its registers cannot be read. The FC3 firmware simply sets the volume to 0 and does not touch any of the other registers while the program is frozen. Upon unfreeze the volume will be set to 15 and it is assumed that the frozen program will set it to its desired value at some point. This has consequences for backups: It might not be the best idea to create a backup while music is being played.

Now, the freezer has been initialized and it is ready to display the menu.

Displaying the freezer menu

The C64 has been successfully frozen, registers have been backed up and preparations for unfreeze have been done. There are a few bytes of memory available, but how on Earth is one going to display a full freezer menu with just a few bytes of memory available?

The secret to this is the so-called “invalid bitmap mode” of the VIC-II. If you enable both the bitmap enable bit (BMM) and enhanced background colour mode bit (ECM) at $D011, the VIC-II considers this an invalid mode and will display a black screen. The VIC-II internally behaves as if it was in bitmap mode, but output is forced to a black colour. The screen is still enabled: Sprites work normally. This is excellent for displaying the freezer menu, since this way the freezer can hide the current screen contents!

But we still need to display something, and need memory for that. Actually, not really, as the Final Cartridge III uses another property of the Ultimax mode of the Commodore 64: On the Commodore Max Machine, with its 2 KB of memory, there wasn’t any space in the memory of the machine for graphics. Therefore on the Max Machine, the VIC-II could read graphics directly from cartridge memory. On the Commodore 64, the VIC-II normally always reads from Commodore 64 RAM, but if you activate the Ultimax mode, the VIC-II is happy to directly read graphics from cartridge memory. This means that it is possible to display graphics without using C64 memory!

In order to display the menu bar, text mode is used. With help of a raster interrupt, the FC3 firmware disables invalid bitmap mode when the menu bar is being drawn and makes sure the VIC-II is in normal text mode. The screen buffer is located in cartridge memory, and so is the character font.

The currently open pull-down menu is rendered with sprites. Sprite 2..5 are set up to retrieve their shape from cartridge memory. The currently selected option in the menu is highlighted by changing the sprite colour inside the raster interrupt.

The click that you hear while navigating through the menus is made by switching SID volume to 15 and back to 0.

Accessing C64 memory

In Ultimax mode, most of the C64’s memory is disabled, however, in order to do its work, the freezer code regularly needs to access memory. It is difficult to release the GAME line while being in the freezer, because GAME is also pulled down by the freeze button, so releasing GAME through the register results in uncertainty whether the button is still pressed.

What the freezer code can do, is also pull down EXROM and if both GAME and EXROM are being pulled down, the C64 is in 16 KB cartridge mode. In 16 KB mode, ROM can be switched off by using the $0001 register and this way the freezer can access all of C64 memory whenever it needs.

Backups

While the freezer menu gives you the choice between slow and fast backups, this actually doesn’t do anything. Regardless of your choice, disk backups are always created with a fastloader inside and tape backups will always be written in turbotape format. A backup to disk is written using the low-level KERNAL API and therefore always slow, unless a KERNAL based speeder like JiffyDOS is installed.

A backup consists of two files “FC” and “-FC”. “FC” contains the loader and backups of the registers, colour RAM and memory from $0000 to $01ff. “-FC” contains the backup of $0402..$ffff, compressed with RLE and then after that, the backup of $0200..$0401, uncompressed.

While loading a backup from disk, the C64’s vectors at $0300 need to remain untouched until very late in the backup process. Therefore, before creating a backup, a routine that restores the vectors at $0300 is installed at $00A6, memory that was previously used for temporary variables while displaying the freezer. The values are read from disk by direct calls into KERNAL memory.

A backup starts by creating the “FC” file and writing the loader to it. Then the registers and memory from $0000..$0401 is written to the file, uncompressed.

Then, the memory from $0402..$ffff is compressed with an RLE algorithm. Then the “-FC” file is being written with the contents.

When loading a backup, the loader restores the memory from $0000 to $01ff right away. It can do so, because the variables needed by the KERNAL for serial I/O are inside the memory region that was freed up by the freezer. The loading thus happens with the original stack in memory.

The memory from $0200 onwards is not yet restored, it is needed for the disk/tape fastloader. The disk fastloader is an in-order version of the FC3 fastloader. The protocol used to transfer data between the floppy drive and the C64 is exactly the same as the normal fastloader, however, all sectors are read in file order, therefore the loading is not as as fast as the regular fastloader and depends on sector gap.

The RLE compressed memory is loaded towards the end of memory, i.e. the compressed block exists from $ffff downwards. This way the decompression routine can safely write the uncompressed memory from $0402 onwards without worrying that it overwrites the compressed memory.

When the memory has been read and decompressed, the loader reopens the “-FC” file, this time without the fastloader and starts loading page $0200. As a last step it jumps into the routine that was installed at $00A6 just before creating the backup, to load memory from $0300 to $0401.

When that has been done, the program can be unfrozen with a simple RTS instruction, just like an exit from the freezer.

Game trainer

The game trainer works by scanning C64 memory for reads of certain CIA and VIC-II registers. The machine code instructions to read these registers are replaced by a JSR to the IO1 window in bank 3 of the FC3 ROM. Because bank 3 remains active when the game trainer is used, the vectors at $0300 no longer point to their handlers in bank 0, i.e. as soon as the game trainer has been used, a game will no longer be able to read from disk. This can be considered an undocumented limitation of the FC3.

Screenshots

I will not discuss the screenshot code extensively, because I haven’t studied it myself enough yet. The screenshot code of the FC3 is pretty advanced in the way that it can convert all of the C64’s graphics modes into raster graphics, including any sprites that are visible. It supports multiple printer languages, and includes support for colour printers.

The screenshot code uses the same printer interface available in BASIC, which means it cannot only use printers connected to the Commodore serial bus, but thanks to the FC3’s additional drivers, also Centronics and RS232 printers connected to the user port.

The preview feature works quite similar to the normal freezer menu: The menu bar is displayed using a raster interrupt using a different location in cartridge memory. Unlike the freezer menu bar, the preview runs in 16 KB mode and temporarily switches to Ultimax mode during the raster interrupt. This allows the preview to disable invalid bitmap mode to temporarily display the original picture contained in C64 RAM.

Final words

I am quite impressed by the freezer, it shows that the creators of the Final Cartridge III had a really deep understanding of the Commodore 64. The creators show knowledge of how the 6510 handles interrupt, knowledge of what Ultimax mode does, knowledge how to retrieve values of registers than can normally not be read, knowledge about not so well known VIC-II features. It is all performed by machine code that needs to work under difficult conditions of using very little memory and be very careful what it touches.

The C64 is not controllable during the freezing process, and there were no modern tools like emulators, so debugging the code must have been a herculean effort.

All of this was used in an innovative way, with as a result, a simple, user-friendly button, that shows a pull-down menu in the look & feel of the Final Cartridge desktop.

When the FC3 came out in 1987, the Commodore 64 had been on the market for 5 years. For sure there was some time for the developers to study the machine, but there was no internet like it exists today to exchange information. There did exist books, but the documentation of the C64 wasn’t as extensive yet as it is today. There is absolutely no way the FC3 could have been created by just reading documentation.

The Commodore has always inspired people to explore how it works, push its limits and make it do more than it was originally designed to do. The creators of the Final Cartridge III were by no means the only C64 gurus at that time. However, they were some amazingly good gurus and for sure did succeed in making the C64 do more than it was originally designed to do.

What's Your Reaction?

Like Like 0
Dislike Dislike 0
Love Love 0
Funny Funny 0
Angry Angry 0
Sad Sad 0
Wow Wow 0