(Ñ) 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, inserted TR-DOS-disks (with entire content), 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.
'DISK' chunk - TR-DOS disk inserted:
This chunk is introduced in version 2.2 of EmuZWin, having TR-DOS support. A
single EZX file can contain up to four TR-DOS disks inserted (A,B,C,D).
offset size description
0 4 name = 'DISK'
4 4 size (size of entire disk
data, for disks with standard 256-byte sectors, can be calculated as 262*(sectors
count)+4+1 bytes).
8 1 Disk unit letter ('A',
'B', 'C', 'D')
9 1 Current Track #
10 1 Disk states
D0:
1-ReadOnly; D1: 1-head down; D2: 0=FM, 1=MFM
11 1 Reserved to store other unit
states
--- starting from here, content of all sectors listed excluding those
containing nulls:
12 1 Cylinder # (0..254, though 83
usually is maximum for
physical
disk, value 255 means end of disk data and
should be
the last byte in the disk image).
13 1 Head (0-down, 1-up)
14 1 Track ID stored in the
sector label (it can differ from Track # above
and
can be of any byte value.
15 1 Side # stored in the
sector label (usually the same as Head above)
16 1 Sector ID (1..16
usually, but any byte value possible).
The
same track can have several sectors with the same ID.
17 1 Sector size code (0=128 bytes,
1=256 bytes, 2=512 bytes, 3=1024bytes,
4=4096
bytes, other values can not be used, though actual data size
is
stored separately).
18 2 Control codes
20 2 Sector data length (though
usually this value = 256, it can be different for non-standart formats).
22 (SectorDataLength) Actual sector data
(usually of
256 bytes length)
--- from here, the next sector starts, ets. until Track# = FF
Other notes: order of Tracks, Sectors, Sides does not matter (though EmuZ itself stores its sorted). Also, duplicated sectors allowed (reserved for non-standard formatted disks).
'BETA' chunk - TR-DOS controller
state:
This chunk is introduced in version 2.2 of EmuZWin, having TR-DOS support.
offset size description
0 4 name = 'BETA'
4 4 size (size of entire chunk
data, 9).
8 1 D1,D0: Current disk unit
(0='A', 1='B', 2='C', 3='D');
D2: Seek
direction; D3: Head down;
D5,D4:
Current operation (0=no operation, 1=read, 2=write,
3=format - the last is not implemented,
just reserved)
D6: not
used, 0; D7: 1=TR-DOS ROM paged on
9 1 Operation Track# (0..254)
10 1 Operation Sector# (D7: Side;
D6-D0: Sector# 0..127)
11 1 Sectors count to finish
read/write operation (0..255)
12 2 Data byte offset from start of
the sector
14 2 reserved to store time (from
last interrupt in current frame) when working with current byte started, to emulate
working with disk exactly
16 1 reserved to store controller
status byte.
'PRVW' chunk - Preview bitmap
This chunk is introduced in version 2.2, and allows to pass any bitmap as a
preview image. Such bitmap should be returned as a single chunk in the additional stream,
if any. It is ignored excluding case, when a plugin is called to return Screen Only.
offset size description
0 4 name = 'PRVW'
4 4 size (size of entire chunk
data).
8 (size) bitmap file image.
'TAPE' chunk - Tape image
This chunk usually is located first, but it is described here since it has most
complicated structure and can contain a lot of sub-structures listed below
offset size description
0 4 name = 'TAPE'
4 4 size (size of entire chunk
data).
8 (size) tape data, see its format below.
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.
Last update: 3-Nov-2003