(Ñ) by Vladimir Kladov, 2003
EmuZWin 2.0 can handle directly only its own format, EZX. To allow reading or writing any other snap formats plugins should be created using rules described in this document. At least plugins are provided for reading TAP, TZX, Z80, SNA and writing SNA - in a distributive.
A plugin is a DLL, located in the same directory, where EmuZWin is installed. A name of DLL has no matter. It must have entry point RegisterLoadSpectrum, and one or more additional entries: LoadSpectrum, SaveSpectrum and ReleaseData.
TSaveFileProc = function( FilePath: PChar; Data: PSpecData ): Boolean; stdcall; FilePath - a path to a file which should be written or rewritten. Data - state of Spectrum machine.
TLoadFileProc = function( FileData: Pointer; FileSize: Integer; FileExt: PChar; Data: PSpecData; ScreenOnly: Boolean ): Boolean; stdcall; FileData - image of a file to be load. It is loaded by the main application to a memory, pointed by FileData; FileSize - size of FileData memory to load from; FileExt - filename extension in lower case with dot preceding it (e.g. '.z80'). Can be used by the plugin to determine file type; can be ignored if the plugin looks at signature or handles files of a single extension only; Data - data structure to be filled in with loaded data. Some fields have initial values, the most for a configuration of S48 (though Lock7FFD initially false, and if S48 loaded, Lock7FFD should be set by the plugin. ScreenOnly - True, if only screen needed. The plugin can load only screen area of length $1B00 bytes to an appropriate memory to optimize Open Dialog performance. TReleaseProc = procedure( Descriptor: DWORD ); stdcall; Descriptor - ReleaseDescriptor, returned by the LoadProc, if it allocates some memory to store additional data (tape image, key map, etc.); ReleaseProc must use it to free allocated memory.
TRegProc = procedure( LoadFileTypes: PChar; SaveFileTypes: PChar; Descriptions: PChar; CanSaveS128: PByte ); stdcall; LoadFileTypes - a buffer (1024 bytes) to fill it with a list of file types supported for load (file masks like *.ext should be listed in a list separated with semicolon, e.g.: '**.sna;*.zxs;.z80'); SaveFileTypes - a buffer (1024 bytes) to fill it with a list of file types supported for save (like LoadFileTypes above); Descriptions - a buffer (1024 bytes) to fill it with a list of file types descriptions separated with semicolon (';'). The first is a description of all the loaded file types if the plugin supports file loading (in such case, entry point LoadSpectrum must be present in the plugin DLL). The others are descriptions for each saved file type separately. Even if a description is the same for all cases, it is necessary to provide always a decription of file types supported for load and for each file type supported to save. Only in case when there are no file types supported for load, its description have to be omitted; CanSaveS128 - a buffer (1024 bytes, initially ones) to fill it with flags (1 byte for each saved file type); plugin should set correspondent byte to 0, if such file type is for save only S48 state. When save dialog is opened to save Spectrum128 state, such file types will not be listed.
TSpecData structure used to pass Spectrum state between EmuZ and plugins, is the same as defined in EZX file format specification:
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; // returned by LoadProc, to be passed to // ReleaseProc to free memory Tape : TTapeData; // current tape reader state end;
* Note: before calling LoadSpectrum entry, main program clears entire structure, but fills in some fields with most possible values. At least, ROM1 and (may be) ROM0 is filled with current default ROM image.
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; // a pointer to tape image. TapeImgLen : Integer; // Length of tape image, ignore it. TapePosition: Integer; PulseLength: Integer; // current pulse length TactsLeast: Integer; // how many tacts current pulse should continue BitIndex: Integer; CurByte: Byte; Pilot : Integer; // Pilot length (not in "tones") Active: Boolean; end;
If tape image is loaded, LoadSpectrum procedure allocates a memory for it (storing a pointer to a start of tape in TapeImgData), and to release this memory, it also returns ReleaseDescriptor <> 0. Later main program calls entry point ReleaseDate with this ReleaseDescriptor as a parameter, and ReleaseData should use this parameter to determine which resources to free. Suggested way is passing a pointer to memory allocated, since it is a single memory block.
Tape image must be in internal EmuZ format, described in EZX format specification. It consists of chunks with first 4 bytes storing chunk name ('TAPE' for tape image, 'POKS' for pokes chunk, etc.), and following 4 bytes store chunk data length (without these 8 bytes of chunk header). For 'TAPE' chunk, length of the chunk can be set to 0 by a plugin, in such case least of memory block passed through TapeImgData pointer assumed to be a tape image, and its size is calculated on base of TapeImgLen field value: if memory block contains only 'TAPE' chunk, this size is equal to (TapeImgLen - 8).