ZXHawk: Added Compressed Square Wave (CSW) tape image support
This commit is contained in:
parent
b90c8f0bec
commit
b81a7539cf
|
@ -66,7 +66,7 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
RomData = FileData;
|
||||
}
|
||||
else if (file.Extension == ".DSK" || file.Extension == ".TAP" || file.Extension == ".TZX" || file.Extension == ".PZX")
|
||||
else if (file.Extension == ".DSK" || file.Extension == ".TAP" || file.Extension == ".TZX" || file.Extension == ".PZX" || file.Extension == ".CSW")
|
||||
{
|
||||
// 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
|
||||
|
|
|
@ -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", ".PZX"
|
||||
".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS", ".TZX", ".PZX", ".CSW"
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;*.pzx;%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;*.csw;%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;*.pzx;%ARCH%",
|
||||
"Sinclair ZX Spectrum", "*.tzx;*.tap;*.dsk;*.pzx;*.csw;%ARCH%",
|
||||
"All Files", "*.*");
|
||||
}
|
||||
|
||||
|
|
|
@ -305,6 +305,7 @@ namespace BizHawk.Emulation.Common
|
|||
|
||||
case ".TZX":
|
||||
case ".PZX":
|
||||
case ".CSW":
|
||||
game.System = "ZXSpectrum";
|
||||
break;
|
||||
|
||||
|
|
|
@ -293,6 +293,7 @@
|
|||
<Compile Include="Computers\SinclairSpectrum\Media\Disk\DiskType.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Media\MediaConverter.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Media\MediaConverterType.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Media\Tape\CSW\CswConverter.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Media\Tape\PZX\PzxConverter.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TapeCommand.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TapeDataBlock.cs" />
|
||||
|
|
|
@ -324,6 +324,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
TzxConverter tzxSer = new TzxConverter(this);
|
||||
TapConverter tapSer = new TapConverter(this);
|
||||
PzxConverter pzxSer = new PzxConverter(this);
|
||||
CswConverter cswSer = new CswConverter(this);
|
||||
|
||||
// TZX
|
||||
if (tzxSer.CheckType(tapeData))
|
||||
|
@ -365,6 +366,26 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
}
|
||||
}
|
||||
|
||||
// CSW
|
||||
else if (cswSer.CheckType(tapeData))
|
||||
{
|
||||
// this file has a csw header - attempt serialization
|
||||
try
|
||||
{
|
||||
cswSer.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 CSW header, but threw an exception whilst data was being parsed.\n\n" + e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Assume TAP
|
||||
else
|
||||
{
|
||||
|
@ -817,7 +838,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
{
|
||||
if (_tapeIsPlaying && _autoPlay)
|
||||
{
|
||||
_monitorTimeOut--;
|
||||
if (DataBlocks.Count > 1 || _dataBlocks[_currentDataBlockIndex].BlockDescription != BlockType.CSW_Recording)
|
||||
{
|
||||
// we should only stop the tape when there are multiple blocks
|
||||
// if we just have one big block (maybe a CSW or WAV) then auto stopping will cock things up
|
||||
_monitorTimeOut--;
|
||||
}
|
||||
|
||||
if (_monitorTimeOut < 0)
|
||||
{
|
||||
|
@ -843,6 +869,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
if (timeout == 0)
|
||||
return;
|
||||
|
||||
// dont autostop if there is only 1 block
|
||||
if (DataBlocks.Count > 2 || _dataBlocks[_currentDataBlockIndex].BlockDescription == BlockType.CSW_Recording)
|
||||
return;
|
||||
|
||||
if (diff >= timeout * 2)
|
||||
{
|
||||
// There have been no attempted tape reads by the CPU within the double timeout period
|
||||
|
|
|
@ -202,6 +202,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
// spectrum .tzx tape file
|
||||
return SpectrumMediaType.Tape;
|
||||
}
|
||||
if (hdr.ToUpper().StartsWith("COMPRESSED SQ"))
|
||||
{
|
||||
// spectrum .tzx tape file
|
||||
return SpectrumMediaType.Tape;
|
||||
}
|
||||
|
||||
// if we get this far, assume a .tap file
|
||||
return SpectrumMediaType.Tape;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
|
@ -130,6 +132,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a byte array that is Z-RLE compressed
|
||||
/// </summary>
|
||||
/// <param name="sourceBuffer"></param>
|
||||
/// <param name="destBuffer"></param>
|
||||
public static void DecompressZRLE(byte[] sourceBuffer, ref byte[] destBuffer)
|
||||
{
|
||||
MemoryStream stream = new MemoryStream();
|
||||
stream.Write(sourceBuffer, 0, sourceBuffer.Length);
|
||||
stream.Position = 0;
|
||||
stream.ReadByte();
|
||||
stream.ReadByte();
|
||||
DeflateStream ds = new DeflateStream(stream, CompressionMode.Decompress, false);
|
||||
ds.Read(destBuffer, 0, destBuffer.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
TZX,
|
||||
TAP,
|
||||
PZX,
|
||||
CSW,
|
||||
DSK
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for Compressed Square Wave conversion
|
||||
/// https://web.archive.org/web/20171024182530/http://ramsoft.bbk.org.omegahg.com/csw.html
|
||||
/// </summary>
|
||||
public class CswConverter : MediaConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of serializer
|
||||
/// </summary>
|
||||
private MediaConverterType _formatType = MediaConverterType.CSW;
|
||||
public override MediaConverterType FormatType
|
||||
{
|
||||
get
|
||||
{
|
||||
return _formatType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Position counter
|
||||
/// </summary>
|
||||
private int _position = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether this class can be used to read the data format
|
||||
/// </summary>
|
||||
public override bool IsReader { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether this class can be used to write the data format
|
||||
/// </summary>
|
||||
public override bool IsWriter { get { return false; } }
|
||||
|
||||
#region Construction
|
||||
|
||||
private DatacorderDevice _datacorder;
|
||||
|
||||
public CswConverter(DatacorderDevice _tapeDevice)
|
||||
{
|
||||
_datacorder = _tapeDevice;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns TRUE if pzx header is detected
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public override bool CheckType(byte[] data)
|
||||
{
|
||||
// CSW Header
|
||||
|
||||
// check whether this is a valid csw format file by looking at the identifier in the header
|
||||
// (first 22 bytes of the file)
|
||||
string ident = Encoding.ASCII.GetString(data, 0, 22);
|
||||
|
||||
// version info
|
||||
int majorVer = data[8];
|
||||
int minorVer = data[9];
|
||||
|
||||
if (ident.ToUpper() != "COMPRESSED SQUARE WAVE")
|
||||
{
|
||||
// this is not a valid CSW format file
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeSerialization method
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public override void Read(byte[] data)
|
||||
{
|
||||
// clear existing tape blocks
|
||||
_datacorder.DataBlocks.Clear();
|
||||
|
||||
// CSW Header
|
||||
|
||||
// check whether this is a valid csw format file by looking at the identifier in the header
|
||||
// (first 22 bytes of the file)
|
||||
string ident = Encoding.ASCII.GetString(data, 0, 22);
|
||||
|
||||
if (ident.ToUpper() != "COMPRESSED SQUARE WAVE")
|
||||
{
|
||||
// this is not a valid CSW format file
|
||||
throw new Exception(this.GetType().ToString() +
|
||||
"This is not a valid CSW format file");
|
||||
}
|
||||
|
||||
if (data[0x16] != 0x1a)
|
||||
{
|
||||
// invalid terminator code
|
||||
throw new Exception(this.GetType().ToString() +
|
||||
"This image reports as a CSW but has an invalid terminator code");
|
||||
}
|
||||
|
||||
_position = 0;
|
||||
|
||||
// version info
|
||||
int majorVer = data[0x17];
|
||||
int minorVer = data[0x18];
|
||||
|
||||
int sampleRate;
|
||||
int totalPulses;
|
||||
byte compressionType;
|
||||
byte flags;
|
||||
byte headerExtensionLen;
|
||||
byte[] cswData;
|
||||
byte[] cswDataUncompressed;
|
||||
|
||||
if (majorVer == 2)
|
||||
{
|
||||
/*
|
||||
CSW-2 Header
|
||||
CSW global file header - status: required
|
||||
Offset Value Type Description
|
||||
0x00 (note) ASCII[22] "Compressed Square Wave" signature
|
||||
0x16 0x1A BYTE Terminator code
|
||||
0x17 0x02 BYTE CSW major revision number
|
||||
0x18 0x00 BYTE CSW minor revision number
|
||||
0x19 - DWORD Sample rate
|
||||
0x1D - DWORD Total number of pulses (after decompression)
|
||||
0x21 - BYTE Compression type (see notes below)
|
||||
0x01: RLE
|
||||
0x02: Z-RLE
|
||||
0x22 - BYTE Flags
|
||||
b0: initial polarity; if set, the signal starts at logical high
|
||||
0x23 HDR BYTE Header extension length in bytes (0x00)
|
||||
For future expansions only, see note below.
|
||||
0x24 - ASCIIZ[16] Encoding application description
|
||||
Information about the tool which created the file (e.g. name and version)
|
||||
0x34 - BYTE[HDR] Header extension data (if present)
|
||||
0x34+HDR - - CSW data.
|
||||
*/
|
||||
|
||||
_position = 0x19;
|
||||
sampleRate = GetInt32(data, _position);
|
||||
_position += 4;
|
||||
|
||||
totalPulses = GetInt32(data, _position);
|
||||
cswDataUncompressed = new byte[totalPulses + 1];
|
||||
_position += 4;
|
||||
|
||||
compressionType = data[_position++];
|
||||
flags = data[_position++];
|
||||
headerExtensionLen = data[_position++];
|
||||
|
||||
_position = 0x34 + headerExtensionLen;
|
||||
|
||||
cswData = new byte[data.Length - _position];
|
||||
Array.Copy(data, _position, cswData, 0, cswData.Length);
|
||||
|
||||
ProcessCSWV2(cswData, ref cswDataUncompressed, compressionType, totalPulses);
|
||||
}
|
||||
else if (majorVer == 1)
|
||||
{
|
||||
/*
|
||||
CSW-1 Header
|
||||
CSW global file header - status: required
|
||||
Offset Value Type Description
|
||||
0x00 (note) ASCII[22] "Compressed Square Wave" signature
|
||||
0x16 0x1A BYTE Terminator code
|
||||
0x17 0x01 BYTE CSW major revision number
|
||||
0x18 0x01 BYTE CSW minor revision number
|
||||
0x19 - WORD Sample rate
|
||||
0x1B 0x01 BYTE Compression type
|
||||
0x01: RLE
|
||||
0x1C - BYTE Flags
|
||||
b0: initial polarity; if set, the signal starts at logical high
|
||||
0x1D 0x00 BYTE[3] Reserved.
|
||||
0x20 - - CSW data.
|
||||
*/
|
||||
|
||||
_position = 0x19;
|
||||
sampleRate = GetWordValue(data, _position);
|
||||
_position += 2;
|
||||
|
||||
compressionType = data[_position++];
|
||||
flags = data[_position++];
|
||||
|
||||
_position += 3;
|
||||
|
||||
cswDataUncompressed = new byte[data.Length - _position];
|
||||
|
||||
if (compressionType == 1)
|
||||
Array.Copy(data, _position, cswDataUncompressed, 0, cswDataUncompressed.Length);
|
||||
else
|
||||
throw new Exception(this.GetType().ToString() +
|
||||
"CSW Format unknown compression type");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(this.GetType().ToString() +
|
||||
"CSW Format Version " + majorVer + "." + minorVer + " is not currently supported");
|
||||
}
|
||||
|
||||
// create the single tape block
|
||||
// (use DATA block for now so initial signal level is handled correctly by the datacorder device)
|
||||
TapeDataBlock t = new TapeDataBlock();
|
||||
t.BlockDescription = BlockType.CSW_Recording;
|
||||
t.BlockID = 0x18;
|
||||
t.DataPeriods = new List<int>();
|
||||
|
||||
if (flags.Bit(0))
|
||||
t.InitialPulseLevel = true;
|
||||
else
|
||||
t.InitialPulseLevel = false;
|
||||
|
||||
var rate = (69888 * 50) / sampleRate;
|
||||
|
||||
for (int i = 0; i < cswDataUncompressed.Length;)
|
||||
{
|
||||
int length = cswDataUncompressed[i++] * rate;
|
||||
if (length == 0)
|
||||
{
|
||||
length = GetInt32(cswDataUncompressed, i) / rate;
|
||||
i += 4;
|
||||
}
|
||||
|
||||
t.DataPeriods.Add(length);
|
||||
}
|
||||
|
||||
// add closing period
|
||||
t.DataPeriods.Add((69888 * 50) / 10);
|
||||
|
||||
// add to datacorder
|
||||
_datacorder.DataBlocks.Add(t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a CSW v2 data block
|
||||
/// </summary>
|
||||
/// <param name="srcBuff"></param>
|
||||
/// <param name="destBuff"></param>
|
||||
/// <param name="sampleRate"></param>
|
||||
/// <param name="compType"></param>
|
||||
/// <param name="pulseCount"></param>
|
||||
public static void ProcessCSWV2(
|
||||
byte[] srcBuff,
|
||||
ref byte[] destBuff,
|
||||
byte compType,
|
||||
int pulseCount)
|
||||
{
|
||||
if (compType == 1)
|
||||
{
|
||||
Array.Copy(srcBuff, 0, destBuff, 0, pulseCount);
|
||||
}
|
||||
else if (compType == 2)
|
||||
{
|
||||
DecompressZRLE(srcBuff, ref destBuff);
|
||||
}
|
||||
else
|
||||
throw new Exception("CSW Format unknown compression type");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,7 +61,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns TRUE if tzx header is detected
|
||||
/// Returns TRUE if pzx header is detected
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public override bool CheckType(byte[] data)
|
||||
|
|
|
@ -643,10 +643,51 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
int blockLen = GetInt32(data, _position);
|
||||
_position += 4;
|
||||
|
||||
_position += blockLen;
|
||||
t.PauseInMS = GetWordValue(data, _position);
|
||||
|
||||
_position += 2;
|
||||
|
||||
int sampleRate = data[_position++] << 16 | data[_position++] << 8 | data[_position++];
|
||||
byte compType = data[_position++];
|
||||
int pulses = GetInt32(data, _position);
|
||||
_position += 4;
|
||||
|
||||
int dataLen = blockLen - 10;
|
||||
|
||||
// build source array
|
||||
byte[] src = new byte[dataLen];
|
||||
// build destination array
|
||||
byte[] dest = new byte[pulses + 1];
|
||||
|
||||
// process the CSW data
|
||||
CswConverter.ProcessCSWV2(src, ref dest, compType, pulses);
|
||||
|
||||
// create the periods
|
||||
var rate = (69888 * 50) / sampleRate;
|
||||
|
||||
for (int i = 0; i < dest.Length;)
|
||||
{
|
||||
int length = dest[i++] * rate;
|
||||
if (length == 0)
|
||||
{
|
||||
length = GetInt32(dest, i) / rate;
|
||||
i += 4;
|
||||
}
|
||||
|
||||
t.DataPeriods.Add(length);
|
||||
}
|
||||
|
||||
// add closing period
|
||||
t.DataPeriods.Add((69888 * 50) / 10);
|
||||
|
||||
_position += dataLen;
|
||||
//_position += blockLen;
|
||||
|
||||
// add the block
|
||||
_datacorder.DataBlocks.Add(t);
|
||||
|
||||
// generate PAUSE block
|
||||
CreatePauseBlock(_datacorder.DataBlocks.Last());
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
|
Loading…
Reference in New Issue