EZX snap format

(Ñ) by Vladimir Kladov, 2003

This format is intended to use it in EmuZWin 2 Spectrum emulator. It allows to save exact state of Spectrum machine, including current registers, memory and other internal data, and also tape state, debug environment, pokes, keyboard redirection and other necessary information.

EZX must be started with four characters signature 'Emuz', and always should have extension 'EZX'. The first part of the file contains fixed part, representing current Spectrum machine snapshot. This part of a file should always be read (except may be a case when only a screen requested for load). It's structure (including signature) is:

TSpecData = packed Record
Signature : array[ 0..3 ] of Char; // 'Emuz'
ROM0, ROM1: TMemoryBank; // ROM0=S128 rom; ROM1=S48 rom
RAMs      : array[ 0..7 ] of TMemoryBank;
Lock7FFD  : Boolean;     // true, if S48K only
State     : TSpecState;  // registers, etc.
Sound     : TSoundChipState;  // sound chip AY state
ReleaseDescriptor: DWORD;     // not used
Tape      : TTapeData;        // current tape reader state
end;

Substructures used are defined as follows:

TMemoryBank = array[ 0..16383 ] of Byte;

TSpecState = packed Record
AF, BC, DE, HL, IX, IY,
AFalt, BCalt, DEalt, HLalt, 
PC, SP               : Word;
I, R, HiddenReg      : Byte;
IFF1, IFF2, IntSignal: Boolean;
ImMode               : Byte;
TactsFromLastInt     : DWord;
BankROM_0000,                   // 0=rom S128, 1=rom S48
BankRAM_C000,                   // 0..7 
BankVideo            : Byte;    // 0=RAM Bank 5, 1=RAM Bank 7
BorderColor          : Byte;    // 0..7
MIC                  : Boolean; 
JustAfterEI          : Boolean; // Interrupt disable for a single instruction if TRUE
Flash                : Boolean; // TRUE if flushed
FramesFromLastFlashSwitch: Byte; // 0..15
end;

TSoundChipState = packed record
LastFFFD             : Byte;
Regs                 : array[ 0..15 ] of Byte;
end;

TTapeData = packed record
    TapeImgData: Pointer;
    TapeImgLen: Integer;
    TapePosition: Integer;
    PulseLength: Integer; // current pulse length
    TactsLeast: Integer;  // how many tacts current pulse should continue
    BitIndex: Integer;
    CurByte: Byte;
    Pilot: Integer;       // when LoadSpectrum called, 1 if to double tone except SpeedLock1 & 2
    Active: Boolean;
end;

The last structure should be ignored if following chunks certain to tape image and tape reader state are not loaded.

Other chunks always contains four-byte chunk name and its data length to allow skipping unsupported chunks.


'TAPC' chunk - call stack of tape recorder:

offset size   description
0      4      name         = 'TAPC'
4      4      size         = size of data (must be a multiple of 4)
8      size   size/4 dwords (return positions)
Data of this chunk has its own format, may be not so compact, but
very easy to implement and therefore allowing to represent any tape
recording in exact pilses. See description of tape format below.

'TAPL' chunk - loop stack of tape recorder:

offset size   description
0      4      name         = 'TAPL'
4      4      size         = size of data (must be a multiple of 8)
8      size   size/8 qwords (counters and correspondent jump positions)

'INIT' chunk - initial state of the Spectrum machine
Actually, dat of this chunk has exactly the same format as the first fixed
data structure. It can be used to re-play game without loading it again
from the snap/tape file.

offset size   description
0      4      name         = 'INIT'
4      4      size         = size of data (always = Sizeof(TSpecData))
8      Sizeof(TSpecData)   copy of TSpecData structure

'ITPC' chunk - call stack of tape recorder in its initial state:

offset size   description
0      4      name         = 'ITPC'
4      4      size         = size of data (must be a multiple of 4)
8      size   size/4 dwords (return positions)

'ITPL' chunk - loop stack of tape recorder in its initial state:

offset size   description
0      4      name         = 'ITPL'
4      4      size         = size of data (must be a multiple of 8)
8      size   size/8 qwords (counters and correspondent jump positions)

'POKS' chunk - a list of pokes supplied:

offset size   description
0      4      name         = 'POKS'
4      4      size         = size of entire data
8      Sequence of strings (separated with #13 and #10 as usual text file)
       in .POK-file format.

'BRKS' chunk - a list of break points for the debugger:

offset size   description
0      4      name        = 'BRKS'
4      4      size        = size of entire data
8      4      N = number of TBreakPoint structures
12     ...    array of TBreakPoint structures

where TBreakPoint is defined as follows:

TPoke = packed record
  MemBank   : Byte;    // 0=ROM0, 1=ROM1, 10H..17H=RAM0..RAM7
  Addr      : Word;    // 0000H..3FFFH in a bank
  Counter   : DWORD;
  StopWhen  : Byte;
  CondCount : Byte;
  Conditions: array[1..CondCount] of array[0..31] of Char;
end;

each condition is a string of format <TARGET>[<MASKorOFFSET>]<OP><VALUE>
where <TARGET> ::= { A | B | BC | (BC) | C | D | DE | (DE) | E | F |
 H | HL | (HL) | I | IFF1 | IFF2 | IM | IX | (IX) | IY | (IY) | L |
 (Addr) | PC | (PC) | R | RAM | ROM | SP | TState }
if target is (IX) or (IY), <MASKorOFFSET> is used as an offset and
is written like (IX+XXXX) (X=hexadecimal digit). If target is (Addr),
<MASKorOFFSET> also used to indicate an addr (in form (XXXX)) Otherwise it is used as
a mask and is written like &XXXX or &XX;
      <OP> ::= { = | <> | < | <= | > | >= };
      <VALUE> always is hexadecimal byte or word written like XX or XXXX.

'LABS' chunk - a list of labels for the debugger:

offset size   description
0      4      name        = 'LABS'
4      4      size        = size of entire data
8      4      N = number of TLabelDef structures
12     ...    array of TLabelDef structures

where TLabelDef is defined as follows:

TLabelDef = packed record
  MemBank   : Byte;
  Addr      : Word;
  LabelName : array of Char; // until #00
end;

LabelName can be any ASCII.

'ASMZ' chunk - a text in assembler window:

offset size   description
0      4      name         = 'ASMZ'
4      4      size         = size of entire data
8      size   text (lines are separated with #13#10, the text is finished with #00)

'KEYS' chunk - key re-definition table.
EZX file can contain several such tables, each with its own name.

offset size   description
0      4      name = 'KEYS'
4      4      size = size of entire data
8      N * Sizeof(TKeyMap) = array of key definitions

where each TKeyMap is defined as follows:

  TKeyMap = packed Record
    PCKey: Byte;
    ZXKey1: DWORD;
    ZXKey2: DWORD;
    Style: TPressStyle;
    Timer: Integer; // time to press key(s) again (pressLoop style)
    Hold: Integer;  // time to hold key(s) pressed (pressAutoUp, pressLoop)
  end;
TPressStyle = ( pressNormal, pressFixed, pressAutoUp, pressLoop );
ZXKeyN fields contains ZX Spectrum key coded as follows:
const
     kCapsShft = $0FE; kZ     = $0FD; kX     = $0FB; kC     = $0F7; kV     = $0EF;
     kA        = $1FE; kS     = $1FD; kD     = $1FB; kF     = $1F7; kG     = $1EF;
     kQ        = $2FE; kW     = $2FD; kE     = $2FB; kR     = $2F7; kT     = $2EF;
     k1        = $3FE; k2     = $3FD; k3     = $3FB; k4     = $3F7; k5     = $3EF;
     k0        = $4FE; k9     = $4FD; k8     = $4FB; k7     = $4F7; k6     = $4EF;
     kP        = $5FE; kO     = $5FD; kI     = $5FB; kU     = $5F7; kY     = $5EF;
     kEnter    = $6FE; kL     = $6FD; kK     = $6FB; kJ     = $6F7; kH     = $6EF;
     kSpace    = $7FE; kSymShft = $7FD; kM   = $7FB; kN     = $7F7; kB     = $7EF;
     jR        = $8FE; jL     = $8FD; jD     = $8FB; jU     = $8F7; jFire  = $8EF;
     k_        = $FFFF; (no key)

'COLR' chunk - color adjustment table:

EZX file can contain several such tables, each with its own name.
See also rules for 'KEYS' chunk (above).

offset size   description
0      4      name = 'COLR'
4      4      size = size of entire data = Length(name)+1+16*3
8      Sizeof(TColorTable) - a table of colors to use it to represent screen.

TColorTable = packed record 
NormalBlack, NormalRed, NormalBlue, NormalMagenta,
NormalGreen, NormalCyan, NormalYellow, NormalWhite,
BrightBlack, BrightRed, BrightBlue, BrightMagenta,
BrightGreen, BrightCyan, BrightYellow, BrightWhite: TRGB;
end;

TRGB = packed record
  R, G, B: Byte;
end;

'LDIR' chunk - flag to allow fast LDIR/LDDR/CPIR/CPDR emulation:

offset size   description
0      4      name = 'LDIR'
4      4      size = 1 (size of entire data)
8      1      flag = 0 LDIR/LDDR/CPIR/CPDR fast emulation disabled,
                     1 enabled (default)
In EmuZWin, fast LDIR/... emulation does not affect emulation 
accuracy though, so it can be left turned on always.

DESCRIPTION OF TAPE FORMAT USED INTERNALLY IN EZX FILE FORMAT

Tape data contains blocks of variable length, identifying by the first byte. Format of each block is very different, but it is not planned to be extended or changed later, since all possible requirements can be expressed using existing blocks.


ID=0 Standard Data Block (TAP)

offset size   description
0      2      Length of Data
2      Length Data

Such block does not require pilot or synchronization defined separately (these are generated before the block). But if a pause needed after the block it should be defined as a separate tones block.


ID=1 Tones Data Block

offset size   description
0      2      Length of pulse in Spectrum T-states (1/350000 sec)
2      4      Tones count (actually bits count in following data)
6      (TonesCount+7)/8 bytes. Each bit represents one tone of
       high (1) or low (0) level. Bits in each byte are scanned
       from b7 to b0.


ID=2 Stop Command

No data. Tape is stopped until user click toolbar button "Play".


ID=3 Jump

offset size   description
0      4      Integer offset relative to start position of the
              block (just after ID byte).

Changes order of block to read. A byte pointed by the offset must be ID of the block to jump to.


ID=4 Loop start

offset size   description
0      4      Loop count.


ID=5 Loop end

No data.


ID=6 Call

offset size   description
0      4      Integer offset to a block called.


ID=7 Return from call

No data.


ID=8 Menu to display or text

offset size   description
0      1      Number of menu items. If 1, this block is just
              Text (can be used to place a description in a
              catalog).
1      4      Jump offset (relative to size byte, located just
              after ID byte).
5      Zero-terminated ASCII string.
x      4      Jump offset for second menu item.
..............


ID=9 Jump if S128

offset size   description
0      4      Integer jump offset.


ID=0A Message to display

offset size   description
0      2      Time to display message (0 - until OK pressed).
2      Zero-terminated ASCII string (#0D character to separate
       lines if necessary).


ID=0B Wait until IN (FE)

No data. This command can be added to stop playing tape until data from port FE requested from the Spectrum.