🧪 Helpers

RetroC64 comes with various helpers to make your life easier when programming the C64.

C64Registers

The C64Registers provides static constants for most C64 IO registers.

This allows you to refer to these locations by name rather than by their numeric addresses, improving code readability and maintainability.

For using them, you simply need to include the static class in your code:

using static RetroC64.C64Registers;

VIC-II Registers

Name Address Description
VIC2_SCREEN_CHARACTER_ADDRESS_DEFAULT 0x0400 Default screen character address.
VIC2_SPRITE0_ADDRESS_DEFAULT 0x03F8 Default address for sprite #0.
VIC2_SPRITE0_X 0xD000 Sprite #0 X-coordinate (bits 0-7). Use VIC2_SPRITE_X_MSB for bit 8.
VIC2_SPRITE0_Y 0xD001 Sprite #0 Y-coordinate.
VIC2_CONTROL1 0xD011 Screen control register #1.
VIC2_RASTER 0xD012 Current raster line (bits 0-7).
VIC2_LIGHT_PEN_X 0xD013 Light pen X-coordinate (bits 1-8). Read-only.
VIC2_LIGHT_PEN_Y 0xD014 Light pen Y-coordinate. Read-only.
VIC2_SPRITE_ENABLE 0xD015 Sprite enable register.
VIC2_CONTROL2 0xD016 Screen control register #2.
VIC2_SPRITE_Y_EXPAND 0xD017 Sprite double height register.
VIC2_MEMORY_POINTERS 0xD018 Memory setup register.
VIC2_INTERRUPT 0xD019 Interrupt status register.
VIC2_INTERRUPT_ENABLE 0xD01A Interrupt control register.
VIC2_SPRITE_PRIORITY 0xD01B Sprite priority register.
VIC2_SPRITE_MULTICOLOR 0xD01C Sprite multicolor mode register.
VIC2_SPRITE_X_EXPAND 0xD01D Sprite double width register.
VIC2_SPRITE_COLLISION 0xD01E Sprite-sprite collision register.
VIC2_SPRITE_BG_COLLISION 0xD01F Sprite-background collision register.
VIC2_BORDER_COLOR 0xD020 Border color (bits 0-3).
VIC2_BG_COLOR0 0xD021 Background color (bits 0-3).
VIC2_BG_COLOR1 0xD022 Extra background color #1 (bits 0-3).
VIC2_BG_COLOR2 0xD023 Extra background color #2 (bits 0-3).
VIC2_BG_COLOR3 0xD024 Extra background color #3 (bits 0-3).
VIC2_SPRITE_MULTICOLOR0 0xD025 Sprite extra color #1 (bits 0-3).
VIC2_SPRITE_MULTICOLOR1 0xD026 Sprite extra color #2 (bits 0-3).
VIC2_SPRITE0_COLOR 0xD027 Sprite #0 color (bits 0-3).
VIC2_SPRITE1_COLOR 0xD028 Sprite #1 color (bits 0-3).
VIC2_SPRITE2_COLOR 0xD029 Sprite #2 color (bits 0-3).
VIC2_SPRITE3_COLOR 0xD02A Sprite #3 color (bits 0-3).
VIC2_SPRITE4_COLOR 0xD02B Sprite #4 color (bits 0-3).
VIC2_SPRITE5_COLOR 0xD02C Sprite #5 color (bits 0-3).
VIC2_SPRITE6_COLOR 0xD02D Sprite #6 color (bits 0-3).
VIC2_SPRITE7_COLOR 0xD02E Sprite #7 color (bits 0-3).

SID Registers

Name Address Description
SID_VOICE1_FREQ_LO 0xD400 Voice #1 frequency (low byte).
SID_VOICE1_FREQ_HI 0xD401 Voice #1 frequency (high byte).
SID_VOICE1_PW_LO 0xD402 Voice #1 pulse width (low byte).
SID_VOICE1_PW_HI 0xD403 Voice #1 pulse width (high byte).
SID_VOICE1_CONTROL 0xD404 Voice #1 control register.
SID_VOICE1_ATTACK_DECAY 0xD405 Voice #1 Attack and Decay length.
SID_VOICE1_SUSTAIN_RELEASE 0xD406 Voice #1 Sustain volume and Release length.
SID_VOICE2_FREQ_LO 0xD407 Voice #2 frequency (low byte).
SID_VOICE2_FREQ_HI 0xD408 Voice #2 frequency (high byte).
SID_VOICE2_PW_LO 0xD409 Voice #2 pulse width (low byte).
SID_VOICE2_PW_HI 0xD40A Voice #2 pulse width (high byte).
SID_VOICE2_CONTROL 0xD40B Voice #2 control register.
SID_VOICE2_ATTACK_DECAY 0xD40C Voice #2 Attack and Decay length.
SID_VOICE2_SUSTAIN_RELEASE 0xD40D Voice #2 Sustain volume and Release length.
SID_VOICE3_FREQ_LO 0xD40E Voice #3 frequency (low byte).
SID_VOICE3_FREQ_HI 0xD40F Voice #3 frequency (high byte).
SID_VOICE3_PW_LO 0xD410 Voice #3 pulse width (low byte).
SID_VOICE3_PW_HI 0xD411 Voice #3 pulse width (high byte).
SID_VOICE3_CONTROL 0xD412 Voice #3 control register.
SID_VOICE3_ATTACK_DECAY 0xD413 Voice #3 Attack and Decay length.
SID_VOICE3_SUSTAIN_RELEASE 0xD414 Voice #3 Sustain volume and Release length.
SID_FILTER_FREQ_LO 0xD415 Filter cut off frequency (bits #0-#2).
SID_FILTER_FREQ_HI 0xD416 Filter cut off frequency (bits #3-#10).
SID_FILTER_RES_FILT 0xD417 Filter control.
SID_FILTER_MODE_VOL 0xD418 Volume and filter modes.
SID_POT_X 0xD419 X value of paddle selected at memory address $DC00. (Updates at every 512 system cycles.)
SID_POT_Y 0xD41A Y value of paddle selected at memory address $DC00. (Updates at every 512 system cycles.)
SID_OSC3 0xD41B Voice #3 waveform output.
SID_ENV3 0xD41C Voice #3 ADSR output.

CIA Registers

Name Address Description
CIA1_PORT_A 0xDC00 Port A, keyboard matrix columns and joystick #2.
CIA1_PORT_B 0xDC01 Port B, keyboard matrix rows and joystick #1.
CIA1_DATA_DIRECTION_A 0xDC02 Port A data direction register.
CIA1_DATA_DIRECTION_B 0xDC03 Port B data direction register.
CIA1_TIMER_A_LO 0xDC04 Timer A low byte. Read: current value. Write: set start value.
CIA1_TIMER_A_HI 0xDC05 Timer A high byte. Read: current value. Write: set start value.
CIA1_TIMER_B_LO 0xDC06 Timer B low byte. Read: current value. Write: set start value.
CIA1_TIMER_B_HI 0xDC07 Timer B high byte. Read: current value. Write: set start value.
CIA1_TIME_OF_DAY_10THS 0xDC08 Time of Day, tenth seconds (BCD, $00-$09). Read: current TOD. Write: set TOD/alarm.
CIA1_TIME_OF_DAY_SEC 0xDC09 Time of Day, seconds (BCD, $00-$59). Read: current TOD. Write: set TOD/alarm.
CIA1_TIME_OF_DAY_MIN 0xDC0A Time of Day, minutes (BCD, $00-$59). Read: current TOD. Write: set TOD/alarm.
CIA1_TIME_OF_DAY_HR 0xDC0B Time of Day, hours (BCD). Read: current TOD. Write: set TOD/alarm.
CIA1_SERIAL_DATA 0xDC0C Serial shift register. Bits read/written on CNT pin edge.
CIA1_INTERRUPT_CONTROL 0xDC0D Interrupt control and status register.
CIA1_CONTROL_A 0xDC0E Timer A control register.
CIA1_CONTROL_B 0xDC0F Timer B control register.
CIA2_PORT_A 0xDD00 Port A for CIA2.
CIA2_PORT_B 0xDD01 Port B for CIA2.
CIA2_DATA_DIRECTION_A 0xDD02 Port A data direction register for CIA2.
CIA2_DATA_DIRECTION_B 0xDD03 Port B data direction register for CIA2.
CIA2_TIMER_A_LO 0xDD04 Timer A low byte for CIA2.
CIA2_TIMER_A_HI 0xDD05 Timer A high byte for CIA2.
CIA2_TIMER_B_LO 0xDD06 Timer B low byte for CIA2.
CIA2_TIMER_B_HI 0xDD07 Timer B high byte for CIA2.
CIA2_TIME_OF_DAY_10THS 0xDD08 Time of Day, tenth seconds for CIA2.
CIA2_TIME_OF_DAY_SEC 0xDD09 Time of Day, seconds for CIA2.
CIA2_TIME_OF_DAY_MIN 0xDD0A Time of Day, minutes for CIA2.
CIA2_TIME_OF_DAY_HR 0xDD0B Time of Day, hours for CIA2.
CIA2_SERIAL_DATA 0xDD0C Serial shift register for CIA2.
CIA2_INTERRUPT_CONTROL 0xDD0D Interrupt control and status register for CIA2.
CIA2_CONTROL_A 0xDD0E Timer A control register for CIA2.
CIA2_CONTROL_B 0xDD0F Timer B control register for CIA2.

C64CharSet

The C64CharSet class provides a convenient way to work with the C64 character set (PETSCII and screen codes). It includes methods for converting between character codes and their corresponding screen representations.

Quick reference

Method Description Returns
CharToPETSCII(char ch) Unicode character to PETSCII byte. Invalid chars return 0xFF. byte
PETSCIIToChar(byte b, bool shifted) PETSCII byte to Unicode char. shifted selects the shifted table. char
StringToPETSCII(string str) Text to PETSCII bytes. byte[]
PETSCIIToPETScreenCode(ReadOnlySpan<byte> buffer) PETSCII bytes to PET screen codes. byte[]
StringToPETScreenCode(string text) Text to PET screen codes (via PETSCII). byte[]

Tip: Add the namespace before use:

using RetroC64;

Examples

CharToPETSCII

  • Signature: byte CharToPETSCII(char ch)
  • Description: Maps a Unicode character to its PETSCII byte. Returns 0xFF if the character has no mapping.
  • Example:
byte a = C64CharSet.CharToPETSCII('A');     // 0x41
byte heart = C64CharSet.CharToPETSCII('♥'); // e.g., mapped PETSCII value
byte invalid = C64CharSet.CharToPETSCII('€'); // 0xFF (no PETSCII mapping)

PETSCIIToChar

  • Signature: char PETSCIIToChar(byte b, bool shifted = false)
  • Description: Converts a PETSCII byte to its Unicode character using the unshifted or shifted table.
  • Example:
char c1 = C64CharSet.PETSCIIToChar(0x41);                // 'A' (unshifted)
char c2 = C64CharSet.PETSCIIToChar(0x41, shifted: true); // 'a' (shifted)

StringToPETSCII

  • Signature: byte[] StringToPETSCII(string str)
  • Description: Converts a .NET string into a PETSCII byte array.
  • Example:
byte[] petscii = C64CharSet.StringToPETSCII("HELLO!");
// petscii: [0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x21]

PETSCIIToPETScreenCode

  • Signature: byte[] PETSCIIToPETScreenCode(ReadOnlySpan<byte> buffer)
  • Description: Translates PETSCII bytes to C64 PET screen codes (as used by screen memory).
  • Example:
byte[] petscii = C64CharSet.StringToPETSCII("HI");
byte[] screen = C64CharSet.PETSCIIToPETScreenCode(petscii);
// screen now contains the screen codes for "HI"

StringToPETScreenCode

  • Signature: byte[] StringToPETScreenCode(string text)
  • Description: Convenience method: string -> PETSCII -> PET screen codes.
  • Example:
byte[] screen = C64CharSet.StringToPETScreenCode("SCORE 123");
// Write 'screen' bytes to C64 screen memory at $0400 in your emulator/tooling

C64AssemblerExtensions

The C64AssemblerExtensions class provides extension methods for the Assembler class to facilitate C64-specific assembly programming tasks.

Tip:

  • Add the namespace before use: using RetroC64;
  • Opcodes/factory helpers: using static Asm6502.Mos6502Factory;

Quick reference

Method Purpose Modifies
SetupStack(byte stackValue = 0xFF) Initialize stack pointer (TXS) to the given value (default $FF). X, stack
SetupRamAccess(CPUPortFlags defaultFlags = CPUPortFlags.Default) Configure CPU port $01 for RAM/ROM banking. A
DisableAllIrq() Disable CIA1/CIA2 IRQs and acknowledge any pending VIC-II IRQ. A
ClearMemoryBy256BytesBlock(ushort address, byte count, byte value = 0) Fill count×256 bytes starting at address with value. A, X
CopyMemoryBy256BytesBlock(Mos6502Label src, ushort dstAddress, byte count) Copy count×256 bytes from label src to dstAddress. A, X
CopyMemory(Mos6502ExpressionU16 src, Mos6502ExpressionU16 dst, ushort length) Reentrant copy of length bytes from src to dst. A, X, Y
PushAllRegisters() Push A, X, Y onto stack. A, stack
PopAllRegisters() Pop Y, X, A from stack. A, X, Y, stack
StoreLabelAtAddress(Mos6502Label label, ushort address) Store a 16-bit label address at memory address (lo/hi). A
InfiniteLoop() Infinite JMP-to-self loop.
SetupRasterIrq(Mos6502Label rasterHandler, byte rasterLine = 0) Install raster IRQ handler and enable VIC raster IRQ. A
DisableNmi() Install dummy NMI handler and arm CIA2 Timer A to lock NMIs. A
SetupTimeOfDayAndGetVerticalFrequency() Configure TOD for correct frequency and detect vertical rate. A = 1 (PAL 50Hz), 0 (NTSC 60Hz). A

Examples

Basic startup

using Asm6502;
using RetroC64;
using static Asm6502.Mos6502Factory;
using static RetroC64.C64Registers;

var asm = new C64Assembler();
asm.SEI()
   .SetupStack()
   .SetupRamAccess()
   .DisableAllIrq()
   .CLI();

Clear screen memory (write spaces to $0400-$07E7)

using RetroC64;
using static RetroC64.C64Registers;

asm.ClearMemoryBy256BytesBlock(VIC2_SCREEN_CHARACTER_ADDRESS_DEFAULT, count: 4, value: 0x20);

Copy data

using Asm6502;
using RetroC64;
using static Asm6502.Mos6502Factory;

// Copy 512 bytes from label 'SRC' to $2000
asm.Label(out var SRC);
// ...emit data for SRC...
asm.CopyMemoryBy256BytesBlock(SRC, 0x2000, count: 2);

// Copy an arbitrary length (e.g., 300 bytes) between addresses
asm.CopyMemory(new Mos6502Label(0x1010), new Mos6502Label(0x2020), length: 300);

Install a raster IRQ:

using Asm6502;
using RetroC64;
using static Asm6502.Mos6502Factory;
using static RetroC64.C64Registers;

asm.Label(out var irq);
asm
    .SetupRasterIrq(irq, rasterLine: 0x30)
    .CLI();

// IRQ routine
asm.Label(irq)
   .PushAllRegisters()
   .LDA(VIC2_INTERRUPT)   // acknowledge VIC IRQ
   .STA(VIC2_INTERRUPT)
   // ...your raster code...
   .PopAllRegisters()
   .RTI();

Disable the RESTORE-key NMI:

using RetroC64;

asm.SEI()
   .DisableNmi()
   .CLI();

Configure TOD and detect PAL/NTSC:

using RetroC64;

asm.SEI()
   .SetupTimeOfDayAndGetVerticalFrequency()
   // A now holds: 1 => PAL (50Hz), 0 => NTSC (60Hz)
   .CLI();

Disk64

The Disk64 class provides a simple API to create, load, format, and manipulate standard D64 images (35 tracks, no error bytes). It handles BAM updates, directory entries, and sector allocation with interleave automatically.

Tip:

  • Namespace: using RetroC64.Storage;
  • D64 is PETSCII-based. CbmFileName helps you pass proper file names (implicit string conversion included).

Quick reference

Member Description
new Disk64() Creates a blank, formatted 35-track D64 image.
void Format(CbmFileName? diskName = null) Re-initializes the image (BAM, directory). Optional disk name.
CbmFileName DiskName { get; set; } Disk name (PETSCII, max 16 chars).
string DiskId { get; set; } 2-character disk ID.
void Load(string path) / void Load(Stream) / void Load(byte[] image) Load an existing D64 image.
void Save(string path) / void Save(Stream) Save the current D64 image.
static Disk64 LoadFromFile(string path) Convenience file loader.
int TotalSectors { get; } Number of usable sectors for files.
Span<byte> UnsafeRawImage { get; } Direct access to raw bytes (use with care).
Span<byte> GetSector(int track, int sector) Read/write a specific 256-byte sector.
void WriteSector(int t, int s, ReadOnlySpan<byte> data) Write exactly 256 bytes to a sector.
int GetFreeSectorsCount(int track) Free sectors on a track.
bool IsSectorFree(int track, int sector) Checks if a sector is free.
void SetTrackFree(int track) Mark an entire track free (not for BAM track 18).
void SetSectorFree(int track, int sector, bool free) Mark a sector free/used.
List<CbmDirectoryEntry> ListDirectory() Returns directory entries.
byte[] ReadFile(CbmFileName name) Reads a file by name.
void WriteFile(CbmFileName name, ReadOnlySpan<byte> data, CbmFileType type = CbmFileType.PRG) Writes a file (allocates sectors, dir entry).
void DeleteFile(CbmFileName name) Deletes a file and frees its sectors.

Related types:

  • CbmFileName: PETSCII name wrapper with implicit conversions to/from string; always 16 bytes padded with 0xA0.
  • CbmDirectoryEntry: FileType, FileName, StartTrack, StartSector, FileSizeSectors.
  • CbmFileType: DEL, SEQ, PRG, USR, REL.

Examples

Create and save a blank disk

using RetroC64.Storage;

var disk = new Disk64
{
    DiskName = "MYDISK",
    DiskId = "01"
};

disk.Save("mydisk.d64");

Write, list, read, delete

using RetroC64.Storage;
using System.Text;

// Prepare some data
var payload = Encoding.ASCII.GetBytes("HELLO FROM DISK!");

// Write as PRG
var disk = new Disk64 { DiskName = "MYDISK", DiskId = "01" };
disk.WriteFile("HELLO", payload);

// List directory
foreach (var e in disk.ListDirectory())
{
    Console.WriteLine($"{(string)e.FileName}  {e.FileType}  {e.FileSizeSectors} blocks");
}

// Read it back
var bytes = disk.ReadFile("HELLO");

// Delete the file
disk.DeleteFile("HELLO");

// Save image
disk.Save("files.d64");

Load an existing image, add a file, and resave

using RetroC64.Storage;

var disk = Disk64.LoadFromFile("input.d64");
disk.WriteFile("DEMO", new byte[] { 0x01, 0x02, 0x03 });
disk.Save("output.d64");

Access raw sectors (advanced)

using RetroC64.Storage;

// Read BAM sector (track 18, sector 0)
var disk = new Disk64();
var bam = disk.GetSector(18, 0);
// bam[...]: 256-byte span; avoid corrupting unless you know the layout

// WriteSector requires exactly 256 bytes
var buffer = new byte[Disk64.SectorSize];
disk.WriteSector(18, 1, buffer); // overwrites directory sector 18/1

Working with names:

using RetroC64.Storage;

// From string (implicit)
CbmFileName fn1 = "HELLO";
// From PETSCII raw bytes
CbmFileName fn2 = new byte[] { 0x48, 0x45, 0x4C, 0x4C, 0x4F }; // "HELLO"