summaryrefslogtreecommitdiff
path: root/extractHintergrund.py
diff options
context:
space:
mode:
Diffstat (limited to 'extractHintergrund.py')
-rwxr-xr-xextractHintergrund.py225
1 files changed, 225 insertions, 0 deletions
diff --git a/extractHintergrund.py b/extractHintergrund.py
new file mode 100755
index 0000000..5369e3b
--- /dev/null
+++ b/extractHintergrund.py
@@ -0,0 +1,225 @@
+#!/usr/bin/env python3
+
+"""
+Extract EUMEL Hintergrund floppy disk image. Known to work only with version
+1.8 images.
+"""
+
+import os
+from enum import IntEnum, unique
+from operator import attrgetter
+
+from eumel import pagesize
+
+from construct import Struct, Const, Padding, PaddedString, Int8ul, Int16ul, \
+ Int24ul, Int32ul, Flag, Computed, this, Array, BitStruct, Bitwise, \
+ BitsInteger, Embedded, Nibble, Sequence, Enum
+
+hgIdent = Struct(
+ "signature" / Const(b"EUMEL-"),
+ "version" / PaddedString(6, "ascii"),
+ Padding(1),
+ "isShutup" / Int8ul * "true if value is 0", # XXX
+ "bootCount" / Int16ul,
+ Padding(0x24) * "undocumented",
+ "_hgblocks2" / Int16ul,
+ Padding(0x50) * "unknown/undocumented",
+ "_hgblocks" / Int16ul,
+ "_plusident" / Int16ul,
+ "isPlus" / Computed(this._hgblocks == 1 and this._plusident == 0),
+ "blocks" / Computed(this._hgblocks if this.isPlus else this._hgblocks2), # XXX: this is not correct
+ ) * "First block of Hintergrund"
+
+blockref = Struct(
+ "value" / Int24ul,
+ "control" / Int8ul,
+ )
+
+anchor = Struct(
+ Const(b"\xff"*4),
+ "akttab" / blockref,
+ "clorX" / blockref,
+ Const(b"\xff"*4*3),
+ "taskRoot" / blockref,
+ Const(b"\xff"*4),
+ ) * "System anchor block"
+
+blockTable = Array(pagesize//blockref.sizeof(), blockref)
+
+# XXX: skip const
+segmentTable = Sequence (Const (2*blockref.sizeof ()*b'\xff'), Array (14, blockref))
+
+drinfo = Struct(
+ "count" / blockref * "Number of blocks/pages allocated",
+ "blocks" / Array(3, blockref) * "Direct block references for page 1, 2 and 3",
+ "blockTables" / Array (2, blockref) * "Block references to block tables",
+ "segmentTables" / Array (2, blockref) * "Block references to segment tables, which refer to block tables",
+ ) * "Dataspace descriptor"
+
+# see src/devel/misc/unknown/src/XSTATUS.ELA
+# EUMEL’s pcb function returns the 16 bit word at position (0x1e+2*<id>)%0x40
+# i.e. module is pcb(23) → at offset 0x0c
+pcb = Struct(
+ "wstate" / Int32ul,
+ "millis" / Int8ul,
+ "unknown" / BitStruct (
+ "unused" / Flag, # bit 7
+ Padding(6),
+ "comflag" / Flag, # bit 0
+ ),
+ "status" / Int8ul,
+ "statusflags" / Int8ul * "unknown status flags",
+ "pricnt" / Int8ul,
+ "_icount" / Int16ul,
+ "flags" / BitStruct( # XXX: embedding BitStruct is not possible
+ "iserror" / Flag, # bit 7
+ "disablestop" / Flag, # bit 6
+ Padding(1),
+ "arith" / Flag, # bit 4
+ Padding(2),
+ "_codesegment" / BitsInteger(2), # bits 0…1
+ ),
+ "icount" / Computed(this._icount | (this.flags._codesegment<<16)), # XXX: byte-swapping 18 bit int is not possible? is codesegment low/high bits of icount?
+ "module" / Int16ul,
+ "pbase" / Int8ul,
+ "c8k" / Int8ul,
+ "lbase" / Int16ul,
+ "ltop" / Int16ul,
+ "lsTop" / Int16ul,
+ "heap" / BitStruct( # XXX: is this just a 16 bit pointer?
+ "top" / BitsInteger(12), # XXX: incorrect byte order
+ "segment" / Nibble, # bit 0…3
+ ),
+ Padding(4),
+ "priclk" / Int8ul,
+ "priv" / Int8ul,
+ Padding(2),
+ "linenr" / Int16ul, # ↓ See library/entwurf-systemdokumentation-1982.djvu section 2.4.13 (page 29)
+ "errorline" / Int16ul,
+ "errorcode" / Int16ul,
+ "channel" / Int16ul,
+ Padding(2), # XXX: sure about this padding?
+ "prio" / Int16ul,
+ "msgcode" / Int16ul,
+ "msgds" / Int16ul,
+ "taskid" / Int16ul,
+ "version" / Int16ul,
+ "fromid" / Int32ul,
+ Padding(8) * "unknown",
+ Padding(64) * "usually ff",
+ ) * "Leitblock"
+assert pcb.sizeof() == 4*drinfo.sizeof(), (pcb.sizeof(), drinfo.sizeof())
+
+class CpuType (IntEnum):
+ Z80 = 1
+ INTEL8088 = 3
+ M68K = 1024
+
+urladerlink = Struct (
+ "signature" / Const(b'EUMEL' + b' '*11),
+ "blocks" / Int16ul,
+ "hgver" / Int16ul,
+ "cputype" / Enum (Int16ul, CpuType),
+ "urver" / Int16ul,
+ Padding (2),
+ "shdvermin" / Int16ul,
+ "shdvermax" / Int16ul,
+ ) * "Urlader Linkleiste"
+
+def copyblock (block, infd, outfd):
+ if block == 0xffffff:
+ written = outfd.write (b'\xff'*pagesize)
+ assert written == pagesize
+ else:
+ infd.seek (block*pagesize, os.SEEK_SET)
+ buf = infd.read (pagesize)
+ assert len (buf) == pagesize
+ written = outfd.write (buf)
+ assert written == pagesize
+
+def copyBlockTable (block, infd, outfd, skip=0):
+ if block != 0xffffff:
+ fd.seek (block*pagesize, os.SEEK_SET)
+ for i, refl2 in enumerate (blockTable.parse_stream (infd)):
+ if i >= skip:
+ copyblock (refl2.value, fd, outfd)
+ else:
+ entries = (blockTable.sizeof()//blockref.sizeof())-skip
+ outfd.write (b'\xff'*(pagesize*entries))
+
+if __name__ == '__main__':
+ import sys
+ from struct import Struct, unpack
+
+ with open (sys.argv[1], 'rb') as fd:
+ # ident
+ print (hgIdent.parse_stream (fd))
+ fd.seek (0x1400, os.SEEK_SET)
+ print (urladerlink.parse_stream (fd))
+
+ fd.seek (pagesize)
+ a = anchor.parse_stream (fd)
+
+ # task root (level 1)
+ fd.seek (a.taskRoot.value*pagesize)
+ taskRoot = blockTable.parse_stream (fd)
+
+ # task dataspaces(?) (level 2)
+ for taskid, taskref in enumerate (taskRoot):
+ if taskref.value == 0xffffff:
+ continue
+ print (f'task {taskid} is at {taskref.value} 0x{taskref.value*pagesize:x}')
+
+ fd.seek (taskref.value*pagesize)
+ dataspaces = blockTable.parse_stream (fd)
+
+ for dsidhigh, dsref in enumerate (dataspaces):
+ if dsref.value == 0xffffff:
+ continue
+ print (f'\ttaskid {taskid} dsid {dsidhigh<<4} is at {dsref.value} 0x{dsref.value*pagesize:x}')
+
+ # pcb and drinfo (level 3)
+ fd.seek (dsref.value*pagesize)
+ drinfoStart = 0
+ if dsidhigh == 0:
+ p = pcb.parse_stream (fd)
+ print (f'\t+pcb taskid {p.taskid} version {p.version} icount {p.icount:x} arith {p.flags.arith} disablestop {p.flags.disablestop} iserror {p.flags.iserror} pbase {p.pbase:x} module {p.module}')
+ drinfoStart = 4
+ print (f'\t\tdrinfo starting at {fd.tell():x}')
+ for dsidlow in range (drinfoStart, 16):
+ dsid = dsidlow | dsidhigh << 4
+ d = drinfo.parse_stream (fd)
+ if d.count.value != 0xffffff and d.count.value != 0:
+ # pbt (page block table) 1/2 contain block refs for pages 0…127 and 128…256
+ # pst (page segment table) 1/2 contain block refs to page block tables for pages > 256
+ print (f'\t\tdrinfo {dsid} #{d.count.value} @ {[x.value for x in d.blocks]}, ind {[x.value for x in d.blockTables]}, ind2 {[x.value for x in d.segmentTables]}')
+
+ pos = fd.tell ()
+ with open (f'{taskid:04d}_{dsid:04d}.ds', 'wb') as outfd:
+ os.ftruncate (outfd.fileno(), 0)
+
+ # get the first three pages
+ for ref in d.blocks:
+ copyblock (ref.value, fd, outfd)
+
+ # indirect block refs (level 4a)
+ assert len (d.blockTables) == 2
+ # first four entries of first table are empty and must not be written!
+ copyBlockTable (d.blockTables[0].value, fd, outfd, 3)
+ copyBlockTable (d.blockTables[1].value, fd, outfd)
+
+ # segment tables (level 4b)
+ for segref in d.segmentTables:
+ if segref.value != 0xffffff:
+ fd.seek (segref.value*pagesize, os.SEEK_SET)
+ segtbl = segmentTable.parse_stream (fd)
+ for ref in segtbl[1]:
+ copyBlockTable (ref.value, fd, outfd)
+ else:
+ outfd.write((14*128*pagesize)*b'\xff')
+
+ # 2*128 pages through block table, 2 segment tables with 14 refs to block tables each
+ expectedSize = (2*128+2*14*128)*pagesize
+ assert outfd.tell() == expectedSize, (outfd.tell(), expectedSize)
+ fd.seek (pos, os.SEEK_SET)
+