Writing Plugins for EmuZWin 2.0

(Ñ) 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).