# EZX snap format

(Ñ) by Vladimir Kladov, 2003

This format is intended to use it in E m u Z Win 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.

    offset size   description
    0      4      name = 'KEYS'
    4      4      size = size of entire data
    8      Sum(i=1..N: Sizeof(TKeyMap) or Sizeof(TKeyMapOld) ) =
    	      array of key definitions

    where each TKeyMap or TKeyMapOld is defined as follows:

      TKeyMapOld = 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;

      TKeyMap = packed Record
        PCKey: Word;
        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;

    If the first byte of a record is 3A or 3B, new TKeyMap is present,
    otherwise it is the TKeyMapOld (which differ by the first field length).
    New TKeyMap intruduced to allow representing Joystick1 (3A) and Joystick2 (3B)
    directions and keys:

Function | Joystick1 | Joystick2
Up | 013A | 013B
Down | 023A | 023B
Left | 033A | 033B
Right | 043A | 043B
Fire (Button1) | 053A | 053B
Button2 | 063A | 063B
...
Button32 | 243A | 243B

    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:

    offset size   description
    0      4      name = 'COLR'
    4      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.

See also block ID=0C (Pure Data) below.

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.

ID=0C Pure data

offset size description
0 2 L0 = Length of pulse correspondent to value "0"
in Spectrum T-states (1/350000 sec)
2 2 L1 = Length of pulse correspondent to value "1" in Spectrum T-states (1/350000 sec)
4 4 Tones count (actually bits count in following data)
8 (TonesCount+7)/8 bytes. Each bit represents one encoded value
0 or 1, each of those will be decoded to a pare of low and high
level pulse each of correspondent length (L0 for value 0, L1 for
value 1). Bits in each byte are scanned from b7 to b0.

This command is added in version 2.1 (release 2.0) and is useful to represent TZX Pure Data Block or data part of Turbo Loading Data Block, especially in case when Zero Pulse Length and One Pulse Length are not relative to each other as p:q (p,q=1..8), but are very far from such releation, e.g. 1:1,8556.