Task 113: .CRT File Format

Task 113: .CRT File Format

1. List of Properties for .CRT File Format

The .CRT file format is used for Commodore 64 (and related systems) cartridge images in emulators like VICE. It consists of a main header followed by one or more "CHIP" packets containing ROM/RAM data. The properties (fields) intrinsic to the format are as follows, based on the official VICE specification (version 2.0, backward-compatible with 1.0/1.1):

  • Signature: 16-byte ASCII string (e.g., "C64 CARTRIDGE   " for C64 cartridges; variations for other systems like VIC20 or C128).
  • Header Length: 4-byte integer (big-endian, typically 0x40 or 64 bytes).
  • Version: 2-byte integer (big-endian; e.g., 0x0100 for v1.0, 0x0200 for v2.0).
  • Hardware Type: 2-byte integer (big-endian; e.g., 0 for generic, 1 for Action Replay, 3 for Final Cartridge III, etc.; over 50 types defined).
  • EXROM Line Status: 1-byte value (0 = active/low, 1 = inactive/high; C64-specific).
  • GAME Line Status: 1-byte value (0 = active/low, 1 = inactive/high; C64-specific).
  • Hardware Revision/Subtype: 1-byte value (usually 0; added in v1.01 for subtype distinctions).
  • Reserved: 5-byte padding (for future use, typically zeros).
  • Name: 32-byte ASCII string (uppercase, null-padded cartridge name).

For each CHIP packet (variable number):

  • CHIP Signature: 4-byte ASCII string ("CHIP").
  • Packet Length: 4-byte integer (big-endian; header + data size, typically 0x2010 for 8KB ROM).
  • Chip Type: 2-byte integer (big-endian; 0 = ROM, 1 = RAM, 2 = Flash ROM, 3 = EEPROM).
  • Bank Number: 2-byte integer (big-endian; logical bank, starting from 0).
  • Load Address: 2-byte integer (big-endian; starting address in C64 memory, e.g., 0x8000).
  • Image Size: 2-byte integer (big-endian; size of the following data in bytes, e.g., 0x2000 for 8KB).
  • Data: Variable-length binary data (ROM/RAM contents; not a "property" per se, but the payload).

These properties define the cartridge's configuration, memory mapping, and contents. The format supports bank-switching and various hardware modes (e.g., Ultimax, 8KB/16KB game).

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .CRT File Dump

This is a self-contained HTML snippet with JavaScript for a Ghost blog embed. It allows drag-and-drop of a .CRT file and dumps all properties to the screen (parsing the header and all CHIP packets). It assumes binary file reading via FileReader.

Drag and drop a .CRT file here

4. Python Class for .CRT File Handling

This class can open, decode (read), write (create/modify), and print properties to console. It uses big-endian for multi-byte values.

import struct
import sys

class CRTFile:
    def __init__(self, filepath=None):
        self.signature = b''
        self.header_length = 0
        self.version = 0
        self.hardware_type = 0
        self.exrom = 0
        self.game = 0
        self.revision = 0
        self.reserved = b'\x00' * 5
        self.name = b''
        self.chips = []  # List of dicts: {'chip_sig': b'CHIP', 'packet_len': int, 'chip_type': int, 'bank': int, 'load_addr': int, 'image_size': int, 'data': bytes}

        if filepath:
            self.read(filepath)

    def read(self, filepath):
        with open(filepath, 'rb') as f:
            data = f.read()
        offset = 0
        self.signature = data[offset:offset+16].rstrip(b'\x00 ').decode('ascii', errors='ignore')
        offset += 16
        self.header_length = struct.unpack('>I', data[offset:offset+4])[0]
        offset += 4
        self.version = struct.unpack('>H', data[offset:offset+2])[0]
        offset += 2
        self.hardware_type = struct.unpack('>H', data[offset:offset+2])[0]
        offset += 2
        self.exrom = data[offset]
        offset += 1
        self.game = data[offset]
        offset += 1
        self.revision = data[offset]
        offset += 1
        self.reserved = data[offset:offset+5]
        offset += 5
        self.name = data[offset:offset+32].rstrip(b'\x00').decode('ascii', errors='ignore')
        offset += 32

        self.chips = []
        while offset < len(data):
            chip = {}
            chip['chip_sig'] = data[offset:offset+4]
            if chip['chip_sig'] != b'CHIP':
                break
            offset += 4
            chip['packet_len'] = struct.unpack('>I', data[offset:offset+4])[0]
            offset += 4
            chip['chip_type'] = struct.unpack('>H', data[offset:offset+2])[0]
            offset += 2
            chip['bank'] = struct.unpack('>H', data[offset:offset+2])[0]
            offset += 2
            chip['load_addr'] = struct.unpack('>H', data[offset:offset+2])[0]
            offset += 2
            chip['image_size'] = struct.unpack('>H', data[offset:offset+2])[0]
            offset += 2
            chip['data'] = data[offset:offset + chip['image_size']]
            offset += chip['image_size']
            self.chips.append(chip)

    def print_properties(self):
        print(f"Signature: {self.signature}")
        print(f"Header Length: {self.header_length}")
        print(f"Version: {self.version}")
        print(f"Hardware Type: {self.hardware_type}")
        print(f"EXROM Status: {self.exrom}")
        print(f"GAME Status: {self.game}")
        print(f"Hardware Revision: {self.revision}")
        print(f"Reserved: {self.reserved.hex()}")
        print(f"Name: {self.name}")
        for i, chip in enumerate(self.chips, 1):
            print(f"\nCHIP Packet {i}:")
            print(f"  Signature: {chip['chip_sig'].decode('ascii')}")
            print(f"  Packet Length: {chip['packet_len']}")
            print(f"  Chip Type: {chip['chip_type']}")
            print(f"  Bank Number: {chip['bank']}")
            print(f"  Load Address: 0x{chip['load_addr']:04X}")
            print(f"  Image Size: {chip['image_size']}")
            print(f"  Data: [Binary data, {chip['image_size']} bytes]")

    def write(self, filepath):
        with open(filepath, 'wb') as f:
            f.write(self.signature.ljust(16, b' ').encode('ascii'))
            f.write(struct.pack('>I', self.header_length))
            f.write(struct.pack('>H', self.version))
            f.write(struct.pack('>H', self.hardware_type))
            f.write(bytes([self.exrom]))
            f.write(bytes([self.game]))
            f.write(bytes([self.revision]))
            f.write(self.reserved)
            f.write(self.name.encode('ascii').ljust(32, b'\x00'))
            for chip in self.chips:
                f.write(chip['chip_sig'])
                f.write(struct.pack('>I', chip['packet_len']))
                f.write(struct.pack('>H', chip['chip_type']))
                f.write(struct.pack('>H', chip['bank']))
                f.write(struct.pack('>H', chip['load_addr']))
                f.write(struct.pack('>H', chip['image_size']))
                f.write(chip['data'])

# Example usage: crt = CRTFile('example.crt'); crt.print_properties(); crt.write('modified.crt')

5. Java Class for .CRT File Handling

This class handles opening, decoding (reading), writing, and printing properties to console. Uses big-endian ByteBuffer.

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;

public class CRTFile {
    private String signature;
    private int headerLength;
    private int version;
    private int hardwareType;
    private byte exrom;
    private byte game;
    private byte revision;
    private byte[] reserved = new byte[5];
    private String name;
    private ChipPacket[] chips;

    static class ChipPacket {
        String chipSig;
        int packetLen;
        int chipType;
        int bank;
        int loadAddr;
        int imageSize;
        byte[] data;
    }

    public CRTFile(String filepath) throws IOException {
        if (filepath != null) {
            read(filepath);
        }
    }

    public void read(String filepath) throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filepath));
        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
        byte[] sigBytes = new byte[16];
        buffer.get(sigBytes);
        signature = new String(sigBytes).trim();
        headerLength = buffer.getInt();
        version = buffer.getShort() & 0xFFFF;
        hardwareType = buffer.getShort() & 0xFFFF;
        exrom = buffer.get();
        game = buffer.get();
        revision = buffer.get();
        buffer.get(reserved);
        byte[] nameBytes = new byte[32];
        buffer.get(nameBytes);
        name = new String(nameBytes).trim().replaceAll("\0.*", "");

        java.util.List<ChipPacket> chipList = new java.util.ArrayList<>();
        while (buffer.hasRemaining()) {
            ChipPacket chip = new ChipPacket();
            byte[] chipSigBytes = new byte[4];
            buffer.get(chipSigBytes);
            chip.chipSig = new String(chipSigBytes);
            if (!chip.chipSig.equals("CHIP")) break;
            chip.packetLen = buffer.getInt();
            chip.chipType = buffer.getShort() & 0xFFFF;
            chip.bank = buffer.getShort() & 0xFFFF;
            chip.loadAddr = buffer.getShort() & 0xFFFF;
            chip.imageSize = buffer.getShort() & 0xFFFF;
            chip.data = new byte[chip.imageSize];
            buffer.get(chip.data);
            chipList.add(chip);
        }
        chips = chipList.toArray(new ChipPacket[0]);
    }

    public void printProperties() {
        System.out.println("Signature: " + signature);
        System.out.println("Header Length: " + headerLength);
        System.out.println("Version: " + version);
        System.out.println("Hardware Type: " + hardwareType);
        System.out.println("EXROM Status: " + exrom);
        System.out.println("GAME Status: " + game);
        System.out.println("Hardware Revision: " + revision);
        System.out.print("Reserved: ");
        for (byte b : reserved) System.out.printf("%02X ", b);
        System.out.println();
        System.out.println("Name: " + name);
        for (int i = 0; i < chips.length; i++) {
            ChipPacket chip = chips[i];
            System.out.println("\nCHIP Packet " + (i + 1) + ":");
            System.out.println("  Signature: " + chip.chipSig);
            System.out.println("  Packet Length: " + chip.packetLen);
            System.out.println("  Chip Type: " + chip.chipType);
            System.out.println("  Bank Number: " + chip.bank);
            System.out.println("  Load Address: 0x" + Integer.toHexString(chip.loadAddr).toUpperCase());
            System.out.println("  Image Size: " + chip.imageSize);
            System.out.println("  Data: [Binary data, " + chip.imageSize + " bytes]");
        }
    }

    public void write(String filepath) throws IOException {
        try (FileChannel channel = new FileOutputStream(filepath).getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate(64 + chips.length * 16 + chips.length * 8192).order(ByteOrder.BIG_ENDIAN); // Approximate
            buffer.put(signature.getBytes(java.nio.charset.StandardCharsets.US_ASCII));
            while (buffer.position() < 16) buffer.put((byte) ' ');
            buffer.putInt(headerLength);
            buffer.putShort((short) version);
            buffer.putShort((short) hardwareType);
            buffer.put(exrom);
            buffer.put(game);
            buffer.put(revision);
            buffer.put(reserved);
            byte[] nameBytes = name.getBytes(java.nio.charset.StandardCharsets.US_ASCII);
            buffer.put(nameBytes);
            while (buffer.position() % 32 != 0 || buffer.position() - nameBytes.length < 32) buffer.put((byte) 0); // Pad to 32
            for (ChipPacket chip : chips) {
                buffer.put(chip.chipSig.getBytes());
                buffer.putInt(chip.packetLen);
                buffer.putShort((short) chip.chipType);
                buffer.putShort((short) chip.bank);
                buffer.putShort((short) chip.loadAddr);
                buffer.putShort((short) chip.imageSize);
                buffer.put(chip.data);
            }
            buffer.flip();
            channel.write(buffer);
        }
    }

    // Example usage: CRTFile crt = new CRTFile("example.crt"); crt.printProperties(); crt.write("modified.crt");
}

6. JavaScript Class for .CRT File Handling

This class works in Node.js (requires 'fs' module). It can open, decode, write, and print properties to console.

const fs = require('fs');

class CRTFile {
  constructor(filepath = null) {
    this.signature = '';
    this.headerLength = 0;
    this.version = 0;
    this.hardwareType = 0;
    this.exrom = 0;
    this.game = 0;
    this.revision = 0;
    this.reserved = Buffer.alloc(5);
    this.name = '';
    this.chips = []; // Array of objects: {chipSig, packetLen, chipType, bank, loadAddr, imageSize, data}

    if (filepath) {
      this.read(filepath);
    }
  }

  read(filepath) {
    const data = fs.readFileSync(filepath);
    let offset = 0;
    this.signature = data.slice(offset, offset + 16).toString('ascii').trim();
    offset += 16;
    this.headerLength = data.readUInt32BE(offset);
    offset += 4;
    this.version = data.readUInt16BE(offset);
    offset += 2;
    this.hardwareType = data.readUInt16BE(offset);
    offset += 2;
    this.exrom = data[offset];
    offset += 1;
    this.game = data[offset];
    offset += 1;
    this.revision = data[offset];
    offset += 1;
    this.reserved = data.slice(offset, offset + 5);
    offset += 5;
    this.name = data.slice(offset, offset + 32).toString('ascii').replace(/\0.*$/, '');
    offset += 32;

    this.chips = [];
    while (offset < data.length) {
      const chip = {};
      chip.chipSig = data.slice(offset, offset + 4).toString('ascii');
      if (chip.chipSig !== 'CHIP') break;
      offset += 4;
      chip.packetLen = data.readUInt32BE(offset);
      offset += 4;
      chip.chipType = data.readUInt16BE(offset);
      offset += 2;
      chip.bank = data.readUInt16BE(offset);
      offset += 2;
      chip.loadAddr = data.readUInt16BE(offset);
      offset += 2;
      chip.imageSize = data.readUInt16BE(offset);
      offset += 2;
      chip.data = data.slice(offset, offset + chip.imageSize);
      offset += chip.imageSize;
      this.chips.push(chip);
    }
  }

  printProperties() {
    console.log(`Signature: ${this.signature}`);
    console.log(`Header Length: ${this.headerLength}`);
    console.log(`Version: ${this.version}`);
    console.log(`Hardware Type: ${this.hardwareType}`);
    console.log(`EXROM Status: ${this.exrom}`);
    console.log(`GAME Status: ${this.game}`);
    console.log(`Hardware Revision: ${this.revision}`);
    console.log(`Reserved: ${this.reserved.toString('hex')}`);
    console.log(`Name: ${this.name}`);
    this.chips.forEach((chip, i) => {
      console.log(`\nCHIP Packet ${i + 1}:`);
      console.log(`  Signature: ${chip.chipSig}`);
      console.log(`  Packet Length: ${chip.packetLen}`);
      console.log(`  Chip Type: ${chip.chipType}`);
      console.log(`  Bank Number: ${chip.bank}`);
      console.log(`  Load Address: 0x${chip.loadAddr.toString(16).toUpperCase()}`);
      console.log(`  Image Size: ${chip.imageSize}`);
      console.log(`  Data: [Binary data, ${chip.imageSize} bytes]`);
    });
  }

  write(filepath) {
    let buffers = [];
    buffers.push(Buffer.from(this.signature.padEnd(16, ' '), 'ascii'));
    let headerBuf = Buffer.alloc(4 + 2 + 2 + 1 + 1 + 1 + 5);
    let off = 0;
    headerBuf.writeUInt32BE(this.headerLength, off); off += 4;
    headerBuf.writeUInt16BE(this.version, off); off += 2;
    headerBuf.writeUInt16BE(this.hardwareType, off); off += 2;
    headerBuf[off++] = this.exrom;
    headerBuf[off++] = this.game;
    headerBuf[off++] = this.revision;
    this.reserved.copy(headerBuf, off); off += 5;
    buffers.push(headerBuf);
    buffers.push(Buffer.from(this.name.padEnd(32, '\x00'), 'ascii'));
    this.chips.forEach(chip => {
      let chipBuf = Buffer.alloc(4 + 4 + 2 + 2 + 2 + 2);
      let coff = 0;
      chipBuf.write(chip.chipSig, coff, 4, 'ascii'); coff += 4;
      chipBuf.writeUInt32BE(chip.packetLen, coff); coff += 4;
      chipBuf.writeUInt16BE(chip.chipType, coff); coff += 2;
      chipBuf.writeUInt16BE(chip.bank, coff); coff += 2;
      chipBuf.writeUInt16BE(chip.loadAddr, coff); coff += 2;
      chipBuf.writeUInt16BE(chip.imageSize, coff); coff += 2;
      buffers.push(chipBuf);
      buffers.push(chip.data);
    });
    fs.writeFileSync(filepath, Buffer.concat(buffers));
  }
}

// Example usage: const crt = new CRTFile('example.crt'); crt.printProperties(); crt.write('modified.crt');

7. C Class (Struct-Based) for .CRT File Handling

This uses structs for simplicity (C doesn't have built-in classes like C++). It can open, decode, write, and print properties to console. Compile with gcc crt.c -o crt and run ./crt example.crt.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <endian.h> // For big-endian conversions if needed; assume host is little-endian

typedef struct {
    char signature[17];
    uint32_t header_length;
    uint16_t version;
    uint16_t hardware_type;
    uint8_t exrom;
    uint8_t game;
    uint8_t revision;
    uint8_t reserved[5];
    char name[33];
} CRTHeader;

typedef struct ChipPacket {
    char chip_sig[5];
    uint32_t packet_len;
    uint16_t chip_type;
    uint16_t bank;
    uint16_t load_addr;
    uint16_t image_size;
    uint8_t *data;
    struct ChipPacket *next;
} ChipPacket;

typedef struct {
    CRTHeader header;
    ChipPacket *chips;
} CRTFile;

void read_crt(CRTFile *crt, const char *filepath) {
    FILE *f = fopen(filepath, "rb");
    if (!f) {
        perror("Failed to open file");
        exit(1);
    }
    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    fseek(f, 0, SEEK_SET);
    uint8_t *data = malloc(size);
    fread(data, 1, size, f);
    fclose(f);

    int offset = 0;
    memcpy(crt->header.signature, data + offset, 16);
    crt->header.signature[16] = '\0';
    offset += 16;
    crt->header.header_length = be32toh(*(uint32_t*)(data + offset));
    offset += 4;
    crt->header.version = be16toh(*(uint16_t*)(data + offset));
    offset += 2;
    crt->header.hardware_type = be16toh(*(uint16_t*)(data + offset));
    offset += 2;
    crt->header.exrom = data[offset++];
    crt->header.game = data[offset++];
    crt->header.revision = data[offset++];
    memcpy(crt->header.reserved, data + offset, 5);
    offset += 5;
    memcpy(crt->header.name, data + offset, 32);
    crt->header.name[32] = '\0';
    for (int i = 31; i >= 0; i--) if (crt->header.name[i] == '\0') crt->header.name[i] = '\0'; else break;
    offset += 32;

    crt->chips = NULL;
    ChipPacket *last = NULL;
    int chip_idx = 1;
    while (offset < size) {
        ChipPacket *chip = malloc(sizeof(ChipPacket));
        memcpy(chip->chip_sig, data + offset, 4);
        chip->chip_sig[4] = '\0';
        if (strcmp(chip->chip_sig, "CHIP") != 0) {
            free(chip);
            break;
        }
        offset += 4;
        chip->packet_len = be32toh(*(uint32_t*)(data + offset));
        offset += 4;
        chip->chip_type = be16toh(*(uint16_t*)(data + offset));
        offset += 2;
        chip->bank = be16toh(*(uint16_t*)(data + offset));
        offset += 2;
        chip->load_addr = be16toh(*(uint16_t*)(data + offset));
        offset += 2;
        chip->image_size = be16toh(*(uint16_t*)(data + offset));
        offset += 2;
        chip->data = malloc(chip->image_size);
        memcpy(chip->data, data + offset, chip->image_size);
        offset += chip->image_size;
        chip->next = NULL;
        if (!crt->chips) crt->chips = chip;
        else last->next = chip;
        last = chip;
    }
    free(data);
}

void print_properties(const CRTFile *crt) {
    printf("Signature: %s\n", crt->header.signature);
    printf("Header Length: %u\n", crt->header.header_length);
    printf("Version: %u\n", crt->header.version);
    printf("Hardware Type: %u\n", crt->header.hardware_type);
    printf("EXROM Status: %u\n", crt->header.exrom);
    printf("GAME Status: %u\n", crt->header.game);
    printf("Hardware Revision: %u\n", crt->header.revision);
    printf("Reserved: ");
    for (int i = 0; i < 5; i++) printf("%02X ", crt->header.reserved[i]);
    printf("\n");
    printf("Name: %s\n", crt->header.name);
    ChipPacket *chip = crt->chips;
    int i = 1;
    while (chip) {
        printf("\nCHIP Packet %d:\n", i++);
        printf("  Signature: %s\n", chip->chip_sig);
        printf("  Packet Length: %u\n", chip->packet_len);
        printf("  Chip Type: %u\n", chip->chip_type);
        printf("  Bank Number: %u\n", chip->bank);
        printf("  Load Address: 0x%04X\n", chip->load_addr);
        printf("  Image Size: %u\n", chip->image_size);
        printf("  Data: [Binary data, %u bytes]\n", chip->image_size);
        chip = chip->next;
    }
}

void write_crt(const CRTFile *crt, const char *filepath) {
    FILE *f = fopen(filepath, "wb");
    if (!f) {
        perror("Failed to write file");
        exit(1);
    }
    char sig[16];
    memset(sig, ' ', 16);
    strncpy(sig, crt->header.signature, 16);
    fwrite(sig, 1, 16, f);
    uint32_t hl = htobe32(crt->header.header_length);
    fwrite(&hl, 4, 1, f);
    uint16_t ver = htobe16(crt->header.version);
    fwrite(&ver, 2, 1, f);
    uint16_t ht = htobe16(crt->header.hardware_type);
    fwrite(&ht, 2, 1, f);
    fwrite(&crt->header.exrom, 1, 1, f);
    fwrite(&crt->header.game, 1, 1, f);
    fwrite(&crt->header.revision, 1, 1, f);
    fwrite(crt->header.reserved, 1, 5, f);
    char name[32];
    memset(name, '\0', 32);
    strncpy(name, crt->header.name, 32);
    fwrite(name, 1, 32, f);
    ChipPacket *chip = crt->chips;
    while (chip) {
        fwrite(chip->chip_sig, 1, 4, f);
        uint32_t pl = htobe32(chip->packet_len);
        fwrite(&pl, 4, 1, f);
        uint16_t ct = htobe16(chip->chip_type);
        fwrite(&ct, 2, 1, f);
        uint16_t bk = htobe16(chip->bank);
        fwrite(&bk, 2, 1, f);
        uint16_t la = htobe16(chip->load_addr);
        fwrite(&la, 2, 1, f);
        uint16_t is = htobe16(chip->image_size);
        fwrite(&is, 2, 1, f);
        fwrite(chip->data, 1, chip->image_size, f);
        chip = chip->next;
    }
    fclose(f);
}

void free_crt(CRTFile *crt) {
    ChipPacket *chip = crt->chips;
    while (chip) {
        ChipPacket *next = chip->next;
        free(chip->data);
        free(chip);
        chip = next;
    }
}

// Example usage: int main(int argc, char **argv) { CRTFile crt; read_crt(&crt, argv[1]); print_properties(&crt); write_crt(&crt, "modified.crt"); free_crt(&crt); return 0; }