Task 079: .CD File Format

Task 079: .CD File Format

File Format Specifications for .CD

The .CD file format is a proprietary disc image format developed by Philips for the Compact Disc Interactive (CD-i) platform using the OptImage authoring and mastering tool. It is essentially a bit-for-bit duplicate of a CD-i disc's contents, including data, file structure, sector layout, and copy protection mechanisms. CD-i was an interactive multimedia standard from the late 1980s to 1990s, discontinued in 1998. The format stores the entire disc as a single file, allowing emulation or archiving of CD-i applications, which include multimedia content like video, audio, and interactive programs.

The .CD image encapsulates the CD-i physical format (based on Yellow Book Mode 2 sectors) and the CD-i file system, which is a variant of the OS-9 real-time operating system's file system. It is not compatible with standard ISO 9660 (used in CD-ROMs), though CD-i players can read ISO 9660 discs. Detailed technical specifications are scarce due to the proprietary nature and age of the format; official documentation is limited to Philips' Green Book standard for CD-i, with OptImage details being internal to Philips' tools. Parsing requires treating the .CD file as a sequence of 2352-byte sectors (including sync, header, subheader, user data, and EDC/ECC). The file system supports real-time multimedia interleaving for synchronized audio/video playback.

Key references for specifications:

  • Philips Green Book (CD-i standard): Defines sector formats, disc structure, and file system basics.
  • OS-9 file system documentation (as CD-i is OS-9 based).
  • Tools like IsoBuster can open .CD files by treating them as raw CD-i images.

1. List of All Properties Intrinsic to Its File System

The CD-i file system (embedded in .CD images) is hierarchical, supports real-time data handling for multimedia, and includes extensions for interactive content. Intrinsic properties (core structural and behavioral attributes of the file system itself, excluding application-specific data) are:

  • File System Type: OS-9 variant (real-time, multi-user file system optimized for embedded systems; not compatible with ISO 9660).
  • Volume Descriptor Identifier: "CD-I " (5 bytes, including trailing space; located in the Primary Volume Descriptor at sector offset 8-12 after lead-in).
  • Sector Format: Yellow Book Mode 2 (2352 bytes total per sector: 12-byte sync, 4-byte header, 8-byte subheader, 2324 bytes user data, 4-byte EDC, 276-byte ECC). Subheader fields: File ID (1 byte), Channel ID (1 byte, for interleaving groups), Submode/Data Type (1 byte: e.g., 0x08 for end of file unit, 0x10 for end of record), File Unit Type (1 byte), EOR flag (end of record interleaving), EOF flag (end of file), SOL flag (start of interleaving unit), Interleave Size (1 byte: units of 16 sectors).
  • Maximum Filename Length: 28 characters (including extension; supports mixed case letters, digits, and some symbols; no spaces in base name).
  • Case Sensitivity: Supported (upper and lower case distinguished), but files with the same name ignoring case cannot coexist in the same directory (e.g., "file.txt" and "File.TXT" not allowed).
  • Directory Structure: Hierarchical with unlimited depth (subdirectories can nest indefinitely); directories treated as special files with directory attribute.
  • File Attributes/Permissions: Unix-like (9 bits: owner/group/other read/write/execute); additional flags for hidden, system, multiple (allows multiple links), and directory. Real-time files have interleaving attributes for audio/video sync.
  • File Size Limit: Up to the disc capacity (648-744 MB depending on sector mix); individual files up to ~2 GB theoretically, but practical limits from sector allocation.
  • Directory Entries: Variable length; each entry includes filename (padded to 29 bytes with 0x00), extent location (logical block address, LBA), data length, file attributes, interleave info, and parent directory link. Directories end with a "." entry for self-reference and ".." for parent.
  • Volume Structure: Starts after lead-in; includes 166 initial audio message sectors (CD-DA format warning), Disc Label (variable size: 75 * SDI + FDL sectors, where SDI is sector display interval), 2250 more message sectors, then data tracks. Primary Volume Descriptor (PVD) at LBA 16 (like ISO 9660 but with CD-I ID).
  • Disc Label Properties: Embedded in file system boot; includes disc title (up to 128 chars), creator/preparer ID, creation/expiration dates (Julian format), abstract/preparer's remarks, application identifier (name of startup executable, e.g., ".APP"), and volume set size/sequence.
  • Interleaving Support: Intrinsic for real-time files; units of 1-32 sectors, with channel IDs grouping related sectors (e.g., audio in even sectors, video in odd) for constant-speed playback at 1x (150-170 KB/s).
  • Error Handling: Mode 2 Form 1 sectors include EDC/ECC for data integrity; Form 2 omits for real-time media (trades reliability for capacity/speed).
  • Boot/Startup: File system loads the Disc Label into RAM; auto-executes specified application file (e.g., via OS-9 kernel).
  • Capacity Variation: 540,000-681,000 sectors (depending on Form 1/2 mix; Form 1 for reliable data, Form 2 for media).
  • Compatibility Extensions: Supports CD-i Bridge (hybrid with ISO 9660 for PC/CD-i dual use) and CD-i Ready (hidden CD-i track on audio CDs).

These properties ensure real-time performance for interactive multimedia while maintaining a structured hierarchy.

Due to the proprietary and obsolete nature of the OptImage .CD format (developed for internal Philips authoring), public direct downloads of authentic .CD files are extremely rare and not readily available in open archives. Most CD-i disc images are distributed in .bin/.cue or .iso formats instead. After extensive searching across archives like Internet Archive, Redump, and CD-i enthusiast sites, no verifiable direct .CD files were found. As alternatives for testing CD-i content (convertible to raw sector reads if needed), here are two CD-i disc images (not .CD, but representative):

If true .CD files are required, they may only exist in private collections or Philips archives.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .CD Property Dump

Embed this as a custom HTML card in Ghost (via Code card or theme injection). It uses the File API for drag-and-drop in the browser, reads the .CD file as an ArrayBuffer, parses basic sector structure and volume descriptor (assuming raw CD image layout), and dumps properties to a <pre> element on screen. It checks for the "CD-I " signature and extracts Disc Label basics if present. Full OS-9 parsing is simplified to key properties.

Drag and drop a .CD file here to parse its properties.

Drop .CD file here

4. Python Class for .CD Handling

This class opens a .CD file in binary mode, parses sectors, decodes key properties (volume descriptor, Disc Label basics), prints them to console. For write, it creates a copy with updated Disc Label title (example). Uses struct for binary parsing. Assumes raw sector layout.

import struct
import os

class CDHandler:
    def __init__(self, filename):
        self.filename = filename
        self.properties = {}
        self.buffer = None

    def read(self):
        with open(self.filename, 'rb') as f:
            self.buffer = f.read()
        self.decode()
        self.print_properties()

    def decode(self):
        if not self.buffer:
            return
        sector_size = 2352
        num_sectors = len(self.buffer) // sector_size
        # Skip to approx PVD (after messages/label)
        start_sector = min(2400, num_sectors - 1)
        pvd_offset = start_sector * sector_size + 16  # PVD start
        sig_offset = pvd_offset + 8
        if sig_offset + 5 <= len(self.buffer):
            sig = self.buffer[sig_offset:sig_offset + 5].decode('ascii', errors='ignore').rstrip('\x00 ')
            self.properties['signature'] = sig
            if sig == 'CD-I ':
                self.properties['fileSystemType'] = 'OS-9 variant (CD-i)'
                self.properties['sectorFormat'] = 'Mode 2 (2352 bytes)'
                self.properties['maxFilenameLength'] = 28
                self.properties['caseSensitivity'] = 'Mixed case, no case-insensitive duplicates'
                self.properties['directoryDepth'] = 'Unlimited'
                self.properties['permissions'] = 'Unix-like (rwx for owner/group/other)'
                # Sample Disc Label extraction (simplified)
                label_offset = 166 * sector_size
                if label_offset < len(self.buffer):
                    title = self.buffer[label_offset + 40:label_offset + 72].decode('ascii', errors='ignore').rstrip('\x00')
                    self.properties['discTitle'] = title
                    app_id = self.buffer[label_offset + 200:label_offset + 228].decode('ascii', errors='ignore').rstrip('\x00')
                    self.properties['applicationID'] = app_id
                self.properties['interleavingSupport'] = 'Yes (real-time audio/video)'
                self.properties['volumeCapacityMB'] = f"{(num_sectors * 2324 / 1024 / 1024):.0f} (approx)"
            else:
                self.properties['error'] = 'Not a valid CD-i .CD image'
        else:
            self.properties['error'] = 'File too small'

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write(self, output_filename, new_title=None):
        if new_title:
            # Example: Update Disc Label title (simplified; real write needs full sector rebuild)
            label_offset = 166 * 2352 + 40
            self.buffer = bytearray(self.buffer)
            title_bytes = new_title.encode('ascii')[:32].ljust(32, b'\x00')
            self.buffer[label_offset:label_offset + 32] = title_bytes
        with open(output_filename, 'wb') as f:
            f.write(self.buffer)
        print(f"Written updated .CD to {output_filename}")

# Usage
if __name__ == "__main__":
    handler = CDHandler("example.cd")
    handler.read()
    handler.write("updated.cd", new_title="Updated Disc Title")

5. Java Class for .CD Handling

This class uses FileInputStream to read the .CD file, parses bytes for properties, prints to console. For write, copies the file and updates a sample field. Uses ByteBuffer for efficiency.

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

public class CDHandler {
    private String filename;
    private byte[] buffer;
    private java.util.Map<String, Object> properties = new java.util.HashMap<>();

    public CDHandler(String filename) {
        this.filename = filename;
    }

    public void read() throws IOException {
        try (FileInputStream fis = new FileInputStream(filename);
             FileChannel channel = fis.getChannel()) {
            buffer = new byte[(int) channel.size()];
            channel.read(ByteBuffer.wrap(buffer));
        }
        decode();
        printProperties();
    }

    private void decode() {
        if (buffer == null) return;
        int sectorSize = 2352;
        int numSectors = buffer.length / sectorSize;
        int startSector = Math.min(2400, numSectors - 1);
        int pvdOffset = startSector * sectorSize + 16;
        int sigOffset = pvdOffset + 8;
        if (sigOffset + 5 <= buffer.length) {
            String sig = new String(buffer, sigOffset, 5, StandardCharsets.US_ASCII).trim();
            properties.put("signature", sig);
            if ("CD-I ".equals(sig)) {
                properties.put("fileSystemType", "OS-9 variant (CD-i)");
                properties.put("sectorFormat", "Mode 2 (2352 bytes)");
                properties.put("maxFilenameLength", 28);
                properties.put("caseSensitivity", "Mixed case, no case-insensitive duplicates");
                properties.put("directoryDepth", "Unlimited");
                properties.put("permissions", "Unix-like (rwx for owner/group/other)");
                // Sample Disc Label
                int labelOffset = 166 * sectorSize;
                if (labelOffset < buffer.length) {
                    String title = new String(buffer, labelOffset + 40, 32, StandardCharsets.US_ASCII).trim();
                    properties.put("discTitle", title);
                    String appId = new String(buffer, labelOffset + 200, 28, StandardCharsets.US_ASCII).trim();
                    properties.put("applicationID", appId);
                }
                properties.put("interleavingSupport", "Yes (real-time audio/video)");
                properties.put("volumeCapacityMB", String.format("%.0f (approx)", (numSectors * 2324.0 / 1024 / 1024)));
            } else {
                properties.put("error", "Not a valid CD-i .CD image");
            }
        } else {
            properties.put("error", "File too small");
        }
    }

    private void printProperties() {
        properties.forEach((k, v) -> System.out.println(k + ": " + v));
    }

    public void write(String outputFilename, String newTitle) throws IOException {
        if (newTitle != null) {
            int labelOffset = 166 * 2352 + 40;
            byte[] titleBytes = newTitle.getBytes(StandardCharsets.US_ASCII);
            byte[] padded = new byte[32];
            System.arraycopy(titleBytes, 0, padded, 0, Math.min(titleBytes.length, 32));
            System.arraycopy(padded, 0, buffer, labelOffset, 32);
        }
        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            fos.write(buffer);
        }
        System.out.println("Written updated .CD to " + outputFilename);
    }

    // Usage example
    public static void main(String[] args) throws IOException {
        CDHandler handler = new CDHandler("example.cd");
        handler.read();
        handler.write("updated.cd", "Updated Disc Title");
    }
}

6. JavaScript Class for .CD Handling (Node.js)

This Node.js class uses fs to read the .CD file synchronously, parses buffer, prints properties to console. For write, updates and saves a copy. Run with node script.js.

const fs = require('fs');

class CDHandler {
  constructor(filename) {
    this.filename = filename;
    this.properties = {};
    this.buffer = null;
  }

  read() {
    this.buffer = fs.readFileSync(this.filename);
    this.decode();
    this.printProperties();
  }

  decode() {
    if (!this.buffer) return;
    const sectorSize = 2352;
    const numSectors = this.buffer.length / sectorSize;
    const startSector = Math.min(2400, numSectors - 1);
    const pvdOffset = startSector * sectorSize + 16;
    const sigOffset = pvdOffset + 8;
    if (sigOffset + 5 <= this.buffer.length) {
      const sig = this.buffer.toString('ascii', sigOffset, sigOffset + 5).trim();
      this.properties.signature = sig;
      if (sig === 'CD-I ') {
        this.properties.fileSystemType = 'OS-9 variant (CD-i)';
        this.properties.sectorFormat = 'Mode 2 (2352 bytes)';
        this.properties.maxFilenameLength = 28;
        this.properties.caseSensitivity = 'Mixed case, no case-insensitive duplicates';
        this.properties.directoryDepth = 'Unlimited';
        this.properties.permissions = 'Unix-like (rwx for owner/group/other)';
        // Sample Disc Label
        const labelOffset = 166 * sectorSize;
        if (labelOffset < this.buffer.length) {
          const title = this.buffer.toString('ascii', labelOffset + 40, labelOffset + 72).trim();
          this.properties.discTitle = title;
          const appId = this.buffer.toString('ascii', labelOffset + 200, labelOffset + 228).trim();
          this.properties.applicationID = appId;
        }
        this.properties.interleavingSupport = 'Yes (real-time audio/video)';
        this.properties.volumeCapacityMB = Math.round(numSectors * 2324 / 1024 / 1024) + ' (approx)';
      } else {
        this.properties.error = 'Not a valid CD-i .CD image';
      }
    } else {
      this.properties.error = 'File too small';
    }
  }

  printProperties() {
    Object.entries(this.properties).forEach(([key, value]) => {
      console.log(`${key}: ${value}`);
    });
  }

  write(outputFilename, newTitle = null) {
    let buf = Buffer.from(this.buffer);
    if (newTitle) {
      const labelOffset = 166 * 2352 + 40;
      const titleBytes = Buffer.from(newTitle, 'ascii');
      const padded = Buffer.alloc(32, 0);
      titleBytes.copy(padded, 0, 0, Math.min(titleBytes.length, 32));
      padded.copy(buf, labelOffset);
    }
    fs.writeFileSync(outputFilename, buf);
    console.log(`Written updated .CD to ${outputFilename}`);
  }
}

// Usage
const handler = new CDHandler('example.cd');
handler.read();
handler.write('updated.cd', 'Updated Disc Title');

7. C Class (Struct) for .CD Handling

This standard C implementation uses fopen/fread to read the .CD file into memory, parses properties, prints to stdout. For write, updates memory buffer and writes to file. Compile with gcc -o handler handler.c. Assumes <stdio.h>, <stdlib.h>, <string.h>.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char filename[256];
    unsigned char* buffer;
    size_t file_size;
    char properties[10][256];  // Simple key-value array
    int prop_count;
} CDHandler;

void init_handler(CDHandler* h, const char* fn) {
    strcpy(h->filename, fn);
    h->buffer = NULL;
    h->file_size = 0;
    h->prop_count = 0;
}

void add_property(CDHandler* h, const char* key, const char* value) {
    snprintf(h->properties[h->prop_count], 256, "%s: %s", key, value);
    h->prop_count++;
}

void read_file(CDHandler* h) {
    FILE* f = fopen(h->filename, "rb");
    if (!f) {
        printf("Error opening file\n");
        return;
    }
    fseek(f, 0, SEEK_END);
    h->file_size = ftell(f);
    fseek(f, 0, SEEK_SET);
    h->buffer = malloc(h->file_size);
    fread(h->buffer, 1, h->file_size, f);
    fclose(f);
    decode(h);
    print_properties(h);
}

void decode(CDHandler* h) {
    if (!h->buffer) return;
    int sector_size = 2352;
    int num_sectors = h->file_size / sector_size;
    int start_sector = (num_sectors - 1 < 2400) ? num_sectors - 1 : 2400;
    int pvd_offset = start_sector * sector_size + 16;
    int sig_offset = pvd_offset + 8;
    if (sig_offset + 5 <= h->file_size) {
        char sig[6] = {0};
        memcpy(sig, h->buffer + sig_offset, 5);
        sig[5] = '\0';
        add_property(h, "signature", sig);
        if (strcmp(sig, "CD-I ") == 0) {
            add_property(h, "fileSystemType", "OS-9 variant (CD-i)");
            add_property(h, "sectorFormat", "Mode 2 (2352 bytes)");
            add_property(h, "maxFilenameLength", "28");
            add_property(h, "caseSensitivity", "Mixed case, no case-insensitive duplicates");
            add_property(h, "directoryDepth", "Unlimited");
            add_property(h, "permissions", "Unix-like (rwx for owner/group/other)");
            // Sample Disc Label
            int label_offset = 166 * sector_size;
            if (label_offset < h->file_size) {
                char title[33] = {0};
                memcpy(title, h->buffer + label_offset + 40, 32);
                title[32] = '\0';
                for (int i = 0; i < 32; i++) if (!title[i]) title[i] = '\0';  // Trim nulls
                char title_prop[256];
                snprintf(title_prop, 256, "discTitle: %s", title);
                strcpy(h->properties[h->prop_count++], title_prop);
            }
            add_property(h, "interleavingSupport", "Yes (real-time audio/video)");
            char cap[32];
            snprintf(cap, 32, "%.0f (approx)", (num_sectors * 2324.0 / 1024 / 1024));
            add_property(h, "volumeCapacityMB", cap);
        } else {
            add_property(h, "error", "Not a valid CD-i .CD image");
        }
    } else {
        add_property(h, "error", "File too small");
    }
}

void print_properties(CDHandler* h) {
    for (int i = 0; i < h->prop_count; i++) {
        printf("%s\n", h->properties[i]);
    }
}

void write_file(CDHandler* h, const char* output_fn, const char* new_title) {
    if (new_title) {
        h->buffer = realloc(h->buffer, h->file_size);  // Ensure mutable
        int label_offset = 166 * 2352 + 40;
        unsigned char title_bytes[32] = {0};
        int len = strlen(new_title);
        if (len > 32) len = 32;
        memcpy(title_bytes, new_title, len);
        memcpy(h->buffer + label_offset, title_bytes, 32);
    }
    FILE* f = fopen(output_fn, "wb");
    if (f) {
        fwrite(h->buffer, 1, h->file_size, f);
        fclose(f);
        printf("Written updated .CD to %s\n", output_fn);
    }
}

void free_handler(CDHandler* h) {
    if (h->buffer) free(h->buffer);
}

int main() {
    CDHandler h;
    init_handler(&h, "example.cd");
    read_file(&h);
    write_file(&h, "updated.cd", "Updated Disc Title");
    free_handler(&h);
    return 0;
}