Files
ZXSpectrum48K/Core/Io/TzxParser.cs

101 lines
4.3 KiB
C#

using Core.Io;
using System;
using System.Collections.Generic;
using System.IO;
namespace Core.Io
{
public class TzxParser
{
public static List<TzxBlock> Parse(byte[] fileBytes)
{
List<TzxBlock> blocks = new List<TzxBlock>();
using (MemoryStream ms = new MemoryStream(fileBytes))
using (BinaryReader br = new BinaryReader(ms))
{
// 1. Verify the 10-Byte Header
// Signature is "ZXTape!" followed by EOF (0x1A)
byte[] signature = br.ReadBytes(8);
string sigString = System.Text.Encoding.ASCII.GetString(signature);
if (sigString != "ZXTape!\x1A")
throw new Exception("Invalid TZX Signature! Is this a real TZX file?");
byte majorVersion = br.ReadByte();
byte minorVersion = br.ReadByte();
// 2. Read the blocks until we hit the end of the file
while (ms.Position < ms.Length)
{
byte blockId = br.ReadByte();
switch (blockId)
{
case 0x10: // Standard Speed Data
var stdBlock = new StandardSpeedBlock
{
PauseAfterMs = br.ReadUInt16(),
DataLength = br.ReadUInt16()
};
stdBlock.Data = br.ReadBytes(stdBlock.DataLength);
blocks.Add(stdBlock);
break;
case 0x11: // Turbo Speed Data Block
var turboBlock = new TurboSpeedBlock
{
PilotPulseLength = br.ReadUInt16(),
SyncFirstPulseLength = br.ReadUInt16(),
SyncSecondPulseLength = br.ReadUInt16(),
ZeroBitPulseLength = br.ReadUInt16(),
OneBitPulseLength = br.ReadUInt16(),
PilotToneLength = br.ReadUInt16(),
UsedBitsInLastByte = br.ReadByte(),
PauseAfterMs = br.ReadUInt16()
};
// Z80 files are Little-Endian. Read 3 bytes and combine them into a 24-bit integer.
byte len0 = br.ReadByte();
byte len1 = br.ReadByte();
byte len2 = br.ReadByte();
turboBlock.DataLength = len0 | (len1 << 8) | (len2 << 16);
// Read the actual tape data
turboBlock.Data = br.ReadBytes(turboBlock.DataLength);
blocks.Add(turboBlock);
break;
case 0x32: // Archive Info Block
var archiveBlock = new ArchiveInfoBlock();
// The total length of the block (we read it to advance the stream,
// but the string count is what we actually use to loop)
ushort totalLength = br.ReadUInt16();
byte stringCount = br.ReadByte();
for (int i = 0; i < stringCount; i++)
{
byte textId = br.ReadByte();
byte textLength = br.ReadByte();
// Read the raw bytes and convert them to an ASCII string
string text = System.Text.Encoding.ASCII.GetString(br.ReadBytes(textLength));
archiveBlock.Metadata[textId] = text;
}
blocks.Add(archiveBlock);
break;
// TODO: Add cases for 0x11 (Turbo), 0x12 (Tone), 0x13 (Pulses), etc.
default:
throw new NotImplementedException($"TZX Block ID 0x{blockId:X2} is not supported yet!");
}
}
}
return blocks;
}
}
}