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

Sectors

Introduction

You can find every track. Inside each one are ten sectors, and somewhere among them is the sector you want - track 2's sector 0, say. The obvious guess is that sector 0 comes first, then sector 1, and so on. On many discs that guess is wrong, and a tool built on it reads the wrong data without any warning at all. This section shows how a track says where its sectors are.

Sector Numbers

Each sector on a track has a number of its own, its ID. On an Einstein disc the IDs run from 0 to 9. When the Einstein wants a sector, it asks for it by track and ID: "track 2, sector ID 4".

Round the track, the sectors can be in any order. Whatever formatted the disc chose one, and discs differ: some have their sectors in the order 0 to 9, some in other orders. Real Einstein discs turn up with all three of these:

0 1 2 3 4 5 6 7 8 9
5 6 7 8 9 0 1 2 3 4
0 5 1 6 2 7 3 8 4 9

A disc image stores each track's sectors in some order too, and that need not be 0 to 9. So the image has to say which is which.

The Sector List

That is what the sector list is for. It starts at 0x18 in each track-info header, and has one 8-byte entry per sector, in the order the sectors' data follows:

Offset in the entryBytesWhat it holds
+01C - the track number
+11H - the side
+21R - the sector's ID
+31N - the size code, 2
+41ST1 - a status byte; 0 on every disc in this course
+51ST2 - another; 0
+62Length of the sector's data: 512, stored little-endian as 00 02

The rule that matters:

The data for the sector at position 0 in the list is at 0x100 in the track; position 1's at 0x100 + 512; position p's at 0x100 + p x 512. To find the sector with ID 4, you look through the list for the entry whose R is 4, note its position, and use that. Never use the ID itself as the position.

ORDER.DSK track 2: the order of the sectors round the track, the sector list, and where each sector's data is

Typed Numbers

The sectors command takes a track number: python3 edisc.py sectors FIRST.DSK 2. Everything on the command line arrives as text, so sys.argv[3] is the text "2", not the number 2. int("2") turns text into the whole number it spells.

The code

The whole of edisc.py so far. New: sector_ids, sectors, and the sectors command.

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 sector_ids(disc, track):
    offset = track_offsets(disc)[track]
    count = disc[offset + 0x15]
    ids = []
    for i in range(count):
        entry = offset + 0x18 + i * 8
        ids.append(disc[entry + 2])
    return ids


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}")


def sectors(disc, track):
    offset = track_offsets(disc)[track]
    count = disc[offset + 0x15]
    print("Track", track, "has", count, "sectors, stored in this order:")
    print("Position  C  H  ID  N  ST1 ST2  Length  Data starts at")
    for i in range(count):
        entry = disc[offset + 0x18 + i * 8:offset + 0x18 + i * 8 + 8]
        length = int.from_bytes(entry[6:8], "little")
        data_at = offset + 0x100 + i * 512
        print(f"{i:8}  {entry[0]:2} {entry[1]:2} {entry[2]:3} {entry[3]:2}"
              f"  {entry[4]:02X}  {entry[5]:02X}  {length:6}  {data_at}")


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)
elif command == "sectors":
    sectors(disc, int(sys.argv[3]))
else:
    sys.exit("Unknown command: " + command)

sector_ids is not used by a command yet - it gives back the IDs of a track's sectors in list order, and S11 needs exactly that.

Run it

python3 edisc.py sectors FIRST.DSK 2
python3 edisc.py sectors ORDER.DSK 2

ORDER.DSK is in the course download.

What you should see

Track 2 has 10 sectors, stored in this order:
Position  C  H  ID  N  ST1 ST2  Length  Data starts at
       0   2  0   0  2  00  00     512  11264
       1   2  0   1  2  00  00     512  11776
       2   2  0   2  2  00  00     512  12288
       3   2  0   3  2  00  00     512  12800
       4   2  0   4  2  00  00     512  13312
       5   2  0   5  2  00  00     512  13824
       6   2  0   6  2  00  00     512  14336
       7   2  0   7  2  00  00     512  14848
       8   2  0   8  2  00  00     512  15360
       9   2  0   9  2  00  00     512  15872
Track 2 has 10 sectors, stored in this order:
Position  C  H  ID  N  ST1 ST2  Length  Data starts at
       0   2  0   0  2  00  00     512  11264
       1   2  0   5  2  00  00     512  11776
       2   2  0   1  2  00  00     512  12288
       3   2  0   6  2  00  00     512  12800
       4   2  0   2  2  00  00     512  13312
       5   2  0   7  2  00  00     512  13824
       6   2  0   3  2  00  00     512  14336
       7   2  0   8  2  00  00     512  14848
       8   2  0   4  2  00  00     512  15360
       9   2  0   9  2  00  00     512  15872

On FIRST.DSK the IDs are in order, so position and ID agree and the guess "sector 4 is the fifth one" happens to work. On ORDER.DSK they do not: the sector with ID 4 is at position 8, its data at byte 15,360, and the data at position 4 belongs to sector 2. Put ORDER.DSK in MAME's drive 1 and DIR 1: lists its two files without trouble: the Einstein reads each sector by its ID, wherever it is.

Look back at S5: the text you found in FIRST.DSK was at byte 13,312. On FIRST.DSK that is track 2, position 4 - sector ID 4.

Change one thing

  • Run sectors ORDER.DSK 3. Is track 3 in the same order as track 2?
  • python3 edisc.py sectors FIRST.DSK two. What does Python say, and why?
  • In sectors, change entry[6:8] to entry[7:9]. What does the Length column show? Then try int.from_bytes(entry[6:8], "big") instead. Why 2?

When it goes wrong

SymptomCause
ValueError: invalid literal for int() with base 10: 'two'The track number was typed as a word; int only reads digits
IndexError: list index out of rangeA track number of 40 or more - the tracks are 0 to 39

Summary

Each track-info header carries a sector list at 0x18: eight bytes per sector - track, side, ID, size code, two status bytes, and a little-endian length of 512. The sectors' data follows at 0x100 in the order of that list, which may not be the order of their IDs: to find a sector, find its ID in the list and use its position. edisc.py sectors shows the list for any track.

Next

S11, Finding Any Sector - one function that turns a track and a sector ID into the 512 bytes the Einstein would read, and the tool's dump command.

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.