ZX Spectrum tape file: Go 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.

Go source code to parse ZX Spectrum tape file

zx_spectrum_tap.go

// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.

import (
	"github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"
	"bytes"
)


/**
 * 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 <a href="https://sinclair.wiki.zxnet.co.uk/wiki/TAP_format">Source</a>
 */

type ZxSpectrumTap_FlagEnum int
const (
	ZxSpectrumTap_FlagEnum__Header ZxSpectrumTap_FlagEnum = 0
	ZxSpectrumTap_FlagEnum__Data ZxSpectrumTap_FlagEnum = 255
)
var values_ZxSpectrumTap_FlagEnum = map[ZxSpectrumTap_FlagEnum]struct{}{0: {}, 255: {}}
func (v ZxSpectrumTap_FlagEnum) isDefined() bool {
	_, ok := values_ZxSpectrumTap_FlagEnum[v]
	return ok
}

type ZxSpectrumTap_HeaderTypeEnum int
const (
	ZxSpectrumTap_HeaderTypeEnum__Program ZxSpectrumTap_HeaderTypeEnum = 0
	ZxSpectrumTap_HeaderTypeEnum__NumArray ZxSpectrumTap_HeaderTypeEnum = 1
	ZxSpectrumTap_HeaderTypeEnum__CharArray ZxSpectrumTap_HeaderTypeEnum = 2
	ZxSpectrumTap_HeaderTypeEnum__Bytes ZxSpectrumTap_HeaderTypeEnum = 3
)
var values_ZxSpectrumTap_HeaderTypeEnum = map[ZxSpectrumTap_HeaderTypeEnum]struct{}{0: {}, 1: {}, 2: {}, 3: {}}
func (v ZxSpectrumTap_HeaderTypeEnum) isDefined() bool {
	_, ok := values_ZxSpectrumTap_HeaderTypeEnum[v]
	return ok
}
type ZxSpectrumTap struct {
	Blocks []*ZxSpectrumTap_Block
	_io *kaitai.Stream
	_root *ZxSpectrumTap
	_parent kaitai.Struct
}
func NewZxSpectrumTap() *ZxSpectrumTap {
	return &ZxSpectrumTap{
	}
}

func (this ZxSpectrumTap) IO_() *kaitai.Stream {
	return this._io
}

func (this *ZxSpectrumTap) Read(io *kaitai.Stream, parent kaitai.Struct, root *ZxSpectrumTap) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	for i := 0;; i++ {
		tmp1, err := this._io.EOF()
		if err != nil {
			return err
		}
		if tmp1 {
			break
		}
		tmp2 := NewZxSpectrumTap_Block()
		err = tmp2.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Blocks = append(this.Blocks, tmp2)
	}
	return err
}
type ZxSpectrumTap_ArrayParams struct {
	Reserved uint8
	VarName uint8
	Reserved1 []byte
	_io *kaitai.Stream
	_root *ZxSpectrumTap
	_parent *ZxSpectrumTap_Header
}
func NewZxSpectrumTap_ArrayParams() *ZxSpectrumTap_ArrayParams {
	return &ZxSpectrumTap_ArrayParams{
	}
}

func (this ZxSpectrumTap_ArrayParams) IO_() *kaitai.Stream {
	return this._io
}

func (this *ZxSpectrumTap_ArrayParams) Read(io *kaitai.Stream, parent *ZxSpectrumTap_Header, root *ZxSpectrumTap) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp3, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.Reserved = tmp3
	tmp4, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.VarName = tmp4
	tmp5, err := this._io.ReadBytes(int(2))
	if err != nil {
		return err
	}
	tmp5 = tmp5
	this.Reserved1 = tmp5
	if !(bytes.Equal(this.Reserved1, []uint8{0, 128})) {
		return kaitai.NewValidationNotEqualError([]uint8{0, 128}, this.Reserved1, this._io, "/types/array_params/seq/2")
	}
	return err
}

/**
 * Variable name (1..26 meaning A$..Z$ +192)
 */
type ZxSpectrumTap_Block struct {
	LenBlock uint16
	Flag ZxSpectrumTap_FlagEnum
	Header *ZxSpectrumTap_Header
	Data []byte
	HeaderlessData []byte
	_io *kaitai.Stream
	_root *ZxSpectrumTap
	_parent *ZxSpectrumTap
}
func NewZxSpectrumTap_Block() *ZxSpectrumTap_Block {
	return &ZxSpectrumTap_Block{
	}
}

func (this ZxSpectrumTap_Block) IO_() *kaitai.Stream {
	return this._io
}

func (this *ZxSpectrumTap_Block) Read(io *kaitai.Stream, parent *ZxSpectrumTap, root *ZxSpectrumTap) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp6, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.LenBlock = uint16(tmp6)
	tmp7, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.Flag = ZxSpectrumTap_FlagEnum(tmp7)
	if ( ((this.LenBlock == 19) && (this.Flag == ZxSpectrumTap_FlagEnum__Header)) ) {
		tmp8 := NewZxSpectrumTap_Header()
		err = tmp8.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Header = tmp8
	}
	if (this.LenBlock == 19) {
		tmp9, err := this._io.ReadBytes(int(this.Header.LenData + 4))
		if err != nil {
			return err
		}
		tmp9 = tmp9
		this.Data = tmp9
	}
	if (this.Flag == ZxSpectrumTap_FlagEnum__Data) {
		tmp10, err := this._io.ReadBytes(int(this.LenBlock - 1))
		if err != nil {
			return err
		}
		tmp10 = tmp10
		this.HeaderlessData = tmp10
	}
	return err
}
type ZxSpectrumTap_BytesParams struct {
	StartAddress uint16
	Reserved []byte
	_io *kaitai.Stream
	_root *ZxSpectrumTap
	_parent *ZxSpectrumTap_Header
}
func NewZxSpectrumTap_BytesParams() *ZxSpectrumTap_BytesParams {
	return &ZxSpectrumTap_BytesParams{
	}
}

func (this ZxSpectrumTap_BytesParams) IO_() *kaitai.Stream {
	return this._io
}

func (this *ZxSpectrumTap_BytesParams) Read(io *kaitai.Stream, parent *ZxSpectrumTap_Header, root *ZxSpectrumTap) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp11, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.StartAddress = uint16(tmp11)
	tmp12, err := this._io.ReadBytes(int(2))
	if err != nil {
		return err
	}
	tmp12 = tmp12
	this.Reserved = tmp12
	return err
}
type ZxSpectrumTap_Header struct {
	HeaderType ZxSpectrumTap_HeaderTypeEnum
	Filename []byte
	LenData uint16
	Params kaitai.Struct
	Checksum uint8
	_io *kaitai.Stream
	_root *ZxSpectrumTap
	_parent *ZxSpectrumTap_Block
}
func NewZxSpectrumTap_Header() *ZxSpectrumTap_Header {
	return &ZxSpectrumTap_Header{
	}
}

func (this ZxSpectrumTap_Header) IO_() *kaitai.Stream {
	return this._io
}

func (this *ZxSpectrumTap_Header) Read(io *kaitai.Stream, parent *ZxSpectrumTap_Block, root *ZxSpectrumTap) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp13, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.HeaderType = ZxSpectrumTap_HeaderTypeEnum(tmp13)
	tmp14, err := this._io.ReadBytes(int(10))
	if err != nil {
		return err
	}
	tmp14 = kaitai.BytesStripRight(tmp14, 32)
	this.Filename = tmp14
	tmp15, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.LenData = uint16(tmp15)
	switch (this.HeaderType) {
	case ZxSpectrumTap_HeaderTypeEnum__Bytes:
		tmp16 := NewZxSpectrumTap_BytesParams()
		err = tmp16.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Params = tmp16
	case ZxSpectrumTap_HeaderTypeEnum__CharArray:
		tmp17 := NewZxSpectrumTap_ArrayParams()
		err = tmp17.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Params = tmp17
	case ZxSpectrumTap_HeaderTypeEnum__NumArray:
		tmp18 := NewZxSpectrumTap_ArrayParams()
		err = tmp18.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Params = tmp18
	case ZxSpectrumTap_HeaderTypeEnum__Program:
		tmp19 := NewZxSpectrumTap_ProgramParams()
		err = tmp19.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Params = tmp19
	}
	tmp20, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.Checksum = tmp20
	return err
}

/**
 * Bitwise XOR of all bytes including the flag byte
 */
type ZxSpectrumTap_ProgramParams struct {
	AutostartLine uint16
	LenProgram uint16
	_io *kaitai.Stream
	_root *ZxSpectrumTap
	_parent *ZxSpectrumTap_Header
}
func NewZxSpectrumTap_ProgramParams() *ZxSpectrumTap_ProgramParams {
	return &ZxSpectrumTap_ProgramParams{
	}
}

func (this ZxSpectrumTap_ProgramParams) IO_() *kaitai.Stream {
	return this._io
}

func (this *ZxSpectrumTap_ProgramParams) Read(io *kaitai.Stream, parent *ZxSpectrumTap_Header, root *ZxSpectrumTap) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp21, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.AutostartLine = uint16(tmp21)
	tmp22, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.LenProgram = uint16(tmp22)
	return err
}