ZX Spectrum tape file: Lua parsing library

TAP files are used by emulators of ZX Spectrum computer (released in 1982 by Sinclair Research). TAP file stores blocks of data as if they are written to magnetic tape, which was used as primary media for ZX Spectrum. Contents of this file can be viewed as a very simple linear filesystem, storing named files with some basic metainformation prepended as a header.

File extension

tap

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of ZX Spectrum tape file using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Lua source code to parse ZX Spectrum tape file

zx_spectrum_tap.lua

-- This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
--
-- This file is compatible with Lua 5.3

local class = require("class")
require("kaitaistruct")
local enum = require("enum")

-- 
-- TAP files are used by emulators of ZX Spectrum computer (released in
-- 1982 by Sinclair Research). TAP file stores blocks of data as if
-- they are written to magnetic tape, which was used as primary media
-- for ZX Spectrum. Contents of this file can be viewed as a very
-- simple linear filesystem, storing named files with some basic
-- metainformation prepended as a header.
-- See also: Source (https://faqwiki.zxnet.co.uk/wiki/TAP_format)
ZxSpectrumTap = class.class(KaitaiStruct)

ZxSpectrumTap.FlagEnum = enum.Enum {
  header = 0,
  data = 255,
}

ZxSpectrumTap.HeaderTypeEnum = enum.Enum {
  program = 0,
  num_array = 1,
  char_array = 2,
  bytes = 3,
}

function ZxSpectrumTap:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function ZxSpectrumTap:_read()
  self.blocks = {}
  local i = 0
  while not self._io:is_eof() do
    self.blocks[i + 1] = ZxSpectrumTap.Block(self._io, self, self._root)
    i = i + 1
  end
end


ZxSpectrumTap.Block = class.class(KaitaiStruct)

function ZxSpectrumTap.Block:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function ZxSpectrumTap.Block:_read()
  self.len_block = self._io:read_u2le()
  self.flag = ZxSpectrumTap.FlagEnum(self._io:read_u1())
  if  ((self.len_block == 19) and (self.flag == ZxSpectrumTap.FlagEnum.header))  then
    self.header = ZxSpectrumTap.Header(self._io, self, self._root)
  end
  if self.len_block == 19 then
    self.data = self._io:read_bytes((self.header.len_data + 4))
  end
  if self.flag == ZxSpectrumTap.FlagEnum.data then
    self.headerless_data = self._io:read_bytes((self.len_block - 1))
  end
end


ZxSpectrumTap.ProgramParams = class.class(KaitaiStruct)

function ZxSpectrumTap.ProgramParams:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function ZxSpectrumTap.ProgramParams:_read()
  self.autostart_line = self._io:read_u2le()
  self.len_program = self._io:read_u2le()
end


ZxSpectrumTap.BytesParams = class.class(KaitaiStruct)

function ZxSpectrumTap.BytesParams:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function ZxSpectrumTap.BytesParams:_read()
  self.start_address = self._io:read_u2le()
  self.reserved = self._io:read_bytes(2)
end


ZxSpectrumTap.Header = class.class(KaitaiStruct)

function ZxSpectrumTap.Header:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function ZxSpectrumTap.Header:_read()
  self.header_type = ZxSpectrumTap.HeaderTypeEnum(self._io:read_u1())
  self.filename = KaitaiStream.bytes_strip_right(self._io:read_bytes(10), 32)
  self.len_data = self._io:read_u2le()
  local _on = self.header_type
  if _on == ZxSpectrumTap.HeaderTypeEnum.program then
    self.params = ZxSpectrumTap.ProgramParams(self._io, self, self._root)
  elseif _on == ZxSpectrumTap.HeaderTypeEnum.num_array then
    self.params = ZxSpectrumTap.ArrayParams(self._io, self, self._root)
  elseif _on == ZxSpectrumTap.HeaderTypeEnum.char_array then
    self.params = ZxSpectrumTap.ArrayParams(self._io, self, self._root)
  elseif _on == ZxSpectrumTap.HeaderTypeEnum.bytes then
    self.params = ZxSpectrumTap.BytesParams(self._io, self, self._root)
  end
  self.checksum = self._io:read_u1()
end

-- 
-- Bitwise XOR of all bytes including the flag byte.

ZxSpectrumTap.ArrayParams = class.class(KaitaiStruct)

function ZxSpectrumTap.ArrayParams:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function ZxSpectrumTap.ArrayParams:_read()
  self.reserved = self._io:read_u1()
  self.var_name = self._io:read_u1()
  self.reserved1 = self._io:read_bytes(2)
  if not(self.reserved1 == "\000\128") then
    error("not equal, expected " ..  "\000\128" .. ", but got " .. self.reserved1)
  end
end

-- 
-- Variable name (1..26 meaning A$..Z$ +192).