Skip to content

Instantly share code, notes, and snippets.

@bbbradsmith
Last active June 18, 2021 03:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bbbradsmith/a0fb690f366c902e96f25a8351a5c46c to your computer and use it in GitHub Desktop.
Save bbbradsmith/a0fb690f366c902e96f25a8351a5c46c to your computer and use it in GitHub Desktop.
Pasti STX disk image python info dumper (Atari ST)
#!/usr/bin/env python3
#
# Pasti .STX Atari ST disk image info dumper
# Prints out a human-readable version of all data found in the file
#
# Usage:
# stx_dump("a.stx")
#
# Based on Pasti reverse engineering:
# http://info-coach.fr/atari/documents/_mydoc/Pasti-documentation.pdf
#
# See also: stx_merge.py
# https://gist.github.com/bbbradsmith/e74766f1fb36d0f62262f4187fbe7de4
#
# Brad Smith, 2019
# http://rainwarrior.ca
#
import struct
def dump(d,offset=0,columns=32):
s = ""
for i in range(len(d)):
if (i % columns) == 0:
s += "$%06X:" % (offset+i)
s += " %02X" % d[i]
if (i % columns) == (columns - 1) and i != (len(d)-1):
s += "\n"
return s
def stx_dump(filename, dump_data=True, dump_sector_descs=True):
print("Pasti: " + filename)
d = open(filename,"rb").read()
print("\nFile Descriptor")
file_desc = struct.unpack("<BBBBHHHBBL",d[0:16])
print("id: $%02X $%02X $%02X $%02X \"%c%c%c\"+$%02X" % (file_desc[0:4]+file_desc[0:4]))
print("version: %d" % file_desc[4])
print("tool: $%04X" % file_desc[5])
print("reserved: $%04X" % file_desc[6])
print("tracks: %d" % file_desc[7])
print("revision: %d" % file_desc[8])
print("reserved: $%08X" % file_desc[9])
p = 16
for t in range(0,file_desc[7]):
print("\nTrack Record %d at $%08X" % (t,p))
track_desc = struct.unpack("<LLHHHBB",d[p:p+16])
print("record size: %d" % track_desc[0])
print("fuzzy count: %d" % track_desc[1])
print("sectors: %d" % track_desc[2])
print("flags: $%04X" % track_desc[3])
print("length: %d" % track_desc[4])
print("side: %d, number: %d" % (track_desc[5]>>7,track_desc[5]&0x7F))
print("type: $%02X" % track_desc[6])
pnext = p + track_desc[0]
p += 16
sector_descs = []
timing_needed = False
if (track_desc[3] & 1):
for s in range(0,track_desc[2]):
if dump_sector_descs:
print("\nSector Descriptor %d:%d at $%08X" % (t,s,p))
sector_desc = struct.unpack("<LHHBBBBHBB",d[p:p+16])
sector_descs.append(sector_desc)
if dump_sector_descs:
print("offset: $%08X" % sector_desc[0])
print("bit position: %d" % sector_desc[1])
print("read time: %d" % sector_desc[2])
print("address track: %d" % sector_desc[3])
print("address head: %d" % sector_desc[4])
print("address number: %d" % sector_desc[5])
print("address size: %d (%d)" % (sector_desc[6],128<<sector_desc[6]))
print("CRC: $%04X" % sector_desc[7])
print("flags: $%02X" % sector_desc[8])
print("reserved: $%02X" % sector_desc[9])
if sector_desc[8] & 1:
timing_needed = True
p+=16
if (track_desc[1] > 0):
print ("\nFuzzy Mask (%d bytes)" % track_desc[1])
if dump_data:
print(dump(d[p:p+track_desc[1]],offset=p))
p += track_desc[1]
pimage = p
pimage_size = 0
if (track_desc[3] & 0x40): # track image
print ("\nTrack Image %d" % t)
if track_desc[3] & 0x80:
first_sync = struct.unpack("<H",d[p:p+2])[0]
print("first sync: $%04X" % first_sync)
p += 2
image_size = struct.unpack("<H",d[p:p+2])[0]
print("image size: %d" % image_size)
p += 2
pimage_size = image_size
if dump_data:
print(dump(d[p:p+image_size],offset=p))
p += image_size
if (track_desc[3] & 1): # sector descriptors = maybe sector images
sector_images = []
if not (track_desc[3] & 0x40): # no track image = all sectors
sector_images = range(0,track_desc[2])
else:
for s in range(0,track_desc[2]):
if sector_descs[s][0] >= image_size: # offset outside track image
sector_images.append(s)
for s in sector_images:
print ("\nSector Image %d" % s)
image_offset = sector_descs[s][0]
image_size = 128 << sector_descs[s][6]
print ("offset: $%08X" % image_offset)
print ("size: %d" % image_size)
if dump_data:
print(dump(d[pimage+image_offset:pimage+image_offset+image_size],pimage+image_offset))
p = pimage+image_offset+image_size # seek to end of this block?
if file_desc[8] == 2 and timing_needed and p < pnext: # timing record
print ("\nTiming Data")
timing_desc = struct.unpack("<HH",d[p:p+4])
print ("flags: %d" % timing_desc[0])
print ("size: %d (%d)" % (timing_desc[1],(timing_desc[1]-4)/2))
for i in range(4,timing_desc[1],2):
block_time = struct.unpack("<H",d[p+i:p+i+2])[0]
print ("%3d: %d" % ((i-4)/2,block_time))
p += timing_desc[1]
if p != pnext:
# track structures seem to be padded to even bytes ($FF for fill)
print ("\nExtra Data? %d bytes" % (pnext-p))
if dump_data:
print(dump(d[p:pnext],p))
p = pnext
if p != len(d):
print("\nExtra Suffix Data? %d bytes" % (len(d)-p))
if dump_data:
print(dump(d[p:pnext],p))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment