RetroC64 comes with various helpers to make your life easier when programming the C64.
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;
| 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). |
| 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. |
| 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. |
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.
| 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;
byte CharToPETSCII(char ch)0xFF if the character has no mapping.byte a = C64CharSet.CharToPETSCII('A'); // 0x41
byte heart = C64CharSet.CharToPETSCII('♥'); // e.g., mapped PETSCII value
byte invalid = C64CharSet.CharToPETSCII('€'); // 0xFF (no PETSCII mapping)
char PETSCIIToChar(byte b, bool shifted = false)char c1 = C64CharSet.PETSCIIToChar(0x41); // 'A' (unshifted)
char c2 = C64CharSet.PETSCIIToChar(0x41, shifted: true); // 'a' (shifted)
byte[] StringToPETSCII(string str)byte[] petscii = C64CharSet.StringToPETSCII("HELLO!");
// petscii: [0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x21]
byte[] PETSCIIToPETScreenCode(ReadOnlySpan<byte> buffer)byte[] petscii = C64CharSet.StringToPETSCII("HI");
byte[] screen = C64CharSet.PETSCIIToPETScreenCode(petscii);
// screen now contains the screen codes for "HI"
byte[] StringToPETScreenCode(string text)byte[] screen = C64CharSet.StringToPETScreenCode("SCORE 123");
// Write 'screen' bytes to C64 screen memory at $0400 in your emulator/tooling
The C64AssemblerExtensions class provides extension methods for the Assembler class to facilitate C64-specific assembly programming tasks.
Tip:
using RetroC64;using static Asm6502.Mos6502Factory;| 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 |
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();
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:
using RetroC64.Storage;CbmFileName helps you pass proper file names (implicit string conversion included).| 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.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"