Skip to content

RZX

RZX is an input recording format for the ZX Spectrum. Rather than storing sound or timing data like the tape formats, it records the fetch counts and I/O port reads needed to replay a session deterministically against an emulated machine, optionally alongside an embedded snapshot of the state to start from.

An RZX file is a sequence of typed blocks: creator information identifying the tool that made the recording, an optional snapshot, and one or more input recording blocks containing the recorded frames.

Details about the RZX format can be found at https://worldofspectrum.net/RZXformat.html.

API

Class Description
RzxFormat Singleton format for reading and writing RZX files.
RzxFile Represents an RZX file as a header and a list of blocks.
RzxHeader The file header, including the RZX version.
RzxBlock Base class for all RZX blocks.
RzxCreatorBlock Identifies the tool that created the recording.
RzxSnapshotBlock An embedded snapshot to start replay from.
RzxInputRecordingBlock A recording of input frames.
RzxInputFrame A single frame's fetch count and I/O port reads within a recording.

Reading and Writing

// Read
using var stream = File.OpenRead("recording.rzx");
RzxFile rzx = RzxFormat.Instance.Read(stream);

// Write
using var output = File.Create("output.rzx");
rzx.Write(output);

Security blocks (used to sign competition entries) are not supported. Reading a file containing one throws a NotSupportedException.

Accessing Blocks

An RzxFile exposes its raw Blocks list, plus convenience properties for the block types every valid recording has:

Console.WriteLine($"Created by: {rzx.Creator.Creator} {rzx.Creator.MajorVersion}.{rzx.Creator.MinorVersion}");

if (rzx.Snapshot is { } snapshot)
{
    Console.WriteLine($"Starting snapshot: .{snapshot.Extension}, {snapshot.SnapshotData.Count} bytes");
}

foreach (var recording in rzx.InputRecordings)
{
    Console.WriteLine($"Recording: {recording.Frames.Count} frames from T-state {recording.StartTStates}");
}

Creator throws if the file has no creator block; Snapshot is null if the file has none.

Each RzxInputFrame carries the number of instruction fetches performed and the bytes returned by I/O port reads during that frame, or indicates that it repeats the previous frame's port reads unchanged:

foreach (var frame in recording.Frames)
{
    if (frame.RepeatsPreviousInputReads)
    {
        Console.WriteLine($"{frame.FetchCount} fetches, repeats previous reads");
    }
    else
    {
        Console.WriteLine($"{frame.FetchCount} fetches, {frame.InputReads.Count} port reads");
    }
}

Creating RZX Files

Build up the blocks and pass them to the RzxFile constructor. A creator block and at least one input recording block are required:

var creator = new RzxCreatorBlock("OakIO", majorVersion: 1, minorVersion: 0);

var frames = new[]
{
    new RzxInputFrame(fetchCount: 1000, inputReads: [0xFF]),
    new RzxInputFrame(fetchCount: 1000, repeatsPreviousInputReads: true),
};
var recording = new RzxInputRecordingBlock(frames);

RzxFile rzx = new([creator, recording]);

An optional RzxSnapshotBlock embeds a snapshot to start replay from, identified by its filename extension:

byte[] snapshotBytes = z80.ToByteArray();
var snapshot = new RzxSnapshotBlock("z80", snapshotBytes);

RzxFile rzx = new([creator, snapshot, recording]);

Compressed and external-data snapshot blocks, and protected input recording blocks, can be read but not created — only uncompressed, embedded, unprotected blocks can be written.

Conversions

RZX recordings do not participate in conversion — no converters are registered to or from the format. See Reading, Writing and Converting.