
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.
Picture the file as a row:
[ disk-info header, 256 ][ track 0, 5376 ][ track 1, 5376 ][ track 2 ... ]
0 256 5632 11008

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.
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 track | Bytes | What it holds |
|---|---|---|
0x00 | 12 | Magic: Track-Info then 0D 0A |
0x10 | 1 | Track number - 0 to 39 |
0x11 | 1 | Side - 0 |
0x14 | 1 | Sector size code, N: 2, which means 512-byte sectors |
0x15 | 1 | Number of sectors on this track: 10 |
0x16 | 1 | Gap 3 - spacing on the real floppy between sectors. Nothing in this course needs it, and images differ |
0x17 | 1 | Filler - the byte the track was formatted with: E5 |
0x18 | 8 per sector | The sector list (S10) |
0x100 | 512 per sector | The 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.
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 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.
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).)track_offsets, change offset = 256 to offset = 0. Which column shows you straight away that the walk is wrong?header[0x10] in hex (:02X). At which track do the two columns first look different?| Symptom | Cause |
|---|---|
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 |
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.
S10, Sectors - the sector list, and why a disc's sectors are not always stored in the order of their numbers.
This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.