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).
2. Two Direct Download Links for .CRT Files
- https://commodore.software/downloads?task=download.send&id=11733:action-replay-v6-0&catid=576 (Action Replay v6.0 cartridge image).
- https://www.dropbox.com/s/0k6mw1jxjt2yn0u/The Final Cartridge III%2BV4.1.CRT?dl=1 (The Final Cartridge III+ v4.1 cartridge image).
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.
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; }