The ZXS File Format


With version 0.99 of zx32 a new file format (ZXS) was introduced, and enhanced in versions 1.01 and 1.02. This is meant to replace all the other Spectrum-related file formats in existence (NOT!J). ZXS files use the Resource Interchange File Format (RIFF). The following is an excerpt adapted from the Microsoft® Multimedia API (MAPI) Programmer's Guide.

RIFF files use four-character codes to identify file elements. These codes are 32-bit quantities representing a sequence of one to four ASCII alphanumeric characters, padded on the right with space characters.
The basic building block of a RIFF file is a chunk. A chunk is a logical unit of multimedia data, such as a single frame in a video clip. Each chunk contains the following fields: The following illustration shows a "RIFF" chunk that contains two subchunks.

A chunk contained in another chunk is a subchunk. The only chunks allowed to contain subchunks are those with a chunk identifier of "RIFF" or "LIST". A chunk that contains another chunk is called a parent chunk. The first chunk in a RIFF file must be a "RIFF" chunk. All other chunks in the file are subchunks of the "RIFF" chunk.
"RIFF" chunks include an additional field in the first four bytes of the data field. This additional field provides the form type of the field. The form type is a four-character code identifying the format of the data stored in the file. For example, Microsoft waveform-audio files have a form type of "WAVE".
"LIST" chunks also include an additional field in the first four bytes of the data field. This additional field contains the list type of the field. The list type is a four-character code identifying the contents of the list. For example, a "LIST" chunk with a list type of "INFO" can contain "ICOP" and "ICRD" chunks providing copyright and creation date information. The following illustration shows a "RIFF" chunk that contains a "LIST" chunk and one other subchunk (the "LIST" chunk contains two subchunks).

ZXS files have a form type of "SNAP". Subchunks that are defined, for now, are:

"fmtz"

File format information

#define ZXM_DEFAULT_    0
#define ZXM_048K____    0x0010  // ZX Spectrum 48K (Model 2)
#define ZXM_048K_P__    0x0020  // ZX Spectrum +   (Model 3)
#define ZXM_128K____    0x0030  // ZX Spectrum 128
#define ZXM_128K_P2_    0x0040  // ZX Spectrum +2
#define ZXM_128K_P2A    0x0050  // ZX Spectrum +2A
#define ZXM_128K_P3_    0x0060  // ZX Spectrum +3

#define ZXH_NONSTANDARD 0x0001  // peripheral hardware

#define ZIP_STORED      0       // compression methods
#define ZIP_SHRUNK      0x0001
#define ZIP_REDUCED1    0x0002
#define ZIP_REDUCED2    0x0003
#define ZIP_REDUCED3    0x0004
#define ZIP_REDUCED4    0x0005
#define ZIP_IMPLODED    0x0006
#define ZIP_TOKENIZED   0x0007
#define ZIP_DEFLATED    0x0008
#define ZIP_NOTZIPPED   0xFFFF

typedef struct _ZIPCHUNKHEADER
               {
               DWORD dwSize     // structure size
               DWORD dwCRC32;   // crc
               DWORD dwLength;  // uncompressed size
               } ZIPCHUNKHEADER;

typedef struct _FMTZ
               {
               WORD wVersion;
               WORD wHardwareModel;
               WORD wHardwareFlags;
               WORD wCompressionMethod;
               } FMTZ;

wVersion is the file format version, with the high-order byte specifying the major version number and the low-order byte specifying the minor version (revision). The current file format version is 0x0101 (i.e., 1.01). In general, an emulator shouldn't attempt to read the rest of the subchunks if the major file version is greater than the one it knows about. The user should be warned if the minor file version is greater than the one supported. Additionally, the value stored should be the lowest possible. For example if disk subchunks are not present 0x0100 (1.00) should be used.
wHardwareModel specifies the Spectrum model. If a Model 3 48K Spectrum is emulated this value should be ZXM_048K_P__. If this value is undefined (model-specific subchunks are not present), then ZXM_DEFAULT_ should be stored.
wHardwareFlags is used to represent the existence of any peripheral hardware devices like the Sinclair Interface I. Only ZXH_NONSTANDARD can be specified in v1.01-. If any non-standard device is actually used, not just enabled, emulators should set this flag and store any device-specific data in their own subchunk (refer to the definition of the "zx32" subchunk, later on, for details).
wCompressionMethod specifies the ZIP-compatible method used to compress data subchunks (RAM pages, the tape image and the disk images in v1.01-). If this value is not ZIP_NOTZIPPED, then all data subchunks should be compressed and stored prefixed by a ZIPCHUNKHEADER structure.

"rZ80"

Z80 processor registers

typedef struct _RZ80
               {
               WORD   zFA; WORD   zBC;
               WORD   zDE; WORD   zHL;
               WORD  _zFA; WORD  _zBC;
               WORD  _zDE; WORD  _zHL;
               WORD   zIX; WORD   zIY;
               WORD   zPC; WORD   zSP;

               BYTE    zI; BYTE    zR;
               BYTE zIFF1; BYTE zIFF2;
               BYTE   zIM;

               DWORD dwTicks;
               } RZ80;

dwTicks is the number of the Z80 clock ticks elapsed since the last interrupt occurred. The function of the other members of this structure should be obvious.

"r048"

48K I/O buffer

typedef struct _R048
               {
               BYTE bufULA;
               BYTE bufKey[8];
               } R048;

bufULA holds the last byte written to a ULA port and can be used to determine the border color.
The lowest five bits of each byte in bufKey represent the state of the corresponding half-row of Spectrum keys. If a bit is set the key is pressed.

"r128"

128K I/O buffer

typedef struct _R128
               {
               BYTE buf7FFD;
               BYTE bufFFFD;
               BYTE bufPSG[16];
               } R128;

buf7FFD holds the last value sent to port 0x7FFD and can be used to determine the memory organization in effect.
bufFFFD is the last byte written to the AY-3-8912 control port and bufPSG holds the values in the corresponding registers of this chip.

"r+3 "

+2A|+3 I/O buffer

typedef struct _RPL3
               {
               BYTE buf1FFD;
               } RPL3;

buf1FFD holds the last value sent to port 0x1FFD and can be used to determine the extended memory organization in effect.
This structure will be expanded in the future to include status buffers for ports 0x2FFD and 0x3FFD (the FDC control and data ports).

"ram0"..
"ram7"

Memory pages

For 48K snapshots "ram5", "ram2" and "ram0" subchunks should be present. They should hold the memory contents at address 0x4000, 0x8000 and 0xC000 respectively.
For 128K snapshots all memory pages must be present, even if they are empty.

Note Memory pages are data subchunks and may be compressed.

"tape"

Tape image

typedef struct _TAPE
               {
               DWORD dwCurrentPosition;
               DWORD dwRemainBlockLength;
               DWORD dwDataSize;
               BYTE  cbData[1];
               } TAPE;

The format utilized for cbData is the TAP file format, as defined by Gerton Lunter for the Sinclair ZX Spectrum Emulator Z80, and adopted by many other emulators.
dwDataSize holds the size of cbData. dwCurrentPosition is the offset in cbData of the next byte to be read and dwRemainBlockLength represents the byte count from that position until the end of the current block.

Note The tape image is a data subchunk and may be compressed.

"dsk0"..
"dsk1"

Disk images

These should reflect disks inserted in drive 0 and 1 respectively. The presence of "dsk1" is not recommended unless absolutely necessary. In fact it shouldn't be present unless "dsk0" already exists.
I'm not making the same mistake I did, in defining extra data and a specific format for the "tape" chunk, again. Any file format can be used as long as its first 4 bytes allow it to be uniquely identified. Disk-controller-specific data like head position and motor status will be defined in the future either by expanding the "r+3 " structure or in a separate "rfdc" subchunk.
zx32 currently supports DSK (signature "MV -") and uncompressed CPD (signature "NORM") data formats. These are used in Marco Vieth's CPCEMU and Bernd Schmidt's CPE Amstrad CPC emulators, respectively. The DSK format is highly recommended since it's very versatile and more widely supported.

Note Disk images are data subchunks and may be compressed.

"zx32"

Reserved for use by zx32

Other emulators can reserve a subchunk for their own requirements, provided that the name selected:
  • matches closely the name of the emulator, and
  • is not in conflict with the ones already defined here, or may be defined in the future for common use. For example, "rfdc" and "mdr " are not good ideas.
In general any signature using all upper-case letters should be avoided, since these types of signatures are usually defined for use in all RIFF chunks.
Please contact me if you'd like to reserve a subchunk or define one for common use. Do NOT feel free to extend the file format to suit your needs, in any other way.

All structures are packed at an 1-byte boundary (that is, alignment of structure fields is disabled)), and values are stored with LSB first (this is inherent in the RIFF specification - subchunk sizes are stored this way, too). As any structure may be expanded in the future, data should be loaded based on the supported structure size and not the size of the subchunk. For example

mmioRead(hmmio, (HPSTR)&fmtz, ck.cksize);    // WRONG!
mmioRead(hmmio, (HPSTR)&fmtz, sizeof(FMTZ)); // CORRECT

Note also that RIFF subchunks can not have an odd size - a null byte is appended in that case. Windows MMIO procedures do this automatically, but if you are writing your own you should bear this in mind.

Subchunks may be saved in any order. None of them is required, but the presence of one may imply the presence of another. If "r048" is present then "rZ80", "ram5", "ram2" and "ram0" need be present, and "r048", "rZ80" and "ram0".."ram7" must be included with "r128". The presence of the "fmtz" subchunk is strongly recommended. It is definitely required if compression is used, a +2A or +3 is emulated or any non-standard (i.e., all of themJ) hardware peripheral is present. I have to state again here, that the file version number stored should be the lowest possible.

The ZXS file extension is not obligatory, but the "zx" prefix is, in order for the file type to be clearly related with a ZX Spectrum emulator. For example, if only the "tape" chunk is present, the extension could be ZXT or zxtape. ZXD or zxdisk are the recommended extensions for disk-only files.

The only compression methods supported by zx32 are ZIP_STORED and ZIP_DEFLATED. The rest of the compression methods are old and inefficient and their use limited by copyrights and patents. They are defined and can be used if necessary, nonetheless.

Since version 1.03, zx32 supports standard "INAM", "ICOP", "ICRD", "IGNR", "IKEY" and "ICMT" subchunks of LIST chunks with a list type of "INFO". Most of them are useful mainly to search engines; any other standard subchunk may be used; and the Comments("ICMT") subchunk should be able to cover a large area of needs. It's highly probable that no extra information subchunks will be defined, therefore.

Revision History

In version 1.01 of the file format specification "dsk0".."dsk1" subchunks are defined.
In version 1.00 "fmtz" and "r+3 " subchunks were defined.


Last Revised: February 16, 2000.
Hosted by GeoCities. Get your own Free Home Page.
Home