diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
index 3b65b45bb8..aefb1900ae 100644
--- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
+++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
@@ -281,6 +281,7 @@
+
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs
index 8018ac23cc..ddc7598f03 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs
@@ -233,12 +233,25 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
public void LoadTape(byte[] tapeData)
{
+ // attempt TZX deserialization
+ TzxSerializer tzxSer = new TzxSerializer(this);
+ try
+ {
+ tzxSer.DeSerialize(tapeData);
+ return;
+ }
+ catch (Exception ex)
+ {
+ // TAP format not detected
+ var e = ex;
+ }
+
// attempt TAP deserialization
TapSerializer tapSer = new TapSerializer(this);
-
try
{
tapSer.DeSerialize(tapeData);
+ return;
}
catch (Exception ex)
{
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs
index b20a6aa8b1..44c99c28ff 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs
@@ -92,7 +92,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
///
///
- protected static ushort GetUInt16(byte[] buf, int offsetIndex)
+ protected static ushort GetWordValue(byte[] buf, int offsetIndex)
{
return (ushort)(buf[offsetIndex] | buf[offsetIndex + 1] << 8);
}
@@ -103,7 +103,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
///
///
- protected static void setUint16(byte[] buf, int offsetIndex, ushort value)
+ protected static void SetWordValue(byte[] buf, int offsetIndex, ushort value)
{
buf[offsetIndex] = (byte)value;
buf[offsetIndex + 1] = (byte)(value >> 8);
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs
index 39bc2853b5..01e9bee86c 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs
@@ -126,6 +126,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
checksum (checkbittoggle would be a better name!).............^^
*/
+ // clear existing tape blocks
+ _datacorder.DataBlocks.Clear();
// convert bytearray to memory stream
MemoryStream stream = new MemoryStream(data);
@@ -174,7 +176,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
}
- // process the flag byte
+ // process the type byte
/* (The type is 0,1,2 or 3 for a Program, Number array, Character array or Code file.
A SCREEN$ file is regarded as a Code file with start address 16384 and length 6912 decimal.
If the file is a Program file, parameter 1 holds the autostart line number (or a number >=32768 if no LINE parameter was given)
@@ -206,9 +208,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
StringBuilder sb = new StringBuilder();
sb.Append(type + ": ");
sb.Append(fileName + " ");
- sb.Append(GetUInt16(blockdata, 14));
+ sb.Append(GetWordValue(blockdata, 14));
sb.Append(":");
- sb.Append(GetUInt16(blockdata, 12));
+ sb.Append(GetWordValue(blockdata, 12));
description = sb.ToString();
}
else if (blockdata[0] == 0xFF)
@@ -223,7 +225,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
description += string.Format(", crc {0}", ((crc != 0) ? string.Format("bad (#{0:X2}!=#{1:X2})", crcFile, crcValue) : "ok"));
}
- tdb.BlockDescription = description;
+ tdb.BlockDescription = BlockType.Standard_Speed_Data_Block;
// calculate the data periods for this block
int pilotLength = 0;
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs
index c75906b496..f714d21385 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs
@@ -19,17 +19,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public int BlockID
{
get { return _blockID; }
- set { _blockID = value; }
+ set {
+ _blockID = value;
+
+ if (MetaData == null)
+ MetaData = new Dictionary();
+
+ AddMetaData(BlockDescriptorTitle.Block_ID, value.ToString());
+ }
}
///
- /// Description of the block
+ /// The block type
///
- private string _blockDescription;
- public string BlockDescription
+ private BlockType _blockType;
+ public BlockType BlockDescription
{
- get { return _blockDescription; }
- set { _blockDescription = value; }
+ get { return _blockType; }
+ set {
+ _blockType = value;
+ if (MetaData == null)
+ MetaData = new Dictionary();
+ }
}
///
@@ -42,6 +53,63 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
set { _blockData = value; }
}
+ ///
+ /// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization)
+ /// Its basically tape information
+ ///
+ private byte[][] _tapeDescriptionData;
+
+ ///
+ /// Returns the Tape Description Data in a human readable format
+ ///
+ public List TapeDescriptionData
+ {
+ get
+ {
+ List data = new List();
+
+ foreach (byte[] b in _tapeDescriptionData)
+ {
+ data.Add(Encoding.ASCII.GetString(b));
+ }
+
+ return data;
+ }
+ }
+
+
+ #region Block Meta Data
+
+ ///
+ /// Dictionary of block related data
+ ///
+ public Dictionary MetaData { get; set; }
+
+ ///
+ /// Adds a single metadata item to the Dictionary
+ ///
+ ///
+ ///
+ public void AddMetaData(BlockDescriptorTitle descriptor, string data)
+ {
+ // check whether entry already exists
+ bool check = MetaData.ContainsKey(descriptor);
+ if (check)
+ {
+ // already exists - update
+ MetaData[descriptor] = data;
+ }
+ else
+ {
+ // create new
+ MetaData.Add(descriptor, data);
+ }
+ }
+
+ #endregion
+
+
+
///
/// List containing the pulse timing values
///
@@ -92,7 +160,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
ser.BeginSection("DataBlock" + blockPosition);
ser.Sync("_blockID", ref _blockID);
- ser.SyncFixedString("_blockDescription", ref _blockDescription, 50);
+ //ser.SyncFixedString("_blockDescription", ref _blockDescription, 200);
+ ser.SyncEnum("_blockType", ref _blockType);
ser.Sync("_blockData", ref _blockData, true);
ser.SyncEnum("_command", ref _command);
@@ -112,4 +181,84 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
ser.EndSection();
}
}
+
+ ///
+ /// The types of TZX blocks
+ ///
+ public enum BlockType
+ {
+ Standard_Speed_Data_Block = 0x10,
+ Turbo_Speed_Data_Block = 0x11,
+ Pure_Tone = 0x12,
+ Pulse_Sequence = 0x13,
+ Pure_Data_Block = 0x14,
+ Direct_Recording = 0x15,
+ CSW_Recording = 0x18,
+ Generalized_Data_Block = 0x19,
+ Pause_or_Stop_the_Tape = 0x20,
+ Group_Start = 0x21,
+ Group_End = 0x22,
+ Jump_to_Block = 0x23,
+ Loop_Start = 0x24,
+ Loop_End = 0x25,
+ Call_Sequence = 0x26,
+ Return_From_Sequence = 0x27,
+ Select_Block = 0x28,
+ Stop_the_Tape_48K = 0x2A,
+ Set_Signal_Level = 0x2B,
+ Text_Description = 0x30,
+ Message_Block = 0x31,
+ Archive_Info = 0x32,
+ Hardware_Type = 0x33,
+ Custom_Info_Block = 0x35,
+ Glue_Block = 0x5A,
+
+ // depreciated blocks
+ C64_ROM_Type_Data_Block = 0x16,
+ C64_Turbo_Tape_Data_Block = 0x17,
+ Emulation_Info = 0x34,
+ Snapshot_Block = 0x40,
+
+ // unsupported / undetected
+ Unsupported
+ }
+
+ ///
+ /// Different title possibilities
+ ///
+ public enum BlockDescriptorTitle
+ {
+ Undefined,
+ Block_ID,
+ Program,
+ Data_Bytes,
+ Bytes,
+
+ Pilot_Pulse_Length,
+ Pilot_Pulse_Count,
+ First_Sync_Length,
+ Second_Sync_Length,
+ Zero_Bit_Length,
+ One_Bit_Length,
+ Data_Length,
+ Bits_In_Last_Byte,
+ Pause_After_Data,
+
+ Pulse_Length,
+ Pulse_Count,
+
+ Text_Description,
+ Title,
+ Publisher,
+ Author,
+ Year,
+ Language,
+ Type,
+ Price,
+ Protection,
+ Origin,
+ Comments,
+
+ Needs_Parsing
+ }
}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs
new file mode 100644
index 0000000000..76c1b1fd97
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs
@@ -0,0 +1,1630 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
+{
+ ///
+ /// Reponsible for TZX format serializaton
+ ///
+ public class TzxSerializer : MediaSerializer
+ {
+ ///
+ /// The type of serializer
+ ///
+ private MediaSerializationType _formatType = MediaSerializationType.TZX;
+ public override MediaSerializationType FormatType
+ {
+ get
+ {
+ return _formatType;
+ }
+ }
+
+ ///
+ /// Signs whether this class can be used to serialize
+ ///
+ public override bool IsSerializer { get { return false; } }
+
+ ///
+ /// Signs whether this class can be used to de-serialize
+ ///
+ public override bool IsDeSerializer { get { return true; } }
+
+ ///
+ /// 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 TzxSerializer(DatacorderDevice _tapeDevice)
+ {
+ _datacorder = _tapeDevice;
+ }
+
+ #endregion
+
+ ///
+ /// DeSerialization method
+ ///
+ ///
+ public override void DeSerialize(byte[] data)
+ {
+ // clear existing tape blocks
+ _datacorder.DataBlocks.Clear();
+
+/*
+ // TZX Header
+ length: 10 bytes
+ Offset Value Type Description
+ 0x00 "ZXTape!" ASCII[7] TZX signature
+ 0x07 0x1A BYTE End of text file marker
+ 0x08 1 BYTE TZX major revision number
+ 0x09 20 BYTE TZX minor revision number
+*/
+
+ // check whether this is a valid tzx format file by looking at the identifier in the header
+ // (first 7 bytes of the file)
+ string ident = Encoding.ASCII.GetString(data, 0, 7);
+ // and 'end of text' marker
+ byte eotm = data[7];
+
+ // version info
+ int majorVer = data[8];
+ int minorVer = data[9];
+
+ if (ident != "ZXTape!" || eotm != 0x1A)
+ {
+ // this is not a valid TZX format file
+ throw new Exception(this.GetType().ToString() +
+ "This is not a valid TZX format file");
+ }
+
+ // iterate through each block
+ _position = 10;
+ while (_position < data.Length)
+ {
+ // block ID is the first byte in a new block
+ int ID = data[_position++];
+
+ // process the data
+ ProcessBlock(data, ID);
+ }
+
+ }
+
+ ///
+ /// Processes a TZX block
+ ///
+ ///
+ ///
+ private void ProcessBlock(byte[] data, int id)
+ {
+ // process based on detected block ID
+ switch (id)
+ {
+ // ID 10 - Standard Speed Data Block
+ case 0x10:
+ ProcessBlockID10(data);
+ break;
+ // ID 11 - Turbo Speed Data Block
+ case 0x11:
+ ProcessBlockID11(data);
+ break;
+ // ID 12 - Pure Tone
+ case 0x12:
+ ProcessBlockID12(data);
+ break;
+ // ID 13 - Pulse sequence
+ case 0x13:
+ ProcessBlockID13(data);
+ break;
+ // ID 14 - Pure Data Block
+ case 0x14:
+ ProcessBlockID14(data);
+ break;
+ // ID 15 - Direct Recording
+ case 0x15:
+ ProcessBlockID15(data);
+ break;
+ // ID 18 - CSW Recording
+ case 0x18:
+ ProcessBlockID18(data);
+ break;
+ // ID 19 - Generalized Data Block
+ case 0x19:
+ ProcessBlockID19(data);
+ break;
+ // ID 20 - Pause (silence) or 'Stop the Tape' command
+ case 0x20:
+ ProcessBlockID20(data);
+ break;
+ // ID 21 - Group start
+ case 0x21:
+ ProcessBlockID21(data);
+ break;
+ // ID 22 - Group end
+ case 0x22:
+ ProcessBlockID22(data);
+ break;
+ // ID 23 - Jump to block
+ case 0x23:
+ ProcessBlockID23(data);
+ break;
+ // ID 24 - Loop start
+ case 0x24:
+ ProcessBlockID24(data);
+ break;
+ // ID 25 - Loop end
+ case 0x25:
+ ProcessBlockID25(data);
+ break;
+ // ID 26 - Call sequence
+ case 0x26:
+ ProcessBlockID26(data);
+ break;
+ // ID 27 - Return from sequence
+ case 0x27:
+ ProcessBlockID27(data);
+ break;
+ // ID 28 - Select block
+ case 0x28:
+ ProcessBlockID28(data);
+ break;
+ // ID 2A - Stop the tape if in 48K mode
+ case 0x2A:
+ ProcessBlockID2A(data);
+ break;
+ // ID 2B - Set signal level
+ case 0x2B:
+ ProcessBlockID2B(data);
+ break;
+ // ID 30 - Text description
+ case 0x30:
+ ProcessBlockID30(data);
+ break;
+ // ID 31 - Message block
+ case 0x31:
+ ProcessBlockID31(data);
+ break;
+ // ID 32 - Archive info
+ case 0x32:
+ ProcessBlockID32(data);
+ break;
+ // ID 33 - Hardware type
+ case 0x33:
+ ProcessBlockID33(data);
+ break;
+ // ID 35 - Custom info block
+ case 0x35:
+ ProcessBlockID35(data);
+ break;
+ // ID 5A - "Glue" block
+ case 0x5A:
+ ProcessBlockID5A(data);
+ break;
+
+ #region Depreciated Blocks
+
+ // ID 16 - C64 ROM Type Data Block
+ case 0x16:
+ ProcessBlockID16(data);
+ break;
+ // ID 17 - C64 Turbo Tape Data Block
+ case 0x17:
+ ProcessBlockID17(data);
+ break;
+ // ID 34 - Emulation info
+ case 0x34:
+ ProcessBlockID34(data);
+ break;
+ // ID 40 - Snapshot block
+ case 0x40:
+ ProcessBlockID40(data);
+ break;
+
+ #endregion
+
+ default:
+ ProcessUnidentifiedBlock(data);
+ break;
+ }
+ }
+
+ #region TZX Block Processors
+
+ #region ID 10 - Standard Speed Data Block
+/* length: [02,03]+04
+ Offset Value Type Description
+ 0x00 - WORD Pause after this block (ms.) {1000}
+ 0x02 N WORD Length of data that follow
+ 0x04 - BYTE[N] Data as in .TAP files
+
+ This block must be replayed with the standard Spectrum ROM timing values - see the values in
+ curly brackets in block ID 11. The pilot tone consists in 8063 pulses if the first data byte
+ (flag byte) is < 128, 3223 otherwise. This block can be used for the ROM loading routines AND
+ for custom loading routines that use the same timings as ROM ones do. */
+ private void ProcessBlockID10(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x10;
+ t.BlockDescription = BlockType.Standard_Speed_Data_Block;
+ t.DataPeriods = new List();
+
+ int pauseLen = GetWordValue(data, _position);
+ int blockLen = GetWordValue(data, _position + 2);
+
+ _position += 4;
+
+ byte[] tmp = new byte[blockLen];
+ tmp = data.Skip(_position).Take(blockLen).ToArray();
+
+ var t2 = DecodeDataBlock(t, tmp, DataBlockType.Standard, 1000);
+
+ // add the block
+ _datacorder.DataBlocks.Add(t2);
+
+ // advance the position to the next block
+ _position += blockLen;
+ }
+ #endregion
+
+ #region ID 11 - Turbo Speed Data Block
+/* length: [0F,10,11]+12
+ Offset Value Type Description
+ 0x00 - WORD Length of PILOT pulse {2168}
+ 0x02 - WORD Length of SYNC first pulse {667}
+ 0x04 - WORD Length of SYNC second pulse {735}
+ 0x06 - WORD Length of ZERO bit pulse {855}
+ 0x08 - WORD Length of ONE bit pulse {1710}
+ 0x0A - WORD Length of PILOT tone (number of pulses) {8063 header (flag<128), 3223 data (flag>=128)}
+ 0x0C - BYTE Used bits in the last byte (other bits should be 0) {8}
+ (e.g. if this is 6, then the bits used (x) in the last byte are: xxxxxx00,
+ where MSb is the leftmost bit, LSb is the rightmost bit)
+ 0x0D - WORD Pause after this block (ms.) {1000}
+ 0x0F N BYTE[3] Length of data that follow
+ 0x12 - BYTE[N] Data as in .TAP files
+
+ This block is very similar to the normal TAP block but with some additional info on the timings and other important
+ differences. The same tape encoding is used as for the standard speed data block. If a block should use some non-standard
+ sync or pilot tones (i.e. all sorts of protection schemes) then use the next three blocks to describe it.*/
+ private void ProcessBlockID11(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x11;
+ t.BlockDescription = BlockType.Turbo_Speed_Data_Block;
+ t.DataPeriods = new List();
+
+ int pilotPL = GetWordValue(data, _position);
+ int sync1P = GetWordValue(data, _position + 2);
+ int sync2P = GetWordValue(data, _position + 4);
+ int bit0P = GetWordValue(data, _position + 6);
+ int bit1P = GetWordValue(data, _position + 8);
+ int pilotTL = GetWordValue(data, _position + 10);
+ int bitinbyte = data[_position + 12];
+ int pause = GetWordValue(data, _position + 13);
+
+ int blockLen = 0xFFFFFF & GetInt32(data, _position + 0x0F);
+
+ _position += 0x12;
+
+ byte[] tmp = new byte[blockLen];
+ tmp = data.Skip(_position).Take(blockLen).ToArray();
+
+ var t2 = DecodeDataBlock(t, tmp, DataBlockType.Turbo, pause, pilotTL, pilotPL, sync1P, sync2P, bit0P, bit1P, bitinbyte);
+
+ // add the block
+ _datacorder.DataBlocks.Add(t2);
+
+ // advance the position to the next block
+ _position += blockLen;
+ }
+ #endregion
+
+ #region ID 12 - Pure Tone
+/* length: 04
+ Offset Value Type Description
+ 0x00 - WORD Length of one pulse in T-states
+ 0x02 - WORD Number of pulses
+
+ This will produce a tone which is basically the same as the pilot tone in the ID 10, ID 11 blocks. You can define how
+ long the pulse is and how many pulses are in the tone. */
+ private void ProcessBlockID12(byte[] data)
+ {
+ int blockLen = 4;
+
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x12;
+ t.BlockDescription = BlockType.Pure_Tone;
+ t.DataPeriods = new List();
+
+ // get values
+ int pulseLength = GetWordValue(data, _position);
+ int pulseCount = GetWordValue(data, _position + 2);
+
+ t.AddMetaData(BlockDescriptorTitle.Pulse_Length, pulseLength.ToString() + " T-States");
+ t.AddMetaData(BlockDescriptorTitle.Pulse_Count, pulseCount.ToString());
+
+ // build period information
+ for (int p = 0; p < pulseCount; p++)
+ {
+ t.DataPeriods.Add(pulseLength);
+ }
+
+ // add the block
+ _datacorder.DataBlocks.Add(t);
+
+ // advance the position to the next block
+ _position += blockLen;
+ }
+ #endregion
+
+ #region ID 13 - Pulse sequence
+/* length: [00]*02+01
+ Offset Value Type Description
+ 0x00 N BYTE Number of pulses
+ 0x01 - WORD[N] Pulses' lengths
+
+ This will produce N pulses, each having its own timing. Up to 255 pulses can be stored in this block; this is useful for non-standard
+ sync tones used by some protection schemes. */
+ private void ProcessBlockID13(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x13;
+ t.BlockDescription = BlockType.Pulse_Sequence;
+ t.DataPeriods = new List();
+
+ // get pulse count
+ int pulseCount = data[_position];
+ t.AddMetaData(BlockDescriptorTitle.Pulse_Count, pulseCount.ToString());
+ _position++;
+
+ // build period information
+ for (int p = 0; p < pulseCount; p++, _position += 2)
+ {
+ // get pulse length
+ int pulseLength = GetWordValue(data, _position);
+ t.AddMetaData(BlockDescriptorTitle.Needs_Parsing, "Pulse " + p + " Length\t" + pulseLength.ToString() + " T-States");
+ t.DataPeriods.Add(pulseLength);
+ }
+
+ // add the block
+ _datacorder.DataBlocks.Add(t);
+ }
+ #endregion
+
+ #region ID 14 - Pure Data Block
+/* length: [07,08,09]+0A
+ Offset Value Type Description
+ 0x00 - WORD Length of ZERO bit pulse
+ 0x02 - WORD Length of ONE bit pulse
+ 0x04 - BYTE Used bits in last byte (other bits should be 0)
+ (e.g. if this is 6, then the bits used (x) in the last byte are: xxxxxx00,
+ where MSb is the leftmost bit, LSb is the rightmost bit)
+ 0x05 - WORD Pause after this block (ms.)
+ 0x07 N BYTE[3] Length of data that follow
+ 0x0A - BYTE[N] Data as in .TAP files
+
+ This is the same as in the turbo loading data block, except that it has no pilot or sync pulses. */
+ private void ProcessBlockID14(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x14;
+ t.BlockDescription = BlockType.Pure_Data_Block;
+ t.DataPeriods = new List();
+
+ int pilotPL = 0;
+ int sync1P = 0;
+ int sync2P = 0;
+ int bit0P = GetWordValue(data, _position + 0);
+ int bit1P = GetWordValue(data, _position + 2);
+ int pilotTL = 0;
+ int bitinbyte = data[_position + 4];
+ int pause = GetWordValue(data, _position + 5);
+
+ int blockLen = 0xFFFFFF & GetInt32(data, _position + 0x07);
+
+ _position += 0x0A;
+
+ byte[] tmp = new byte[blockLen];
+ tmp = data.Skip(_position).Take(blockLen).ToArray();
+
+ var t2 = DecodeDataBlock(t, tmp, DataBlockType.Pure, pause, pilotTL, pilotPL, sync1P, sync2P, bit0P, bit1P, bitinbyte);
+
+ // add the block
+ _datacorder.DataBlocks.Add(t2);
+
+ // advance the position to the next block
+ _position += blockLen;
+ }
+ #endregion
+
+ #region ID 15 - Direct Recording
+/* length: [05,06,07]+08
+ Offset Value Type Description
+ 0x00 - WORD Number of T-states per sample (bit of data)
+ 0x02 - WORD Pause after this block in milliseconds (ms.)
+ 0x04 - BYTE Used bits (samples) in last byte of data (1-8)
+ (e.g. if this is 2, only first two samples of the last byte will be played)
+ 0x05 N BYTE[3] Length of samples' data
+ 0x08 - BYTE[N] Samples data. Each bit represents a state on the EAR port (i.e. one sample).
+ MSb is played first.
+
+ This block is used for tapes which have some parts in a format such that the turbo loader block cannot be used.
+ This is not like a VOC file, since the information is much more compact. Each sample value is represented by one bit only
+ (0 for low, 1 for high) which means that the block will be at most 1/8 the size of the equivalent VOC.
+ The preferred sampling frequencies are 22050 or 44100 Hz (158 or 79 T-states/sample).
+ Please, if you can, don't use other sampling frequencies.
+ Please use this block only if you cannot use any other block. */
+ private void ProcessBlockID15(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x15;
+ t.BlockDescription = BlockType.Direct_Recording;
+ t.DataPeriods = new List();
+
+ // get values
+ int samLen = GetInt32(data, _position + 5);
+ int samSize = 0xFFFFFF & samLen;
+
+ int tStatesPerSample = GetWordValue(data, _position);
+ int pauseAfterBlock = GetWordValue(data, _position + 2);
+ int usedBitsInLastByte = data[_position + 4];
+
+ // skip to samples data
+ _position += 8;
+
+ int pulseLength = 0;
+ int pulseCount = 0;
+
+ // ascertain the pulse count
+ for (int i = 0; i < samSize; i++)
+ {
+ for (int p = 0x80; p != 0; p >>= 1)
+ {
+ if (((data[_position + i] ^ pulseLength) & p) != 0)
+ {
+ pulseCount++;
+ pulseLength ^= -1;
+ }
+ }
+ }
+
+ // get the pulses
+ t.DataPeriods = new List(pulseCount + 2);
+ int tStateCount = 0;
+ pulseLength = 0;
+ for (int i = 1; i < samSize; i++)
+ {
+ for (int p = 0x80; p != 0; p >>= 1)
+ {
+ tStateCount += tStatesPerSample;
+ if (((data[_position] ^ pulseLength) & p) != 0)
+ {
+ t.DataPeriods.Add(tStateCount);
+ pulseLength ^= -1;
+ tStateCount = 0;
+ }
+ }
+
+ // incrememt position
+ _position++;
+ }
+
+ // get the pulses in the last byte of data
+ for (int p = 0x80; p != (byte)(0x80 >> usedBitsInLastByte); p >>= 1)
+ {
+ tStateCount += tStatesPerSample;
+ if (((data[_position] ^ pulseLength) & p) != 0)
+ {
+ t.DataPeriods.Add(tStateCount);
+ pulseLength ^= -1;
+ tStateCount = 0;
+ }
+ }
+
+ // add final pulse
+ t.DataPeriods.Add(tStateCount);
+
+ // add end of block pause
+ if (pauseAfterBlock > 0)
+ {
+ t.DataPeriods.Add(3500 * pauseAfterBlock);
+ }
+
+ // increment position
+ _position++;
+
+ // add the block
+ _datacorder.DataBlocks.Add(t);
+ }
+ #endregion
+
+ #region ID 18 - CSW Recording
+/* length: [00,01,02,03]+04
+ Offset Value Type Description
+ 0x00 10+N DWORD Block length (without these four bytes)
+ 0x04 - WORD Pause after this block (in ms).
+ 0x06 - BYTE[3] Sampling rate
+ 0x09 - BYTE Compression type
+ 0x01: RLE
+ 0x02: Z-RLE
+ 0x0A - DWORD Number of stored pulses (after decompression, for validation purposes)
+ 0x0E - BYTE[N] CSW data, encoded according to the CSW file format specification.
+
+ This block contains a sequence of raw pulses encoded in CSW format v2 (Compressed Square Wave). */
+ private void ProcessBlockID18(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x18;
+ t.BlockDescription = BlockType.CSW_Recording;
+ t.DataPeriods = new List();
+
+ // add the block
+ _datacorder.DataBlocks.Add(t);
+ }
+ #endregion
+
+ #region ID 19 - Generalized Data Block
+/* length: [00,01,02,03]+04
+ Offset Value Type Description
+ 0x00 - DWORD Block length (without these four bytes)
+ 0x04 - WORD Pause after this block (ms)
+ 0x06 TOTP DWORD Total number of symbols in pilot/sync block (can be 0)
+ 0x0A NPP BYTE Maximum number of pulses per pilot/sync symbol
+ 0x0B ASP BYTE Number of pilot/sync symbols in the alphabet table (0=256)
+ 0x0C TOTD DWORD Total number of symbols in data stream (can be 0)
+ 0x10 NPD BYTE Maximum number of pulses per data symbol
+ 0x11 ASD BYTE Number of data symbols in the alphabet table (0=256)
+ 0x12 - SYMDEF[ASP] Pilot and sync symbols definition table
+ This field is present only if TOTP>0
+ 0x12+
+ (2*NPP+1)*ASP - PRLE[TOTP] Pilot and sync data stream
+ This field is present only if TOTP>0
+ 0x12+
+ (TOTP>0)*((2*NPP+1)*ASP)+
+ TOTP*3 - SYMDEF[ASD] Data symbols definition table
+ This field is present only if TOTD>0
+ 0x12+
+ (TOTP>0)*((2*NPP+1)*ASP)+
+ TOTP*3+
+ (2*NPD+1)*ASD - BYTE[DS] Data stream
+ This field is present only if TOTD>0
+
+ This block has been specifically developed to represent an extremely wide range of data encoding techniques.
+ The basic idea is that each loading component (pilot tone, sync pulses, data) is associated to a specific sequence
+ of pulses, where each sequence (wave) can contain a different number of pulses from the others.
+ In this way we can have a situation where bit 0 is represented with 4 pulses and bit 1 with 8 pulses.
+
+ ----
+ SYMDEF structure format
+ Offset Value Type Description
+ 0x00 - BYTE Symbol flags
+ b0-b1: starting symbol polarity
+ 00: opposite to the current level (make an edge, as usual) - default
+ 01: same as the current level (no edge - prolongs the previous pulse)
+ 10: force low level
+ 11: force high level
+ 0x01 - WORD[MAXP] Array of pulse lengths.
+
+ The alphabet is stored using a table where each symbol is a row of pulses. The number of columns (i.e. pulses) of the table is the
+ length of the longest sequence amongst all (MAXP=NPP or NPD, for pilot/sync or data blocks respectively); shorter waves are terminated by a
+ zero-length pulse in the sequence.
+ Any number of data symbols is allowed, so we can have more than two distinct waves; for example, imagine a loader which writes two bits at a
+ time by encoding them with four distinct pulse lengths: this loader would have an alphabet of four symbols, each associated to a specific
+ sequence of pulses (wave).
+ ----
+ ----
+ PRLE structure format
+ Offset Value Type Description
+ 0x00 - BYTE Symbol to be represented
+ 0x01 - WORD Number of repetitions
+
+ Most commonly, pilot and sync are repetitions of the same pulse, thus they are represented using a very simple RLE encoding structure which stores
+ the symbol and the number of times it must be repeated.
+ Each symbol in the data stream is represented by a string of NB bits of the block data, where NB = ceiling(Log2(ASD)).
+ Thus the length of the whole data stream in bits is NB*TOTD, or in bytes DS=ceil(NB*TOTD/8).
+ ---- */
+ private void ProcessBlockID19(byte[] data)
+ {
+ string test = "dgfg";
+ }
+ #endregion
+
+ #region ID 20 - Pause (silence) or 'Stop the Tape' command
+/* length: 02
+ Offset Value Type Description
+ 0x00 - WORD Pause duration (ms.)
+
+ This will make a silence (low amplitude level (0)) for a given time in milliseconds. If the value is 0 then the
+ emulator or utility should (in effect) STOP THE TAPE, i.e. should not continue loading until the user or emulator requests it. */
+ private void ProcessBlockID20(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x20;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Pause_or_Stop_the_Tape;
+
+ int pauseDuration = GetWordValue(data, _position);
+ if (pauseDuration != 0)
+ {
+ //t.BlockDescription = "Pause: " + pauseDuration + " ms";
+ }
+ else
+ {
+ //t.BlockDescription = "[STOP THE TAPE]";
+ }
+
+ if (pauseDuration == 0)
+ {
+ // issue stop the tape command
+ t.Command = TapeCommand.STOP_THE_TAPE;
+ // add 1ms period
+ t.DataPeriods.Add(3500);
+ pauseDuration = -1;
+ }
+ else
+ {
+ // this is actually just a pause
+ pauseDuration = 3500 * pauseDuration;
+ }
+
+ // add end of block pause
+ t.DataPeriods.Add(pauseDuration);
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advanced position to next block
+ _position += 2;
+ }
+ #endregion
+
+ #region ID 21 - Group start
+/* length: [00]+01
+ Offset Value Type Description
+ 0x00 L BYTE Length of the group name string
+ 0x01 - CHAR[L] Group name in ASCII format (please keep it under 30 characters long)
+
+ This block marks the start of a group of blocks which are to be treated as one single (composite) block.
+ This is very handy for tapes that use lots of subblocks like Bleepload (which may well have over 160 custom loading blocks).
+ You can also give the group a name (example 'Bleepload Block 1').
+ For each group start block, there must be a group end block. Nesting of groups is not allowed. */
+ private void ProcessBlockID21(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x21;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Group_Start;
+
+ int nameLength = data[_position];
+ _position++;
+
+ string name = Encoding.ASCII.GetString(data, _position, nameLength);
+ //t.BlockDescription = "[GROUP: " + name + "]";
+ t.Command = TapeCommand.BEGIN_GROUP;
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += nameLength;
+ }
+ #endregion
+
+ #region ID 22 - Group end
+/* length: 00
+
+ This indicates the end of a group. This block has no body. */
+ private void ProcessBlockID22(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x22;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Group_End;
+ t.Command = TapeCommand.END_GROUP;
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+ }
+ #endregion
+
+ #region ID 23 - Jump to block
+/* length: 02
+ Offset Value Type Description
+ 0x00 - WORD Relative jump value
+
+ This block will enable you to jump from one block to another within the file. The value is a signed short word
+ (usually 'signed short' in C); Some examples:
+ Jump 0 = 'Loop Forever' - this should never happen
+ Jump 1 = 'Go to the next block' - it is like NOP in assembler ;)
+ Jump 2 = 'Skip one block'
+ Jump -1 = 'Go to the previous block'
+ All blocks are included in the block count!. */
+ private void ProcessBlockID23(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x23;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Jump_to_Block;
+
+ int relativeJumpValue = GetWordValue(data, _position);
+ string result = string.Empty;
+
+ switch(relativeJumpValue)
+ {
+ case 0:
+ result = "Loop Forever";
+ break;
+ case 1:
+ result = "To Next Block";
+ break;
+ case 2:
+ result = "Skip One Block";
+ break;
+ case -1:
+ result = "Go to Previous Block";
+ break;
+ }
+
+ //t.BlockDescription = "[JUMP BLOCK - " + result +"]";
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += 2;
+ }
+ #endregion
+
+ #region ID 24 - Loop start
+/* length: 02
+ Offset Value Type Description
+ 0x00 - WORD Number of repetitions (greater than 1)
+
+ If you have a sequence of identical blocks, or of identical groups of blocks, you can use this block to tell how many times they should
+ be repeated. This block is the same as the FOR statement in BASIC.
+ For simplicity reasons don't nest loop blocks! */
+ private void ProcessBlockID24(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x24;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Loop_Start;
+
+ // loop should start from the next block
+ int loopStart = _datacorder.DataBlocks.Count() + 1;
+
+ int numberOfRepetitions = GetWordValue(data, _position);
+
+ // update loop counter
+ _loopCounter.Add(
+ new KeyValuePair(
+ loopStart,
+ numberOfRepetitions));
+
+ // update description
+ //t.BlockDescription = "[LOOP START - " + numberOfRepetitions + " times]";
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += 2;
+ }
+ #endregion
+
+ #region ID 25 - Loop end
+/* length: 00
+
+ This is the same as BASIC's NEXT statement. It means that the utility should jump back to the start of the loop if it hasn't
+ been run for the specified number of times.
+ This block has no body. */
+ private void ProcessBlockID25(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x25;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Loop_End;
+
+ // get the most recent loop info
+ var loop = _loopCounter.LastOrDefault();
+
+ int loopStart = loop.Key;
+ int numberOfRepetitions = loop.Value;
+
+ if (numberOfRepetitions == 0)
+ {
+ return;
+ }
+
+ // get the number of blocks to loop
+ int blockCnt = _datacorder.DataBlocks.Count() - loopStart;
+
+ // loop through each group to repeat
+ for (int b = 0; b < numberOfRepetitions; b++)
+ {
+ TapeDataBlock repeater = new TapeDataBlock();
+ //repeater.BlockDescription = "[LOOP REPEAT - " + (b + 1) + "]";
+ repeater.DataPeriods = new List();
+
+ // add the repeat block
+ _datacorder.DataBlocks.Add(repeater);
+
+ // now iterate through and add the blocks to be repeated
+ for (int i = 0; i < blockCnt; i++)
+ {
+ var block = _datacorder.DataBlocks[loopStart + i];
+ _datacorder.DataBlocks.Add(block);
+ }
+ }
+ }
+ #endregion
+
+ #region ID 26 - Call sequence
+/* length: [00,01]*02+02
+ Offset Value Type Description
+ 0x00 N WORD Number of calls to be made
+ 0x02 - WORD[N] Array of call block numbers (relative-signed offsets)
+
+ This block is an analogue of the CALL Subroutine statement. It basically executes a sequence of blocks that are somewhere else and
+ then goes back to the next block. Because more than one call can be normally used you can include a list of sequences to be called.
+ The 'nesting' of call blocks is also not allowed for the simplicity reasons. You can, of course, use the CALL blocks in the LOOP
+ sequences and vice versa. The value is relative for the obvious reasons - so that you can add some blocks in the beginning of the
+ file without disturbing the call values. Please take a look at 'Jump To Block' for reference on the values. */
+ private void ProcessBlockID26(byte[] data)
+ {
+ // block processing not implemented for this - just gets added for informational purposes only
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x26;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Call_Sequence;
+
+ int blockSize = 2 + 2 * GetWordValue(data, _position);
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += blockSize;
+ }
+ #endregion
+
+ #region ID 27 - Return from sequence
+/* length: 00
+
+ This block indicates the end of the Called Sequence. The next block played will be the block after the last CALL block (or the next Call,
+ if the Call block had multiple calls).
+ Again, this block has no body. */
+ private void ProcessBlockID27(byte[] data)
+ {
+ // block processing not implemented for this - just gets added for informational purposes only
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x27;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Return_From_Sequence;
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+ }
+ #endregion
+
+ #region ID 28 - Select block
+/* length: [00,01]+02
+ Offset Value Type Description
+ 0x00 - WORD Length of the whole block (without these two bytes)
+ 0x02 N BYTE Number of selections
+ 0x03 - SELECT[N] List of selections
+
+ ----
+ SELECT structure format
+ Offset Value Type Description
+ 0x00 - WORD Relative Offset
+ 0x02 L BYTE Length of description text
+ 0x03 - CHAR[L] Description text (please use single line and max. 30 chars)
+ ----
+
+ This block is useful when the tape consists of two or more separately-loadable parts. With this block, you are able to select
+ one of the parts and the utility/emulator will start loading from that block. For example you can use it when the game has a
+ separate Trainer or when it is a multiload. Of course, to make some use of it the emulator/utility has to show a menu with the
+ selections when it encounters such a block. All offsets are relative signed words. */
+ private void ProcessBlockID28(byte[] data)
+ {
+ // block processing not implemented for this - just gets added for informational purposes only
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x28;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Select_Block;
+
+ int blockSize = 2 + GetWordValue(data, _position);
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += blockSize;
+ }
+ #endregion
+
+ #region ID 2A - Stop the tape if in 48K mode
+/* length: 04
+ Offset Value Type Description
+ 0x00 0 DWORD Length of the block without these four bytes (0)
+
+ When this block is encountered, the tape will stop ONLY if the machine is an 48K Spectrum. This block is to be used for
+ multiloading games that load one level at a time in 48K mode, but load the entire tape at once if in 128K mode.
+ This block has no body of its own, but follows the extension rule. */
+ private void ProcessBlockID2A(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x2A;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Stop_the_Tape_48K;
+ t.Command = TapeCommand.STOP_THE_TAPE_48K;
+
+ int blockSize = 4 + GetWordValue(data, _position);
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += blockSize;
+ }
+ #endregion
+
+ #region ID 2B - Set signal level
+/* length: 05
+ Offset Value Type Description
+ 0x00 1 DWORD Block length (without these four bytes)
+ 0x04 - BYTE Signal level (0=low, 1=high)
+
+ This block sets the current signal level to the specified value (high or low). It should be used whenever it is necessary to avoid any
+ ambiguities, e.g. with custom loaders which are level-sensitive. */
+ private void ProcessBlockID2B(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x2B;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Set_Signal_Level;
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += 5;
+ }
+ #endregion
+
+ #region ID 30 - Text description
+/* length: [00]+01
+ Offset Value Type Description
+ 0x00 N BYTE Length of the text description
+ 0x01 - CHAR[N] Text description in ASCII format
+
+ This is meant to identify parts of the tape, so you know where level 1 starts, where to rewind to when the game ends, etc.
+ This description is not guaranteed to be shown while the tape is playing, but can be read while browsing the tape or changing
+ the tape pointer.
+ The description can be up to 255 characters long but please keep it down to about 30 so the programs can show it in one line
+ (where this is appropriate).
+ Please use 'Archive Info' block for title, authors, publisher, etc. */
+ private void ProcessBlockID30(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x30;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Text_Description;
+
+ int textLen = data[_position];
+ _position++;
+
+ string desc = Encoding.ASCII.GetString(data, _position, textLen);
+
+ //t.BlockDescription = "[" + desc + "]";
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += textLen;
+ }
+ #endregion
+
+ #region ID 31 - Message block
+/* length: [01]+02
+ Offset Value Type Description
+ 0x00 - BYTE Time (in seconds) for which the message should be displayed
+ 0x01 N BYTE Length of the text message
+ 0x02 - CHAR[N] Message that should be displayed in ASCII format
+
+ This will enable the emulators to display a message for a given time. This should not stop the tape and it should not make silence.
+ If the time is 0 then the emulator should wait for the user to press a key.
+ The text message should:
+ stick to a maximum of 30 chars per line;
+ use single 0x0D (13 decimal) to separate lines;
+ stick to a maximum of 8 lines.
+ If you do not obey these rules, emulators may display your message in any way they like. */
+ private void ProcessBlockID31(byte[] data)
+ {
+ // currently not implemented properly in ZXHawk
+
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x31;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Message_Block;
+
+ _position++;
+
+ int msgLen = data[_position];
+ _position++;
+
+ string desc = Encoding.ASCII.GetString(data, _position, msgLen);
+
+ t.Command = TapeCommand.SHOW_MESSAGE;
+
+ //t.BlockDescription = "[MESSAGE: " + desc + "]";
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += msgLen;
+ }
+ #endregion
+
+ #region ID 32 - Archive info
+/* length: [00,01]+02
+ Offset Value Type Description
+ 0x00 - WORD Length of the whole block (without these two bytes)
+ 0x02 N BYTE Number of text strings
+ 0x03 - TEXT[N] List of text strings
+
+ ----
+ TEXT structure format
+ Offset Value Type Description
+ 0x00 - BYTE Text identification byte:
+ 00 - Full title
+ 01 - Software house/publisher
+ 02 - Author(s)
+ 03 - Year of publication
+ 04 - Language
+ 05 - Game/utility type
+ 06 - Price
+ 07 - Protection scheme/loader
+ 08 - Origin
+ FF - Comment(s)
+ 0x01 L BYTE Length of text string
+ 0x02 - CHAR[L] Text string in ASCII format
+ ----
+
+ Use this block at the beginning of the tape to identify the title of the game, author, publisher, year of publication, price (including
+ the currency), type of software (arcade adventure, puzzle, word processor, ...), protection scheme it uses (Speedlock 1, Alkatraz, ...)
+ and its origin (Original, Budget re-release, ...), etc. This block is built in a way that allows easy future expansion.
+ The block consists of a series of text strings. Each text has its identification number (which tells us what the text means) and then
+ the ASCII text. To make it possible to skip this block, if needed, the length of the whole block is at the beginning of it.
+ If all texts on the tape are in English language then you don't have to supply the 'Language' field
+ The information about what hardware the tape uses is in the 'Hardware Type' block, so no need for it here. */
+ private void ProcessBlockID32(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x32;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Archive_Info;
+
+ int blockLen = GetWordValue(data, 0);
+ _position += 2;
+ int stringCount = data[_position++];
+
+ // iterate through each string
+ for (int s = 0; s < stringCount; s++)
+ {
+ // identify the type of text
+ int type = data[_position++];
+
+ // get text length
+ int strLen = data[_position++];
+
+ string title = "Info: ";
+
+ switch (type)
+ {
+ case 0x00:
+ title = "Full Title: ";
+ break;
+ case 0x01:
+ title = "Software House/Publisher: ";
+ break;
+ case 0x02:
+ title = "Author(s): ";
+ break;
+ case 0x03:
+ title = "Year of Publication: ";
+ break;
+ case 0x04:
+ title = "Language: ";
+ break;
+ case 0x05:
+ title = "Game/Utility Type: ";
+ break;
+ case 0x06:
+ title = "Price: ";
+ break;
+ case 0x07:
+ title = "Protection Scheme/Loader: ";
+ break;
+ case 0x08:
+ title = "Origin: ";
+ break;
+ case 0xFF:
+ title = "Comment(s): ";
+ break;
+ default:
+ break;
+ }
+
+ // add title to description
+ //t.BlockDescription += title;
+
+ // get string data
+ string val = Encoding.ASCII.GetString(data, _position, strLen);
+ //t.BlockDescription += val + " \n";
+
+ // advance to next string block
+ _position += strLen;
+ }
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+ }
+ #endregion
+
+ #region ID 33 - Hardware type
+/* length: [00]*03+01
+ Offset Value Type Description
+ 0x00 N BYTE Number of machines and hardware types for which info is supplied
+ 0x01 - HWINFO[N] List of machines and hardware
+
+ ----
+ HWINFO structure format
+ Offset Value Type Description
+ 0x00 - BYTE Hardware type
+ 0x01 - BYTE Hardware ID
+ 0x02 - BYTE Hardware information:
+ 00 - The tape RUNS on this machine or with this hardware,
+ but may or may not use the hardware or special features of the machine.
+ 01 - The tape USES the hardware or special features of the machine,
+ such as extra memory or a sound chip.
+ 02 - The tape RUNS but it DOESN'T use the hardware
+ or special features of the machine.
+ 03 - The tape DOESN'T RUN on this machine or with this hardware.
+ ----
+
+ This blocks contains information about the hardware that the programs on this tape use. Please include only machines and hardware for
+ which you are 100% sure that it either runs (or doesn't run) on or with, or you know it uses (or doesn't use) the hardware or special
+ features of that machine.
+ If the tape runs only on the ZX81 (and TS1000, etc.) then it clearly won't work on any Spectrum or Spectrum variant, so there's no
+ need to list this information.
+ If you are not sure or you haven't tested a tape on some particular machine/hardware combination then do not include it in the list.
+ The list of hardware types and IDs is somewhat large, and may be found at the end of the format description. */
+ private void ProcessBlockID33(byte[] data)
+ {
+ // currently not implemented properly in ZXHawk
+
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x33;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Hardware_Type;
+
+ _position += 2;
+ int blockLen = GetWordValue(data, 0);
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += blockLen;
+ }
+ #endregion
+
+ #region ID 35 - Custom info block
+/* length: [10,11,12,13]+14
+ Offset Value Type Description
+ 0x00 - CHAR[10] Identification string (in ASCII)
+ 0x10 L DWORD Length of the custom info
+ 0x14 - BYTE[L] Custom info
+
+ This block can be used to save any information you want. For example, it might contain some information written by a utility,
+ extra settings required by a particular emulator, or even poke data. */
+ private void ProcessBlockID35(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x35;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Custom_Info_Block;
+
+ string info = Encoding.ASCII.GetString(data, _position, 0x10);
+ //t.BlockDescription = "[CUSTOM INFO: " + info + "]";
+ _position += 0x10;
+
+ int blockLen = BitConverter.ToInt32(data, _position);
+ _position += 4;
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += blockLen;
+ }
+ #endregion
+
+ #region ID 5A - "Glue" block
+/* length: 09
+ Offset Value Type Description
+ 0x00 - BYTE[9] Value: { "XTape!",0x1A,MajR,MinR }
+ Just skip these 9 bytes and you will end up on the next ID.
+
+ This block is generated when you merge two ZX Tape files together. It is here so that you can easily copy the files together and use
+ them. Of course, this means that resulting file would be 10 bytes longer than if this block was not used. All you have to do
+ if you encounter this block ID is to skip next 9 bytes.
+ If you can avoid using this block for this purpose, then do so; it is preferable to use a utility to join the two files and
+ ensure that they are both of the higher version number. */
+ private void ProcessBlockID5A(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x5A;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Glue_Block;
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += 9;
+ }
+ #endregion
+
+ #region UnDetected Blocks
+
+ private void ProcessUnidentifiedBlock(byte[] data)
+ {
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = -2;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Unsupported;
+ //t.BlockDescription = "[UNSUPPORTED - 0x" + data[_position - 1] + "]";
+
+ _position += GetInt32(data, _position) & 0xFFFFFF;
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += 4;
+ }
+
+ #endregion
+
+ #region Depreciated Blocks
+
+ // These mostly should be ignored by ZXHawk - here for completeness
+
+ #region ID 16 - C64 ROM Type Data Block
+ private void ProcessBlockID16(byte[] data)
+ {
+
+ }
+ #endregion
+
+ #region ID 17 - C64 Turbo Tape Data Block
+ private void ProcessBlockID17(byte[] data)
+ {
+
+ }
+ #endregion
+
+ #region ID 34 - Emulation info
+ private void ProcessBlockID34(byte[] data)
+ {
+ // currently not implemented properly in ZXHawk
+
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x34;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Emulation_Info;
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += 8;
+ }
+ #endregion
+
+ #region ID 40 - Snapshot block
+ /* length: [01,02,03]+04
+ Offset Value Type Description
+ 0x00 - BYTE Snapshot type:
+ 00: .Z80 format
+ 01: .SNA format
+ 0x01 L BYTE[3] Snapshot length
+ 0x04 - BYTE[L] Snapshot itself
+
+ This would enable one to snapshot the game at the start and still have all the tape blocks (level data, etc.) in the same file.
+ Only .Z80 and .SNA snapshots are supported for compatibility reasons!
+ The emulator should take care of that the snapshot is not taken while the actual Tape loading is taking place (which doesn't do much sense).
+ And when an emulator encounters the snapshot block it should load it and then continue with the next block. */
+ private void ProcessBlockID40(byte[] data)
+ {
+ // currently not implemented properly in ZXHawk
+
+ TapeDataBlock t = new TapeDataBlock();
+ t.BlockID = 0x40;
+ t.DataPeriods = new List();
+ t.BlockDescription = BlockType.Snapshot_Block;
+
+ _position++;
+
+ int blockLen = data[_position] |
+ data[_position + 1] << 8 |
+ data[_position + 2] << 16;
+ _position += 3;
+
+ // add to tape
+ _datacorder.DataBlocks.Add(t);
+
+ // advance to next block
+ _position += blockLen;
+ }
+ #endregion
+
+ #endregion
+
+ #endregion
+
+ #region DataBlockDecoder
+
+ ///
+ /// Used to process either a standard or turbo data block
+ ///
+ ///
+ ///
+ ///
+ private TapeDataBlock DecodeDataBlock
+ (
+ TapeDataBlock block,
+ byte[] blockdata,
+ DataBlockType dataBlockType,
+ int pauseAfterBlock,
+ int pilotCount,
+
+ int pilotToneLength = 2168,
+ int sync1PulseLength = 667,
+ int sync2PulseLength = 735,
+ int bit0PulseLength = 855,
+ int bit1PulseLength = 1710,
+ int bitsInLastByte = 8
+ )
+ {
+ // first get the block description
+ string description = string.Empty;
+
+ // process the type byte
+ /* (The type is 0,1,2 or 3 for a Program, Number array, Character array or Code file.
+ A SCREEN$ file is regarded as a Code file with start address 16384 and length 6912 decimal.
+ If the file is a Program file, parameter 1 holds the autostart line number (or a number >=32768 if no LINE parameter was given)
+ and parameter 2 holds the start of the variable area relative to the start of the program. If it's a Code file, parameter 1 holds
+ the start of the code block when saved, and parameter 2 holds 32768. For data files finally, the byte at position 14 decimal holds the variable name.)
+ */
+
+ int blockSize = blockdata.Length;
+
+ // dont get description info for Pure Data Blocks
+ if (dataBlockType != DataBlockType.Pure)
+ {
+ if (blockdata[0] == 0x00 && blockSize == 19 && (blockdata[1] == 0x00) || blockdata[1] == 3)
+ {
+ // This is the program header
+ string fileName = Encoding.ASCII.GetString(blockdata.Skip(2).Take(10).ToArray()).Trim();
+
+ string type = "";
+ if (blockdata[0] == 0x00)
+ {
+ type = "Program";
+ block.AddMetaData(BlockDescriptorTitle.Program, fileName);
+ }
+ else
+ {
+ type = "Bytes";
+ block.AddMetaData(BlockDescriptorTitle.Bytes, fileName);
+ }
+
+ // now build the description string
+ StringBuilder sb = new StringBuilder();
+ sb.Append(type + ": ");
+ sb.Append(fileName + " ");
+ sb.Append(GetWordValue(blockdata, 14));
+ sb.Append(":");
+ sb.Append(GetWordValue(blockdata, 12));
+ description = sb.ToString();
+ }
+ else if (blockdata[0] == 0xFF)
+ {
+ // this is a data block
+ description = "Data Block " + (blockSize - 2) + "bytes";
+ block.AddMetaData(BlockDescriptorTitle.Data_Bytes, (blockSize - 2).ToString() + " Bytes");
+ }
+ else
+ {
+ // other type
+ description = string.Format("#{0} block, {1} bytes", blockdata[0].ToString("X2"), blockSize - 2);
+ //description += string.Format(", crc {0}", ((crc != 0) ? string.Format("bad (#{0:X2}!=#{1:X2})", crcFile, crcValue) : "ok"));
+ block.AddMetaData(BlockDescriptorTitle.Undefined, description);
+ }
+ }
+
+ // update metadata
+ switch (dataBlockType)
+ {
+ case DataBlockType.Standard:
+ case DataBlockType.Turbo:
+
+ if (dataBlockType == DataBlockType.Standard)
+ block.BlockDescription = BlockType.Standard_Speed_Data_Block;
+ if (dataBlockType == DataBlockType.Turbo)
+ block.BlockDescription = BlockType.Turbo_Speed_Data_Block;
+
+ block.AddMetaData(BlockDescriptorTitle.Pilot_Pulse_Length, pilotToneLength.ToString() + " T-States");
+ block.AddMetaData(BlockDescriptorTitle.Pilot_Pulse_Count, pilotCount.ToString() + " Pulses");
+ block.AddMetaData(BlockDescriptorTitle.First_Sync_Length, sync1PulseLength.ToString() + " T-States");
+ block.AddMetaData(BlockDescriptorTitle.Second_Sync_Length, sync2PulseLength.ToString() + " T-States");
+ break;
+
+ case DataBlockType.Pure:
+ block.BlockDescription = BlockType.Pure_Data_Block;
+ break;
+ }
+
+ block.AddMetaData(BlockDescriptorTitle.Zero_Bit_Length, bit0PulseLength.ToString() + " T-States");
+ block.AddMetaData(BlockDescriptorTitle.One_Bit_Length, bit1PulseLength.ToString() + " T-States");
+ block.AddMetaData(BlockDescriptorTitle.Data_Length, blockSize.ToString() + " Bytes");
+ block.AddMetaData(BlockDescriptorTitle.Bits_In_Last_Byte, bitsInLastByte.ToString() + " Bits");
+ block.AddMetaData(BlockDescriptorTitle.Pause_After_Data, pauseAfterBlock.ToString() + " ms");
+
+ // calculate period information
+ List dataPeriods = new List();
+
+ // generate pilot pulses
+
+ if (pilotCount > 0)
+ {
+ for (int i = 0; i < pilotCount; i++)
+ {
+ dataPeriods.Add(pilotToneLength);
+ }
+
+ // add syncro pulses
+ dataPeriods.Add(sync1PulseLength);
+ dataPeriods.Add(sync2PulseLength);
+ }
+
+ int pos = 0;
+
+ // add bit0 and bit1 periods
+ for (int i = 0; i < blockSize - 1; i++, pos++)
+ {
+ for (byte b = 0x80; b != 0; b >>= 1)
+ {
+ if ((blockdata[i] & b) != 0)
+ dataPeriods.Add(bit1PulseLength);
+ else
+ dataPeriods.Add(bit0PulseLength);
+ if ((blockdata[i] & b) != 0)
+ dataPeriods.Add(bit1PulseLength);
+ else
+ dataPeriods.Add(bit0PulseLength);
+ }
+ }
+
+ // add the last byte
+ for (byte c = 0x80; c != (byte)(0x80 >> bitsInLastByte); c >>= 1)
+ {
+ if ((blockdata[pos] & c) != 0)
+ dataPeriods.Add(bit1PulseLength);
+ else
+ dataPeriods.Add(bit0PulseLength);
+ if ((blockdata[pos] & c) != 0)
+ dataPeriods.Add(bit1PulseLength);
+ else
+ dataPeriods.Add(bit0PulseLength);
+ }
+
+ // add block pause if pause is not 0
+ if (pauseAfterBlock != 0)
+ {
+ int actualPause = pauseAfterBlock * 3500;
+ dataPeriods.Add(actualPause);
+ }
+
+ // add to the tapedatablock object
+ block.DataPeriods = dataPeriods;
+
+ // add the raw data
+ block.BlockData = blockdata;
+
+ return block;
+ }
+
+ ///
+ /// Used to process either a standard or turbo data block
+ ///
+ ///
+ ///
+ ///
+ private TapeDataBlock DecodeDataBlock
+ (
+ TapeDataBlock block,
+ byte[] blockData,
+ DataBlockType dataBlockType,
+ int pauseAfterBlock,
+
+ int pilotToneLength = 2168,
+ int sync1PulseLength = 667,
+ int sync2PulseLength = 735,
+ int bit0PulseLength = 855,
+ int bit1PulseLength = 1710,
+ int bitsInLastByte = 8
+ )
+ {
+
+ // pilot count needs to be ascertained from flag byte
+ int pilotCount;
+ if (blockData[0] < 128)
+ pilotCount = 8063;
+ else
+ pilotCount = 3223;
+
+ // now we can decode
+ var nBlock = DecodeDataBlock
+ (
+ block,
+ blockData,
+ dataBlockType,
+ pauseAfterBlock,
+ pilotCount,
+ pilotToneLength,
+ sync1PulseLength,
+ sync2PulseLength,
+ bit0PulseLength,
+ bit1PulseLength,
+ bitsInLastByte
+ );
+
+
+ return nBlock;
+ }
+
+ #endregion
+ }
+
+ public enum DataBlockType
+ {
+ Standard,
+ Turbo,
+ Pure
+ }
+}