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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
#!/usr/bin/env python3
"""
Extract EUMEL Hintergrund floppy disk image. Known to work only with version
1.8 images.
"""
import os, logging
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"
assert pagesize//blockref.sizeof() == 128
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:
logging.debug (f'copying empty block')
written = outfd.write (b'\xff'*pagesize)
assert written == pagesize
else:
logging.debug (f'copying block {block}@{block*pagesize:x}h')
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:
logging.debug (f'copying block table {block}@{block*pagesize:x}h, skipping {skip}')
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:
logging.debug (f'copying empty block table')
entries = (blockTable.sizeof()//blockref.sizeof())-skip
outfd.write (b'\xff'*(pagesize*entries))
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='Extract EUMEL Hintergrund.')
parser.add_argument('-v', '--verbose', action='store_true', help='Verbose debugging output')
parser.add_argument('input', metavar='FILE', type=argparse.FileType('rb'), help='Input file')
args = parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
with args.input as fd:
# ident
logging.info (hgIdent.parse_stream (fd))
fd.seek (0x1400, os.SEEK_SET)
logging.info (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
logging.info (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
logging.info (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)
logging.info (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
logging.info (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
logging.info (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)
# the first page of a dataspace is used by the OS
# and not stored to the Hintergrund
outfd.seek (pagesize)
# 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, 4)
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)
|