diff --git a/BizHawk.Client.Common/RomGame.cs b/BizHawk.Client.Common/RomGame.cs
index 13eaf5b94f..315a3adbca 100644
--- a/BizHawk.Client.Common/RomGame.cs
+++ b/BizHawk.Client.Common/RomGame.cs
@@ -66,7 +66,7 @@ namespace BizHawk.Client.Common
{
RomData = FileData;
}
- else if (file.Extension == ".DSK" || file.Extension == ".TAP" || file.Extension == ".TZX")
+ else if (file.Extension == ".DSK" || file.Extension == ".TAP" || file.Extension == ".TZX" || file.Extension == ".PZX")
{
// these are not roms. unforunately if treated as such there are certain edge-cases
// where a header offset is detected. This should mitigate this issue until a cleaner solution is found
diff --git a/BizHawk.Client.EmuHawk/FileLoader.cs b/BizHawk.Client.EmuHawk/FileLoader.cs
index db3c00d76d..a20c1a4222 100644
--- a/BizHawk.Client.EmuHawk/FileLoader.cs
+++ b/BizHawk.Client.EmuHawk/FileLoader.cs
@@ -51,7 +51,7 @@ namespace BizHawk.Client.EmuHawk
return new[]
{
".NES", ".FDS", ".UNF", ".SMS", ".GG", ".SG", ".GB", ".GBC", ".GBA", ".PCE", ".SGX", ".BIN", ".SMD", ".GEN", ".MD", ".SMC", ".SFC", ".A26", ".A78", ".LNX", ".COL", ".ROM", ".M3U", ".CUE", ".CCD", ".SGB", ".Z64", ".V64", ".N64", ".WS", ".WSC", ".XML", ".DSK", ".DO", ".PO", ".PSF", ".MINIPSF", ".NSF",
- ".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS", ".TZX"
+ ".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS", ".TZX", ".PZX"
};
}
diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs
index d839888927..2626719f67 100644
--- a/BizHawk.Client.EmuHawk/MainForm.cs
+++ b/BizHawk.Client.EmuHawk/MainForm.cs
@@ -2081,7 +2081,7 @@ namespace BizHawk.Client.EmuHawk
if (VersionInfo.DeveloperBuild)
{
return FormatFilter(
- "Rom Files", "*.nes;*.fds;*.unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.mds;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.32x;*.col;*.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.tap;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.vb;*.ngp;*.ngc;*.psf;*.minipsf;*.nsf;*.tzx;%ARCH%",
+ "Rom Files", "*.nes;*.fds;*.unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.mds;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.32x;*.col;*.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.tap;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.vb;*.ngp;*.ngc;*.psf;*.minipsf;*.nsf;*.tzx;*.pzx;%ARCH%",
"Music Files", "*.psf;*.minipsf;*.sid;*.nsf",
"Disc Images", "*.cue;*.ccd;*.mds;*.m3u",
"NES", "*.nes;*.fds;*.unf;*.nsf;%ARCH%",
@@ -2109,7 +2109,7 @@ namespace BizHawk.Client.EmuHawk
"Apple II", "*.dsk;*.do;*.po;%ARCH%",
"Virtual Boy", "*.vb;%ARCH%",
"Neo Geo Pocket", "*.ngp;*.ngc;%ARCH%",
- "Sinclair ZX Spectrum", "*.tzx;*.tap;*.dsk;%ARCH%",
+ "Sinclair ZX Spectrum", "*.tzx;*.tap;*.dsk;*.pzx;%ARCH%",
"All Files", "*.*");
}
diff --git a/BizHawk.Emulation.Common/Database/Database.cs b/BizHawk.Emulation.Common/Database/Database.cs
index 052b1957cc..730acf9a05 100644
--- a/BizHawk.Emulation.Common/Database/Database.cs
+++ b/BizHawk.Emulation.Common/Database/Database.cs
@@ -304,6 +304,7 @@ namespace BizHawk.Emulation.Common
break;
case ".TZX":
+ case ".PZX":
game.System = "ZXSpectrum";
break;
diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
index 08933f73d1..61b2dd874d 100644
--- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
+++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
@@ -293,10 +293,11 @@
+
-
-
+
+
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs
index 6701d0a923..ff80df1982 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs
@@ -320,8 +320,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
public void LoadTape(byte[] tapeData)
{
- // check TZX first
+ // instantiate converters
TzxConverter tzxSer = new TzxConverter(this);
+ TapConverter tapSer = new TapConverter(this);
+ PzxConverter pzxSer = new PzxConverter(this);
+
+ // TZX
if (tzxSer.CheckType(tapeData))
{
// this file has a tzx header - attempt serialization
@@ -340,9 +344,30 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
"\n\nTape image file has a valid TZX header, but threw an exception whilst data was being parsed.\n\n" + e.ToString());
}
}
+
+ // PZX
+ else if (pzxSer.CheckType(tapeData))
+ {
+ // this file has a pzx header - attempt serialization
+ try
+ {
+ pzxSer.Read(tapeData);
+ // reset block index
+ CurrentDataBlockIndex = 0;
+ return;
+ }
+ catch (Exception ex)
+ {
+ // exception during operation
+ var e = ex;
+ throw new Exception(this.GetType().ToString() +
+ "\n\nTape image file has a valid PZX header, but threw an exception whilst data was being parsed.\n\n" + e.ToString());
+ }
+ }
+
+ // Assume TAP
else
{
- TapConverter tapSer = new TapConverter(this);
try
{
tapSer.Read(tapeData);
@@ -383,7 +408,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
counter++;
- if (counter > 50)
+ if (counter > 30)
{
counter = 0;
bool state = GetEarBit(_machine.CPU.TotalExecutedCycles);
@@ -421,7 +446,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
// process the cycles based on the waitEdge
- while (cycles >= _waitEdge)
+ while (cycles >= _waitEdge)
{
// decrement cycles
cycles -= _waitEdge;
@@ -433,6 +458,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
// start of block
+ //if (!_dataBlocks[_currentDataBlockIndex].InitialPulseLevel[_position])
+ //currentState = !currentState;
+
// notify about the current block
var bl = _dataBlocks[_currentDataBlockIndex];
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs
index 5d38566152..9a2a37b15c 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs
@@ -197,6 +197,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// spectrum .tzx tape file
return SpectrumMediaType.Tape;
}
+ if (hdr.ToUpper().StartsWith("PZXT"))
+ {
+ // spectrum .tzx tape file
+ return SpectrumMediaType.Tape;
+ }
// if we get this far, assume a .tap file
return SpectrumMediaType.Tape;
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaConverterType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaConverterType.cs
index 034e45e5d0..25b3245a20 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaConverterType.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaConverterType.cs
@@ -9,6 +9,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
NONE,
TZX,
TAP,
+ PZX,
DSK
}
}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/PZX/PzxConverter.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/PZX/PzxConverter.cs
new file mode 100644
index 0000000000..1d913aef8c
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/PZX/PzxConverter.cs
@@ -0,0 +1,403 @@
+using BizHawk.Common.NumberExtensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
+{
+ ///
+ /// Reponsible for PZX format serializaton
+ /// Based on the information here: http://zxds.raxoft.cz/docs/pzx.txt
+ ///
+ public class PzxConverter : MediaConverter
+ {
+ ///
+ /// The type of serializer
+ ///
+ private MediaConverterType _formatType = MediaConverterType.PZX;
+ public override MediaConverterType FormatType
+ {
+ get
+ {
+ return _formatType;
+ }
+ }
+
+ ///
+ /// Signs whether this class can be used to read the data format
+ ///
+ public override bool IsReader { get { return true; } }
+
+ ///
+ /// Signs whether this class can be used to write the data format
+ ///
+ public override bool IsWriter { get { return false; } }
+
+ ///
+ /// Working list of generated tape data blocks
+ ///
+ private List _blocks = new List();
+
+ ///
+ /// Position counter
+ ///
+ private int _position = 0;
+
+ ///
+ /// Object to keep track of loops - this assumes there is only one loop at a time
+ ///
+ private List> _loopCounter = new List>();
+
+ #region Construction
+
+ private DatacorderDevice _datacorder;
+
+ public PzxConverter(DatacorderDevice _tapeDevice)
+ {
+ _datacorder = _tapeDevice;
+ }
+
+ #endregion
+
+ ///
+ /// Returns TRUE if tzx header is detected
+ ///
+ ///
+ public override bool CheckType(byte[] data)
+ {
+ // PZX Header
+
+ // check whether this is a valid pzx format file by looking at the identifier in the header
+ // (first 4 bytes of the file)
+ string ident = Encoding.ASCII.GetString(data, 0, 4);
+
+ // version info
+ int majorVer = data[8];
+ int minorVer = data[9];
+
+ if (ident.ToUpper() != "PZXT")
+ {
+ // this is not a valid PZX format file
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ ///
+ /// DeSerialization method
+ ///
+ ///
+ public override void Read(byte[] data)
+ {
+ // clear existing tape blocks
+ _datacorder.DataBlocks.Clear();
+
+ /*
+ // PZX uniform block layout
+ offset type name meaning
+ ------ ---- ---- -------
+ 0 u32 tag unique identifier for the block type.
+ 4 u32 size size of the block in bytes, excluding the tag and size fields themselves.
+ 8 u8[size] data arbitrary amount of block data.
+ */
+
+ // check whether this is a valid pzx format file by looking at the identifier in the header block
+ string ident = Encoding.ASCII.GetString(data, 0, 4);
+
+ if (ident.ToUpper() != "PZXT")
+ {
+ // this is not a valid TZX format file
+ throw new Exception(this.GetType().ToString() +
+ "This is not a valid PZX format file");
+ }
+
+ _position = 0;
+
+ // parse all blocks out into seperate byte arrays first
+ List bDatas = new List();
+
+ while (_position < data.Length)
+ {
+ int startPos = _position;
+
+ // data size
+ _position += 4;
+ int blockSize = GetInt32(data, _position);
+ _position += 4;
+
+ // block data
+ byte[] bd = new byte[8 + blockSize];
+ Array.Copy(data, startPos, bd, 0, bd.Length);
+ bDatas.Add(bd);
+
+ _position += blockSize;
+ }
+
+ // process the blocks
+ foreach (var b in bDatas)
+ {
+ int pos = 8;
+ string blockId = Encoding.ASCII.GetString(b, 0, 4);
+ int blockSize = GetInt32(b, 4);
+
+ TapeDataBlock t = new TapeDataBlock();
+
+ switch (blockId)
+ {
+ // PZXT - PZX header block
+ /*
+ offset type name meaning
+ 0 u8 major major version number (currently 1).
+ 1 u8 minor minor version number (currently 0).
+ 2 u8[?] info tape info, see below.
+ */
+ case "PZXT":
+
+ break;
+
+ // PULS - Pulse sequence
+ /*
+ offset type name meaning
+ 0 u16 count bits 0-14 optional repeat count (see bit 15), always greater than zero
+ bit 15 repeat count present: 0 not present 1 present
+ 2 u16 duration1 bits 0-14 low/high (see bit 15) pulse duration bits
+ bit 15 duration encoding: 0 duration1 1 ((duration1<<16)+duration2)
+ 4 u16 duration2 optional low bits of pulse duration (see bit 15 of duration1)
+ 6 ... ... ditto repeated until the end of the block
+ */
+ case "PULS":
+
+ t.BlockID = GetInt32(b, 0);
+ t.DataPeriods = new List();
+
+ List pulses = new List();
+
+ while (pos < blockSize + 8)
+ {
+ ushort[] p = new ushort[2];
+ p[0] = 1;
+ p[1] = GetWordValue(b, pos);
+ pos += 2;
+
+ if (p[1] > 0x8000)
+ {
+ p[0] = (ushort)(p[1] & 0x7fff);
+ p[1] = GetWordValue(b, pos);
+ pos += 2;
+ }
+
+ if (p[1] >= 0x8000)
+ {
+ p[1] &= 0x7fff;
+ p[1] <<= 16;
+ p[1] |= GetWordValue(b, pos);
+ pos += 2;
+ }
+
+ pulses.Add(p);
+ }
+
+ // convert to tape block
+ t.BlockDescription = BlockType.PULS;
+ t.PauseInMS = 0;
+
+ foreach (var x in pulses)
+ {
+ for (int i = 0; i < x[0]; i++)
+ {
+ t.DataPeriods.Add(x[1]);
+ t.InitialPulseLevel.Add(true);
+ }
+ }
+
+ _datacorder.DataBlocks.Add(t);
+
+ break;
+
+ // DATA - Data block
+ /*
+ offset type name meaning
+ 0 u32 count bits 0-30 number of bits in the data stream
+ bit 31 initial pulse level: 0 low 1 high
+ 4 u16 tail duration of extra pulse after last bit of the block
+ 6 u8 p0 number of pulses encoding bit equal to 0.
+ 7 u8 p1 number of pulses encoding bit equal to 1.
+ 8 u16[p0] s0 sequence of pulse durations encoding bit equal to 0.
+ 8+2*p0 u16[p1] s1 sequence of pulse durations encoding bit equal to 1.
+ 8+2*(p0+p1) u8[ceil(bits/8)] data data stream, see below.
+ */
+ case "DATA":
+
+ t.BlockID = GetInt32(b, 0);
+ t.DataPeriods = new List();
+
+ List s0 = new List();
+ List s1 = new List();
+ List dData = new List();
+
+ uint initPulseLevel = 1;
+ int dCount = 1;
+ ushort tail = 0;
+
+ while (pos < blockSize + 8)
+ {
+ dCount = GetInt32(b, pos);
+ initPulseLevel = (uint)((dCount & 0x80000000) == 0 ? 0 : 1);
+
+ dCount = (int)(dCount & 0x7FFFFFFF);
+ pos += 4;
+
+ tail = GetWordValue(b, pos);
+ pos += 2;
+
+ var p0 = b[pos++];
+ var p1 = b[pos++];
+
+ for (int i = 0; i < p1; i++)
+ {
+ var s = GetWordValue(b, pos);
+ pos += 2;
+ s0.Add(s);
+ }
+
+ for (int i = 0; i < p1; i++)
+ {
+ var s = GetWordValue(b, pos);
+ pos += 2;
+ s1.Add(s);
+ }
+
+ for (int i = 0; i < Math.Ceiling((decimal)dCount / 8); i++)
+ {
+ var buff = b[pos++];
+ dData.Add(buff);
+ }
+
+ bool initPulse = initPulseLevel == 1 ? true : false;
+
+ foreach (var by in dData)
+ {
+ for (int i = 7; i >= 0; i--)
+ {
+ if (by.Bit(i) == true)
+ {
+ foreach (var pu in s1)
+ {
+ t.DataPeriods.Add((int)pu);
+ t.InitialPulseLevel.Add(initPulse);
+ }
+
+ }
+ else
+ {
+ foreach (var pu in s0)
+ {
+ t.DataPeriods.Add((int)pu);
+ t.InitialPulseLevel.Add(initPulse);
+ }
+
+ }
+ }
+ }
+
+ dData.Clear();
+ }
+
+ // convert to tape block
+ t.BlockDescription = BlockType.DATA;
+ t.PauseInMS = 0;
+
+ // tail
+ t.DataPeriods.Add(tail);
+
+ _datacorder.DataBlocks.Add(t);
+
+ break;
+
+ // PAUS - Pause
+ /*
+ offset type name meaning
+ 0 u32 duration bits 0-30 duration of the pause
+ bit 31 initial pulse level: 0 low 1 high
+ */
+ case "PAUS":
+
+ t.BlockID = GetInt32(b, 0);
+ t.DataPeriods = new List();
+
+ int iniPulseLevel = 1;
+ int pCount = 0;
+
+ var d = GetInt32(b, pos);
+ iniPulseLevel = ((d & 0x80000000) == 0 ? 0 : 1);
+ pCount = (d & 0x7FFFFFFF);
+
+ // convert to tape block
+ t.BlockDescription = BlockType.PAUS;
+ t.DataPeriods.Add(pCount);
+
+ if (iniPulseLevel == 1)
+ t.InitialPulseLevel.Add(true);
+ else
+ t.InitialPulseLevel.Add(false);
+
+ _datacorder.DataBlocks.Add(t);
+
+ break;
+
+ // BRWS - Browse point
+ /*
+ offset type name meaning
+ 0 u8[?] text text describing this browse point
+ */
+ case "BRWS":
+
+ t.BlockID = GetInt32(b, 0);
+ t.DataPeriods = new List();
+
+ string info = Encoding.ASCII.GetString(b, 8, blockSize);
+
+ // convert to tape block
+ t.BlockDescription = BlockType.BRWS;
+ t.MetaData.Add(BlockDescriptorTitle.Comments, info);
+ t.PauseInMS = 0;
+
+ _datacorder.DataBlocks.Add(t);
+
+ break;
+
+ // STOP - Stop tape command
+ /*
+ offset type name meaning
+ 0 u16 flags when exactly to stop the tape (1 48k only, other always).
+ */
+ case "STOP":
+
+
+ t.BlockID = GetInt32(b, 0);
+ t.DataPeriods = new List();
+
+ var flags = GetWordValue(b, pos);
+ if (flags == 1)
+ {
+ t.BlockDescription = BlockType.Stop_the_Tape_48K;
+ t.Command = TapeCommand.STOP_THE_TAPE_48K;
+ }
+ else
+ {
+ t.BlockDescription = BlockType.Pause_or_Stop_the_Tape;
+ t.Command = TapeCommand.STOP_THE_TAPE;
+ }
+
+ _datacorder.DataBlocks.Add(t);
+
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapConverter.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapConverter.cs
similarity index 100%
rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapConverter.cs
rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapConverter.cs
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxConverter.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxConverter.cs
similarity index 100%
rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxConverter.cs
rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxConverter.cs
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs
index 0b1c4e910f..ed3c046fdc 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs
@@ -113,6 +113,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
public List DataPeriods = new List();
+ public List InitialPulseLevel = new List();
+
///
/// Command that is raised by this data block
/// (that may or may not need to be acted on)
@@ -229,8 +231,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
Snapshot_Block = 0x40,
// unsupported / undetected
- Unsupported
+ Unsupported,
+
+ // PZX blocks
+ PZXT,
+ PULS,
+ DATA,
+ BRWS,
+ PAUS
+
}
+
///
/// Different title possibilities