Inside the Einstein: The Disc, a TatungBytes course: XtalDOS listing the first practice disc image, on a rainbow sunburst
← Back to Modules
module
9

Tracks

Introduction

The header told you how long each track's stretch of the file is. That is enough to find every track: the first starts straight after the 256-byte header, and each one after that starts where the last one ended. This section walks the whole disc that way, and opens the header that each track starts with.

Finding Every Track

Picture the file as a row:

[ disk-info header, 256 ][ track 0, 5376 ][ track 1, 5376 ][ track 2 ... ]
  0                       256              5632             11008

The whole image: the header, then forty track blocks; one block opened up

Track 0 starts at byte 256. Track 1 starts at 256 + track 0's size. Track 2 starts at that, plus track 1's size, and so on. Keep a running total, start it at 256, and add each track's size as you go:

def track_offsets(disc):
    offsets = []
    offset = 256
    for size in track_sizes(disc):
        offsets.append(offset)
        offset = offset + size
    return offsets

track_offsets gives back a list of forty numbers: where each track begins in the file. Because it uses the sizes from the header, it is right even for an image whose tracks are not all the same size.

The Track-Info Header

Every track's stretch of the file starts with its own 256-byte header, before its sectors. Its job is to describe that one track: which track it is, and how its sectors are laid out.

Offset in the trackBytesWhat it holds
0x0012Magic: Track-Info then 0D 0A
0x101Track number - 0 to 39
0x111Side - 0
0x141Sector size code, N: 2, which means 512-byte sectors
0x151Number of sectors on this track: 10
0x161Gap 3 - spacing on the real floppy between sectors. Nothing in this course needs it, and images differ
0x171Filler - the byte the track was formatted with: E5
0x188 per sectorThe sector list (S10)
0x100512 per sectorThe sectors' data

Track 2 of FIRST.DSK: the first 32 bytes of its header, field by field, then its ten sectors' data

The magic and the track number are your checks that the walk has landed where it should: if track_offsets is right, every track begins with Track-Info, and its number is its place in the list.

Numbering A List

To print each track with its number, the loop needs both the number and the item. range(len(offsets)) gives the numbers 0 to 39, one for each item in the list, and offsets[i] picks out item i:

for i in range(len(offsets)):
    offset = offsets[i]

The tracks function then slices out the track's 256-byte header and prints its fields in columns. {offset:9} in an f-string means "at least 9 characters wide, lined up on the right", which keeps a table of numbers tidy. A long print can be split over two lines by giving it two f-strings side by side inside its brackets; Python joins them.

The code

The whole of edisc.py so far. New: track_offsets, tracks, and the tracks command at the end.

import sys


def show_byte(b):
    if 32 <= b < 127:
        return chr(b)
    return "."


def dump(data, start, length):
    for offset in range(start, start + length, 16):
        row = data[offset:offset + 16]
        hex_part = ""
        text_part = ""
        for b in row:
            hex_part = hex_part + f"{b:02X} "
            text_part = text_part + show_byte(b)
        print(f"{offset:06X}  {hex_part:<48} {text_part}")


def read_image(filename):
    with open(filename, "rb") as f:
        return f.read()


def image_format(disc):
    if disc[0:16] == b"EXTENDED CPC DSK":
        return "extended"
    if disc[0:8] == b"MV - CPC":
        return "standard"
    sys.exit("This is not a disc image: it does not start EXTENDED CPC DSK or MV - CPC")


def track_sizes(disc):
    form = image_format(disc)
    count = disc[0x30] * disc[0x31]
    sizes = []
    if form == "extended":
        for i in range(count):
            sizes.append(disc[0x34 + i] * 256)
    else:
        size = int.from_bytes(disc[0x32:0x34], "little")
        for i in range(count):
            sizes.append(size)
    return sizes


def track_offsets(disc):
    offsets = []
    offset = 256
    for size in track_sizes(disc):
        offsets.append(offset)
        offset = offset + size
    return offsets


def info(filename, disc):
    sizes = track_sizes(disc)
    expected = 256 + sum(sizes)
    print("Image:   ", filename)
    print("Format:  ", image_format(disc))
    print("Tracks:  ", disc[0x30])
    print("Sides:   ", disc[0x31])
    print("Tracks are", min(sizes), "to", max(sizes), "bytes")
    print("Expected size:", expected, "bytes")
    print("Actual size:  ", len(disc), "bytes")
    if expected == len(disc):
        print("The sizes add up.")
    else:
        print("The sizes DO NOT add up.")


def tracks(disc):
    print("Track  Starts at  Magic  Track Side Sectors  N  Gap3 Filler")
    offsets = track_offsets(disc)
    for i in range(len(offsets)):
        offset = offsets[i]
        header = disc[offset:offset + 256]
        if header[0:12] == b"Track-Info\r\n":
            magic = "yes"
        else:
            magic = "NO"
        print(f"{i:5}  {offset:9}  {magic:5}  {header[0x10]:5} {header[0x11]:4}"
              f" {header[0x15]:7} {header[0x14]:2}  {header[0x16]:02X}   {header[0x17]:02X}")


command = sys.argv[1]
filename = sys.argv[2]
disc = read_image(filename)

if command == "info":
    info(filename, disc)
elif command == "header":
    dump(disc, 0, 256)
elif command == "tracks":
    tracks(disc)
else:
    sys.exit("Unknown command: " + command)

Run it

python3 edisc.py tracks FIRST.DSK

What you should see (the first five tracks and the last two; the thirty-three between follow the same pattern)

Track  Starts at  Magic  Track Side Sectors  N  Gap3 Filler
    0        256  yes        0    0      10  2  2A   E5
    1       5632  yes        1    0      10  2  2A   E5
    2      11008  yes        2    0      10  2  2A   E5
    3      16384  yes        3    0      10  2  2A   E5
    4      21760  yes        4    0      10  2  2A   E5
...
   38     204544  yes       38    0      10  2  2A   E5
   39     209920  yes       39    0      10  2  2A   E5

Every track begins with Track-Info, every one says it is the track you expected, each has ten sectors of size code 2, and each starts 5,376 bytes after the one before. The last one starts at 209,920; add its 5,376 and you reach 215,296, the end of the file. The walk fits the file exactly.

Change one thing

  • Run tracks on BADSIZE.DSK, the image from S8 with one size wrong. The first six tracks are fine. What happens at track 6, and what are the 229s you see there? (Hint: hex(229).)
  • In track_offsets, change offset = 256 to offset = 0. Which column shows you straight away that the walk is wrong?
  • Change the tool to print header[0x10] in hex (:02X). At which track do the two columns first look different?

When it goes wrong

SymptomCause
From one track on, Magic says NO and the numbers are 229 (E5)The walk has gone wrong at that track: a size in the header does not match the file, as in BADSIZE.DSK. The tool is now reading sector data as if it were a header

Summary

An image is its 256-byte disk-info header followed by its tracks, back to back, each as long as the header says. track_offsets finds every one by keeping a running total. Each track starts with a 256-byte track-info header - Track-Info, the track's number and side, its sector size code (2, for 512 bytes), its sector count, gap 3 and filler - followed by a list of its sectors at 0x18, and its sector data from 0x100.

Next

S10, Sectors - the sector list, and why a disc's sectors are not always stored in the order of their numbers.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.