
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.
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.
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 entry | Bytes | What it holds |
|---|---|---|
| +0 | 1 | C - the track number |
| +1 | 1 | H - the side |
| +2 | 1 | R - the sector's ID |
| +3 | 1 | N - the size code, 2 |
| +4 | 1 | ST1 - a status byte; 0 on every disc in this course |
| +5 | 1 | ST2 - another; 0 |
| +6 | 2 | Length 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.

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 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.
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?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?| Symptom | Cause |
|---|---|
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 range | A track number of 40 or more - the tracks are 0 to 39 |
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.
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.
This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.