How to compile a C program into an 8k cartridge binary

An extremely bare bones guide, 2017-10-26

This mini article is about compiling your C program into a Commodore 64 8k cartridge binary. This is a nearly pointless exercise because the compiled version is going to go over 8k pretty easily. It doesn't cover burning the binary: that is covered here.

Requirements

  1. cc65
  2. Joseph Rose's "gen cart 64" https://sourceforge.net/projects/cc65extra/files/memory%20cfgs/

Procedure

Build a modified C runtime lib crt0 for cc65

The default cc65 C runtime puts a header on the binary suitable for a prg file, but not a cart. I tinkered with the linker config to get rid of it, catted in my own cart header, and while I got it working in a rudimentary hacky way, there will still other problems. So we need a modified C runtime to do it properly. Joseph Rose's has figured this out and written a modified crt.

  1. copy the cc65 c64.lib to a new file, one we will modify
  2. delete the old crt0.o from the new file
  3. compile Joseph Rose's crt0.o replacement (it's in cfg/generic/cart64_8k.S in the gen cart zip)
  4. add it to your new c64 lib
cd /c/cc65/lib
cp c64.lib c64-cart.lib
ar65 d c64-cart.lib crt0.o
ca65 cart64_8k.S
ar65 a c64-cart.lib cart64_8k.o

One problem with Joseph Rose's config is that it is for building CRT files, which have extra headers for use with emulators. I'm only interested in binaries that can be burned onto an EPROM. This extra header needs removing. Also, gen cart 64 hasn't been udpated in a couple of years and the linker config file is missing the 'ONCE' line.

Make a new linker cfg file

I'm using Joseph Rose's with a couple of mods:

  1. removed the CRT header stuff (or actually, just make it not write to the image)
  2. add in the ONCE line that seems to be required by more recent versions of cc65

cut and paste the below and save it as cc65/cfg/cart64_8k.cfg (your custom linker config)

SYMBOLS {
    __STACKSIZE__: value = $0800, type = weak; # 2k stack
}
MEMORY {
    ZP:   start = $0002, size = $001A, type = rw, define = yes;
#This CRT header stuff is refereced cart64_8k.s but not written to cart binary
    CHEADER:  start = $0000, size = $0040, type = ro, file = "", fill=yes;
    ROMCHIP:  start = $0000, size = $0010, type = ro, file = "", fill=yes;
#8k catridge data.
    ROM:  start = $8000, size = $2000, file = %O, fill=yes, define = yes;
#Usable RAM for data.
    RAM:  start = $0800, size = $7800, type = rw, define = yes;
#Data at $C000.  Not normally used but made available to your cartridge.
#Top 2k is for the stack.
    HIRAM:  start = $C000, size = $1000-__STACKSIZE__, type = rw;
}
SEGMENTS {
#Cartridge ID and settings - these are not written to cart binary but still req
#because referenced in cart64_8k.S
    HEADERDATA: load = CHEADER, type = ro;
    CHIP0:  load = ROMCHIP, type = ro;
#-----------------------------------------
    STARTUP:  load = ROM, type = ro;
    LOWCODE:  load = ROM, type = ro,               optional = yes;
    INIT: load = ROM, type = ro, define = yes, optional = yes;
    CODE: load = ROM, type = ro;
    RODATA: load = ROM, type = ro;
    DATA: load = ROM, run = RAM, type = rw, define = yes;
    ONCE:     load = ROM,     type = ro,  define   = yes; # MAX ADDED THIS
    BSS:  load = RAM, type = bss, define = yes;
    HEAP: load = RAM, type = bss, optional = yes; # must sit just below stack
#Data stored at $C000.
    BSSHI:  load = HIRAM, type = bss, define = yes, optional = yes;
    ZEROPAGE: load = ZP,  type = zp;
}
FEATURES {
    CONDES: segment = INIT,
      type = constructor,
      label = __CONSTRUCTOR_TABLE__,
      count = __CONSTRUCTOR_COUNT__;
    CONDES: segment = RODATA,
      type = destructor,
      label = __DESTRUCTOR_TABLE__,
      count = __DESTRUCTOR_COUNT__;
    CONDES: segment = RODATA,
      type = interruptor,
      label = __INTERRUPTOR_TABLE__,
      count = __INTERRUPTOR_COUNT__;
}

Compile your c file

cl65 -O -c program.c

Link it with your modified c runtime to make an 8k bin

ld65 -o program.bin -C cart64_8k.cfg program.o c64-cart.lib

Test it with winvice

x64.exe -cart8 yourprogram.bin

References

Joseph Rose's "gen cart 64"
https://sourceforge.net/projects/cc65extra/files/memory%20cfgs/

This explains the format of the linker config file
https://cc65.github.io/doc/ld65.html

Out of date but useful info on cart creation with C for vic 20
http://sleepingelephant.com/denial/wiki/index.php?title=C_using_CC65

Back to index