using Core.Io; using System; using System.Collections.Generic; using System.IO; namespace Core.Io { public class TzxParser { public static List Parse(byte[] fileBytes) { List blocks = new List(); 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; } } }