# 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).