WIP implementing TZX support
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Core.Io;
|
||||||
|
|
||||||
namespace Core.Io
|
namespace Core.Io
|
||||||
{
|
{
|
||||||
@@ -7,6 +8,7 @@ namespace Core.Io
|
|||||||
{
|
{
|
||||||
private Queue<byte[]> _blocks = new Queue<byte[]>();
|
private Queue<byte[]> _blocks = new Queue<byte[]>();
|
||||||
private byte[] _currentBlock;
|
private byte[] _currentBlock;
|
||||||
|
private List<TzxBlock> _tzxBlocks = new List<TzxBlock>();
|
||||||
|
|
||||||
// State Machine Tracking
|
// State Machine Tracking
|
||||||
private enum TapeState { Idle, Pilot, Sync1, Sync2, Data, Pause }
|
private enum TapeState { Idle, Pilot, Sync1, Sync2, Data, Pause }
|
||||||
@@ -204,5 +206,13 @@ namespace Core.Io
|
|||||||
_currentBlock = null;
|
_currentBlock = null;
|
||||||
return _blocks.Count > 0 ? _blocks.Dequeue() : null;
|
return _blocks.Count > 0 ? _blocks.Dequeue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void LoadTzxData(List<TzxBlock> blocks)
|
||||||
|
{
|
||||||
|
_tzxBlocks = blocks;
|
||||||
|
|
||||||
|
// Just to prove it works before we build the State Machine!
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Successfully loaded {_tzxBlocks.Count} TZX blocks!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
24
Core/Io/TzxBlocks.cs
Normal file
24
Core/Io/TzxBlocks.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Core.Io
|
||||||
|
{
|
||||||
|
// The base class all future TZX blocks will inherit from
|
||||||
|
public abstract class TzxBlock
|
||||||
|
{
|
||||||
|
public abstract int BlockId { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID 0x10: Standard Speed Data Block
|
||||||
|
public class StandardSpeedBlock : TzxBlock
|
||||||
|
{
|
||||||
|
public override int BlockId => 0x10;
|
||||||
|
|
||||||
|
// How long to pause the tape in milliseconds after this block loads
|
||||||
|
public ushort PauseAfterMs { get; set; }
|
||||||
|
|
||||||
|
public ushort DataLength { get; set; }
|
||||||
|
|
||||||
|
// The actual tape bytes
|
||||||
|
public byte[] Data { get; set; } = Array.Empty<byte>();
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Core/Io/TzxParser.cs
Normal file
56
Core/Io/TzxParser.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ namespace Core
|
|||||||
public IO_Bus IoBus { get; private set; }
|
public IO_Bus IoBus { get; private set; }
|
||||||
public ULA Ula { get; private set; }
|
public ULA Ula { get; private set; }
|
||||||
public TapManager TapeDeck { get; private set; }
|
public TapManager TapeDeck { get; private set; }
|
||||||
|
public TzxParser _tzxParser { get; set; }
|
||||||
|
|
||||||
// Audio (If BeeperDevice is in Desktop, you may need to move it to Core,
|
// Audio (If BeeperDevice is in Desktop, you may need to move it to Core,
|
||||||
// or inject it via an interface later. For now, assuming it's accessible).
|
// or inject it via an interface later. For now, assuming it's accessible).
|
||||||
@@ -276,6 +277,8 @@ namespace Core
|
|||||||
ForceRet();
|
ForceRet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void ForceRet()
|
private void ForceRet()
|
||||||
{
|
{
|
||||||
byte pcLow = Memory.Read(Cpu.SP);
|
byte pcLow = Memory.Read(Cpu.SP);
|
||||||
|
|||||||
@@ -392,6 +392,14 @@ namespace Desktop
|
|||||||
{
|
{
|
||||||
_machine.LoadSnapshot(fileBytes);
|
_machine.LoadSnapshot(fileBytes);
|
||||||
}
|
}
|
||||||
|
else if (resourceName.EndsWith(".tzx", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// 1. Pass the raw bytes to our static parser
|
||||||
|
List<TzxBlock> tzxBlocks = TzxParser.Parse(fileBytes);
|
||||||
|
|
||||||
|
// 2. Hand the cleanly parsed blocks over to the Tape Deck
|
||||||
|
_machine.TapeDeck.LoadTzxData(tzxBlocks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user