.trd file is a raw dump of TR-DOS (ZX-Spectrum) floppy. .trd files are headerless and contain consequent "logical tracks", each logical track consists of 16 256-byte sectors.
Logical tracks are defined the same way as used by TR-DOS: for single-side floppies it's just a physical track number, for two-side floppies sides are interleaved, i.e. logical_track_num = (physical_track_num << 1) | side
So, this format definition is more for TR-DOS filesystem than for .trd files, which are formatless.
Strings (file names, disk label, disk password) are padded with spaces and use ZX Spectrum character set, including UDGs, block drawing chars and Basic tokens. ASCII range is mostly standard ASCII, with few characters (^, `, DEL) replaced with (up arrow, pound, copyright symbol).
.trd file can be smaller than actual floppy disk, if last logical tracks are empty (contain no file data) they can be omitted.
This page hosts a formal specification of TR-DOS flat-file disk image using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.
// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.
import (
"github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"
"io"
"bytes"
)
/**
* .trd file is a raw dump of TR-DOS (ZX-Spectrum) floppy. .trd files are
* headerless and contain consequent "logical tracks", each logical track
* consists of 16 256-byte sectors.
*
* Logical tracks are defined the same way as used by TR-DOS: for single-side
* floppies it's just a physical track number, for two-side floppies sides are
* interleaved, i.e. logical_track_num = (physical_track_num << 1) | side
*
* So, this format definition is more for TR-DOS filesystem than for .trd files,
* which are formatless.
*
* Strings (file names, disk label, disk password) are padded with spaces and use
* ZX Spectrum character set, including UDGs, block drawing chars and Basic
* tokens. ASCII range is mostly standard ASCII, with few characters (^, `, DEL)
* replaced with (up arrow, pound, copyright symbol).
*
* .trd file can be smaller than actual floppy disk, if last logical tracks are
* empty (contain no file data) they can be omitted.
*/
type TrDosImage_DiskType int
const (
TrDosImage_DiskType__Type80TracksDoubleSide TrDosImage_DiskType = 22
TrDosImage_DiskType__Type40TracksDoubleSide TrDosImage_DiskType = 23
TrDosImage_DiskType__Type80TracksSingleSide TrDosImage_DiskType = 24
TrDosImage_DiskType__Type40TracksSingleSide TrDosImage_DiskType = 25
)
var values_TrDosImage_DiskType = map[TrDosImage_DiskType]struct{}{22: {}, 23: {}, 24: {}, 25: {}}
func (v TrDosImage_DiskType) isDefined() bool {
_, ok := values_TrDosImage_DiskType[v]
return ok
}
type TrDosImage struct {
Files []*TrDosImage_File
_io *kaitai.Stream
_root *TrDosImage
_parent kaitai.Struct
_f_volumeInfo bool
volumeInfo *TrDosImage_VolumeInfo
}
func NewTrDosImage() *TrDosImage {
return &TrDosImage{
}
}
func (this TrDosImage) IO_() *kaitai.Stream {
return this._io
}
func (this *TrDosImage) Read(io *kaitai.Stream, parent kaitai.Struct, root *TrDosImage) (err error) {
this._io = io
this._parent = parent
this._root = root
for i := 1;; i++ {
tmp1 := NewTrDosImage_File()
err = tmp1.Read(this._io, this, this._root)
if err != nil {
return err
}
_it := tmp1
this.Files = append(this.Files, _it)
tmp2, err := _it.IsTerminator()
if err != nil {
return err
}
if tmp2 {
break
}
}
return err
}
func (this *TrDosImage) VolumeInfo() (v *TrDosImage_VolumeInfo, err error) {
if (this._f_volumeInfo) {
return this.volumeInfo, nil
}
this._f_volumeInfo = true
_pos, err := this._io.Pos()
if err != nil {
return nil, err
}
_, err = this._io.Seek(int64(2048), io.SeekStart)
if err != nil {
return nil, err
}
tmp3 := NewTrDosImage_VolumeInfo()
err = tmp3.Read(this._io, this, this._root)
if err != nil {
return nil, err
}
this.volumeInfo = tmp3
_, err = this._io.Seek(_pos, io.SeekStart)
if err != nil {
return nil, err
}
return this.volumeInfo, nil
}
type TrDosImage_File struct {
Name *TrDosImage_Filename
Extension uint8
PositionAndLength kaitai.Struct
LengthSectors uint8
StartingSector uint8
StartingTrack uint8
_io *kaitai.Stream
_root *TrDosImage
_parent *TrDosImage
_raw_Name []byte
_f_contents bool
contents []byte
_f_isDeleted bool
isDeleted bool
_f_isTerminator bool
isTerminator bool
}
func NewTrDosImage_File() *TrDosImage_File {
return &TrDosImage_File{
}
}
func (this TrDosImage_File) IO_() *kaitai.Stream {
return this._io
}
func (this *TrDosImage_File) Read(io *kaitai.Stream, parent *TrDosImage, root *TrDosImage) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp4, err := this._io.ReadBytes(int(8))
if err != nil {
return err
}
tmp4 = tmp4
this._raw_Name = tmp4
_io__raw_Name := kaitai.NewStream(bytes.NewReader(this._raw_Name))
tmp5 := NewTrDosImage_Filename()
err = tmp5.Read(_io__raw_Name, this, this._root)
if err != nil {
return err
}
this.Name = tmp5
tmp6, err := this._io.ReadU1()
if err != nil {
return err
}
this.Extension = tmp6
switch (this.Extension) {
case 35:
tmp7 := NewTrDosImage_PositionAndLengthPrint()
err = tmp7.Read(this._io, this, this._root)
if err != nil {
return err
}
this.PositionAndLength = tmp7
case 66:
tmp8 := NewTrDosImage_PositionAndLengthBasic()
err = tmp8.Read(this._io, this, this._root)
if err != nil {
return err
}
this.PositionAndLength = tmp8
case 67:
tmp9 := NewTrDosImage_PositionAndLengthCode()
err = tmp9.Read(this._io, this, this._root)
if err != nil {
return err
}
this.PositionAndLength = tmp9
default:
tmp10 := NewTrDosImage_PositionAndLengthGeneric()
err = tmp10.Read(this._io, this, this._root)
if err != nil {
return err
}
this.PositionAndLength = tmp10
}
tmp11, err := this._io.ReadU1()
if err != nil {
return err
}
this.LengthSectors = tmp11
tmp12, err := this._io.ReadU1()
if err != nil {
return err
}
this.StartingSector = tmp12
tmp13, err := this._io.ReadU1()
if err != nil {
return err
}
this.StartingTrack = tmp13
return err
}
func (this *TrDosImage_File) Contents() (v []byte, err error) {
if (this._f_contents) {
return this.contents, nil
}
this._f_contents = true
_pos, err := this._io.Pos()
if err != nil {
return nil, err
}
_, err = this._io.Seek(int64((this.StartingTrack * 256) * 16 + this.StartingSector * 256), io.SeekStart)
if err != nil {
return nil, err
}
tmp14, err := this._io.ReadBytes(int(this.LengthSectors * 256))
if err != nil {
return nil, err
}
tmp14 = tmp14
this.contents = tmp14
_, err = this._io.Seek(_pos, io.SeekStart)
if err != nil {
return nil, err
}
return this.contents, nil
}
func (this *TrDosImage_File) IsDeleted() (v bool, err error) {
if (this._f_isDeleted) {
return this.isDeleted, nil
}
this._f_isDeleted = true
tmp15, err := this.Name.FirstByte()
if err != nil {
return false, err
}
this.isDeleted = bool(tmp15 == 1)
return this.isDeleted, nil
}
func (this *TrDosImage_File) IsTerminator() (v bool, err error) {
if (this._f_isTerminator) {
return this.isTerminator, nil
}
this._f_isTerminator = true
tmp16, err := this.Name.FirstByte()
if err != nil {
return false, err
}
this.isTerminator = bool(tmp16 == 0)
return this.isTerminator, nil
}
type TrDosImage_Filename struct {
Name []byte
_io *kaitai.Stream
_root *TrDosImage
_parent *TrDosImage_File
_f_firstByte bool
firstByte uint8
}
func NewTrDosImage_Filename() *TrDosImage_Filename {
return &TrDosImage_Filename{
}
}
func (this TrDosImage_Filename) IO_() *kaitai.Stream {
return this._io
}
func (this *TrDosImage_Filename) Read(io *kaitai.Stream, parent *TrDosImage_File, root *TrDosImage) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp17, err := this._io.ReadBytes(int(8))
if err != nil {
return err
}
tmp17 = tmp17
this.Name = tmp17
return err
}
func (this *TrDosImage_Filename) FirstByte() (v uint8, err error) {
if (this._f_firstByte) {
return this.firstByte, nil
}
this._f_firstByte = true
_pos, err := this._io.Pos()
if err != nil {
return 0, err
}
_, err = this._io.Seek(int64(0), io.SeekStart)
if err != nil {
return 0, err
}
tmp18, err := this._io.ReadU1()
if err != nil {
return 0, err
}
this.firstByte = tmp18
_, err = this._io.Seek(_pos, io.SeekStart)
if err != nil {
return 0, err
}
return this.firstByte, nil
}
type TrDosImage_PositionAndLengthBasic struct {
ProgramAndDataLength uint16
ProgramLength uint16
_io *kaitai.Stream
_root *TrDosImage
_parent *TrDosImage_File
}
func NewTrDosImage_PositionAndLengthBasic() *TrDosImage_PositionAndLengthBasic {
return &TrDosImage_PositionAndLengthBasic{
}
}
func (this TrDosImage_PositionAndLengthBasic) IO_() *kaitai.Stream {
return this._io
}
func (this *TrDosImage_PositionAndLengthBasic) Read(io *kaitai.Stream, parent *TrDosImage_File, root *TrDosImage) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp19, err := this._io.ReadU2le()
if err != nil {
return err
}
this.ProgramAndDataLength = uint16(tmp19)
tmp20, err := this._io.ReadU2le()
if err != nil {
return err
}
this.ProgramLength = uint16(tmp20)
return err
}
type TrDosImage_PositionAndLengthCode struct {
StartAddress uint16
Length uint16
_io *kaitai.Stream
_root *TrDosImage
_parent *TrDosImage_File
}
func NewTrDosImage_PositionAndLengthCode() *TrDosImage_PositionAndLengthCode {
return &TrDosImage_PositionAndLengthCode{
}
}
func (this TrDosImage_PositionAndLengthCode) IO_() *kaitai.Stream {
return this._io
}
func (this *TrDosImage_PositionAndLengthCode) Read(io *kaitai.Stream, parent *TrDosImage_File, root *TrDosImage) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp21, err := this._io.ReadU2le()
if err != nil {
return err
}
this.StartAddress = uint16(tmp21)
tmp22, err := this._io.ReadU2le()
if err != nil {
return err
}
this.Length = uint16(tmp22)
return err
}
/**
* Default memory address to load this byte array into
*/
type TrDosImage_PositionAndLengthGeneric struct {
Reserved uint16
Length uint16
_io *kaitai.Stream
_root *TrDosImage
_parent *TrDosImage_File
}
func NewTrDosImage_PositionAndLengthGeneric() *TrDosImage_PositionAndLengthGeneric {
return &TrDosImage_PositionAndLengthGeneric{
}
}
func (this TrDosImage_PositionAndLengthGeneric) IO_() *kaitai.Stream {
return this._io
}
func (this *TrDosImage_PositionAndLengthGeneric) Read(io *kaitai.Stream, parent *TrDosImage_File, root *TrDosImage) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp23, err := this._io.ReadU2le()
if err != nil {
return err
}
this.Reserved = uint16(tmp23)
tmp24, err := this._io.ReadU2le()
if err != nil {
return err
}
this.Length = uint16(tmp24)
return err
}
type TrDosImage_PositionAndLengthPrint struct {
ExtentNo uint8
Reserved uint8
Length uint16
_io *kaitai.Stream
_root *TrDosImage
_parent *TrDosImage_File
}
func NewTrDosImage_PositionAndLengthPrint() *TrDosImage_PositionAndLengthPrint {
return &TrDosImage_PositionAndLengthPrint{
}
}
func (this TrDosImage_PositionAndLengthPrint) IO_() *kaitai.Stream {
return this._io
}
func (this *TrDosImage_PositionAndLengthPrint) Read(io *kaitai.Stream, parent *TrDosImage_File, root *TrDosImage) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp25, err := this._io.ReadU1()
if err != nil {
return err
}
this.ExtentNo = tmp25
tmp26, err := this._io.ReadU1()
if err != nil {
return err
}
this.Reserved = tmp26
tmp27, err := this._io.ReadU2le()
if err != nil {
return err
}
this.Length = uint16(tmp27)
return err
}
type TrDosImage_VolumeInfo struct {
CatalogEnd []byte
Unused []byte
FirstFreeSectorSector uint8
FirstFreeSectorTrack uint8
DiskType TrDosImage_DiskType
NumFiles uint8
NumFreeSectors uint16
TrDosId []byte
Unused2 []byte
Password []byte
Unused3 []byte
NumDeletedFiles uint8
Label []byte
Unused4 []byte
_io *kaitai.Stream
_root *TrDosImage
_parent *TrDosImage
_f_numSides bool
numSides int8
_f_numTracks bool
numTracks int8
}
func NewTrDosImage_VolumeInfo() *TrDosImage_VolumeInfo {
return &TrDosImage_VolumeInfo{
}
}
func (this TrDosImage_VolumeInfo) IO_() *kaitai.Stream {
return this._io
}
func (this *TrDosImage_VolumeInfo) Read(io *kaitai.Stream, parent *TrDosImage, root *TrDosImage) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp28, err := this._io.ReadBytes(int(1))
if err != nil {
return err
}
tmp28 = tmp28
this.CatalogEnd = tmp28
if !(bytes.Equal(this.CatalogEnd, []uint8{0})) {
return kaitai.NewValidationNotEqualError([]uint8{0}, this.CatalogEnd, this._io, "/types/volume_info/seq/0")
}
tmp29, err := this._io.ReadBytes(int(224))
if err != nil {
return err
}
tmp29 = tmp29
this.Unused = tmp29
tmp30, err := this._io.ReadU1()
if err != nil {
return err
}
this.FirstFreeSectorSector = tmp30
tmp31, err := this._io.ReadU1()
if err != nil {
return err
}
this.FirstFreeSectorTrack = tmp31
tmp32, err := this._io.ReadU1()
if err != nil {
return err
}
this.DiskType = TrDosImage_DiskType(tmp32)
tmp33, err := this._io.ReadU1()
if err != nil {
return err
}
this.NumFiles = tmp33
tmp34, err := this._io.ReadU2le()
if err != nil {
return err
}
this.NumFreeSectors = uint16(tmp34)
tmp35, err := this._io.ReadBytes(int(1))
if err != nil {
return err
}
tmp35 = tmp35
this.TrDosId = tmp35
if !(bytes.Equal(this.TrDosId, []uint8{16})) {
return kaitai.NewValidationNotEqualError([]uint8{16}, this.TrDosId, this._io, "/types/volume_info/seq/7")
}
tmp36, err := this._io.ReadBytes(int(2))
if err != nil {
return err
}
tmp36 = tmp36
this.Unused2 = tmp36
tmp37, err := this._io.ReadBytes(int(9))
if err != nil {
return err
}
tmp37 = tmp37
this.Password = tmp37
tmp38, err := this._io.ReadBytes(int(1))
if err != nil {
return err
}
tmp38 = tmp38
this.Unused3 = tmp38
tmp39, err := this._io.ReadU1()
if err != nil {
return err
}
this.NumDeletedFiles = tmp39
tmp40, err := this._io.ReadBytes(int(8))
if err != nil {
return err
}
tmp40 = tmp40
this.Label = tmp40
tmp41, err := this._io.ReadBytes(int(3))
if err != nil {
return err
}
tmp41 = tmp41
this.Unused4 = tmp41
return err
}
func (this *TrDosImage_VolumeInfo) NumSides() (v int8, err error) {
if (this._f_numSides) {
return this.numSides, nil
}
this._f_numSides = true
var tmp42 int8;
if (this.DiskType & 8 != 0) {
tmp42 = 1
} else {
tmp42 = 2
}
this.numSides = int8(tmp42)
return this.numSides, nil
}
func (this *TrDosImage_VolumeInfo) NumTracks() (v int8, err error) {
if (this._f_numTracks) {
return this.numTracks, nil
}
this._f_numTracks = true
var tmp43 int8;
if (this.DiskType & 1 != 0) {
tmp43 = 40
} else {
tmp43 = 80
}
this.numTracks = int8(tmp43)
return this.numTracks, nil
}
/**
* track number is logical, for double-sided disks it's
* (physical_track << 1) | side, the same way that tracks are stored
* sequentially in .trd file
*/
/**
* Number of non-deleted files. Directory can have more than
* number_of_files entries due to deleted files
*/