summaryrefslogtreecommitdiff
path: root/extractHintergrund.py
blob: 5369e3b5e3be9f2745201eb147126316d8c6ae7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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)