lif80utils 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lif80utils/amigo.py ADDED
@@ -0,0 +1,271 @@
1
+ #
2
+ # This file is part of the lif80utils distribution (https://codeberg.org/Boeingflieger/lif80utils).
3
+ # Copyright (c) 2026 Christian Grosz.
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, version 3.
8
+ #
9
+ # This program is distributed in the hope that it will be useful, but
10
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+
18
+ import gpib
19
+ import time # sleep
20
+
21
+ class Amigo:
22
+ """This class implements the amigo command set."""
23
+
24
+ # https://linux-gpib.sourceforge.io/doc_html/reference-function-ibeot.html
25
+ # If send_eoi is non-zero, then the EOI line will be asserted with
26
+ # the last byte sent by calls to ibwrt() and related functions.
27
+ EOT = 1
28
+
29
+ # https://linux-gpib.sourceforge.io/doc_html/reference-function-ibeos.html
30
+ # The least significant 8 bits of eosmode specify the eos character. You may
31
+ # also bitwise-or one or more of the following bits to set the eos mode:
32
+ REOS = 0x400 # Enable termination of reads when eos character is received.
33
+ XEOS = 0x800 # Assert the EOI line whenever the eos character is sent during writes.
34
+ BIN = 0x1000 # Match eos character using all 8 bits (instead of only looking at the 7 least significant bits).
35
+
36
+ def __init__ ( self, board, pad):
37
+ self.ppmask = 0x80 >> pad
38
+
39
+ self.con_00 = gpib.dev( board, pad, 0x60 + 0x00, gpib.T1s, 0) # Receive Data
40
+ self.con_08 = gpib.dev( board, pad, 0x60 + 0x08, gpib.T10s, self.EOT) # Unbuffered
41
+ self.con_09 = gpib.dev( board, pad, 0x60 + 0x09, gpib.T10s, self.EOT) # Buffered Write
42
+ self.con_0A = gpib.dev( board, pad, 0x60 + 0x0A, gpib.T1s, self.EOT) # Buffered Read
43
+ self.con_0C = gpib.dev( board, pad, 0x60 + 0x0C, gpib.T10s, self.EOT) # REQU PHY, FORMAT
44
+ self.con_10 = gpib.dev( board, pad, 0x60 + 0x10, gpib.T1s, self.EOT) # DSJ (Device Specified Jump)
45
+ self.con_1E = gpib.dev( board, pad, 0x60 + 0x1E, gpib.T1s, self.EOT) # Write/Read Loopback
46
+
47
+ self.con_1F = gpib.dev( board, pad, 0x60 + 0x1F, gpib.T1s, self.EOT, 0) # Write/Read Loopback # 10011
48
+
49
+ def __del__(self):
50
+ self.end( )
51
+
52
+ gpib.close( self.con_00)
53
+ gpib.close( self.con_08)
54
+ gpib.close( self.con_09)
55
+ gpib.close( self.con_0A)
56
+ gpib.close( self.con_0C)
57
+ gpib.close( self.con_10)
58
+ gpib.close( self.con_1E)
59
+ gpib.close( self.con_1F)
60
+
61
+ def dsj ( self):
62
+ return gpib.read( self.con_10, 1)
63
+
64
+ # Request Status
65
+ def request_status ( self, unit = 0):
66
+ gpib.write \
67
+ ( self.con_08
68
+ , b'\x03'
69
+ + unit .to_bytes( 1, 'big')
70
+ )
71
+
72
+ dsj = self.dsj( )
73
+
74
+ if b'\x00' != dsj:
75
+ print( f"{dsj} REQUEST STATUS")
76
+ return
77
+
78
+ return gpib.read( self.con_08, 4)
79
+
80
+ # Read Self-Test
81
+ def read_self_test ( self):
82
+ return gpib.read( self.con_1F, 2)
83
+
84
+ # Write Loopback
85
+ def write_loopback ( self, data):
86
+ gpib.write( self.con_1E, data)
87
+
88
+ # Read Loopback
89
+ def read_loopback ( self):
90
+ return gpib.read( self.con_1E, 255)
91
+
92
+ # Seek
93
+ def seek ( self, unit = 0, cylinder = 0, head = 0, sector = 0):
94
+ # Seek can need more then 1s timeout.
95
+ gpib.write \
96
+ ( self.con_08
97
+ , b'\x02'
98
+ + unit .to_bytes( 1, 'big')
99
+ + cylinder.to_bytes( 2, 'big')
100
+ + head .to_bytes( 1, 'big')
101
+ + sector .to_bytes( 1, 'big')
102
+ )
103
+
104
+ dsj = self.dsj( )
105
+
106
+ if b'\x00' != dsj:
107
+ print( f"{dsj} SEEK")
108
+
109
+ return dsj
110
+
111
+ # Buffered Read
112
+ def buffered_read ( self, unit = 0):
113
+ gpib.write \
114
+ ( self.con_0A
115
+ , b'\x05'
116
+ + unit .to_bytes( 1, 'big')
117
+ )
118
+
119
+ dsj = self.dsj( )
120
+
121
+ if b'\x00' != dsj:
122
+ print( f"{dsj} READ")
123
+
124
+ return dsj
125
+
126
+ #
127
+ def receive ( self, size = 256):
128
+ res = gpib.read( self.con_00, size)
129
+ return res
130
+
131
+ # Request (Logical) Disc Address
132
+ def request_logical ( self):
133
+ gpib.write \
134
+ ( self.con_08
135
+ , b'\x14\x00'
136
+ )
137
+
138
+ dsj = self.dsj( )
139
+
140
+ if b'\x00' != dsj:
141
+ print( f"{dsj} REQU LOG")
142
+ return
143
+
144
+ return gpib.read( self.con_08, 4)
145
+
146
+ # Request (Physical) Disc Address
147
+ def request_physical ( self):
148
+ gpib.write \
149
+ ( self.con_0C
150
+ , b'\x14\x00'
151
+ )
152
+
153
+ dsj = self.dsj( )
154
+
155
+ if b'\x00' != dsj:
156
+ print( f"{dsj} REQU PHY")
157
+ return
158
+
159
+ return gpib.read( self.con_08, 4)
160
+
161
+ # End
162
+ def end ( self):
163
+ gpib.write \
164
+ ( self.con_08
165
+ , b'\x15\x00'
166
+ )
167
+
168
+ dsj = self.dsj( )
169
+
170
+ if b'\x00' != dsj:
171
+ print( f"{dsj} END")
172
+
173
+ return
174
+
175
+ # Buffered Write
176
+ def buffered_write ( self, unit = 0):
177
+ gpib.write \
178
+ ( self.con_09
179
+ , b'\x08'
180
+ + unit .to_bytes( 1, 'big')
181
+ )
182
+
183
+ dsj = self.dsj( )
184
+
185
+ if b'\x00' != dsj:
186
+ print( f"{dsj} WRITE")
187
+
188
+ return dsj
189
+
190
+ #
191
+ def send ( self, buf):
192
+ gpib.write \
193
+ ( self.con_00
194
+ , buf
195
+ )
196
+
197
+ dsj = self.dsj( )
198
+
199
+ if b'\x00' != dsj:
200
+ print( f"{dsj} send")
201
+
202
+ return dsj
203
+
204
+ # Format
205
+ def request_format ( self, unit, option, interleave, databyte):
206
+ gpib.write \
207
+ ( self.con_0C
208
+ , b'\x18'
209
+ + unit .to_bytes( 1, 'big')
210
+ + option .to_bytes( 1, 'big')
211
+ + interleave.to_bytes( 1, 'big')
212
+ + databyte.to_bytes( 1, 'big')
213
+ )
214
+
215
+ # Formatting needs around 18 sec on my 9121D and around 90 sec on my 9123D.
216
+ # (The manual states that the 9123D is doing verification as well)
217
+ time.sleep( 18)
218
+
219
+ # DSJ (QSTAT) will timeout regardless of the timeout settings.
220
+ # Added my own ibrpp call to python.
221
+ # TODO: Check if I made a mistake with timout or wait.
222
+
223
+
224
+ # Execution
225
+ # Reporting
226
+ while True:
227
+ # ppres = gpib.parallel_poll( 0) # board
228
+ ppres = gpib.parallel_poll( self.con_0C) # device
229
+ print( f"{ppres:08b} {ppres:02x} {self.ppmask:02x}")
230
+ if self.ppmask & ppres:
231
+ break
232
+ time.sleep( 1)
233
+
234
+ dsj = self.dsj( )
235
+
236
+ if b'\x00' != dsj:
237
+ print( f"{dsj} REQUEST FORMAT")
238
+
239
+ return
240
+
241
+
242
+ # Initiate Selftest
243
+ def initiate_selftest ( self):
244
+ gpib.write \
245
+ ( self.con_1F
246
+ , b'\x00' # Ignored by the 9121D/S
247
+ b'\x00' # 0000W000, 0 = no format test, 1 = format test
248
+ # b'\x08' # 0000W000, 0 = no format test, 1 = format test
249
+ )
250
+
251
+ time.sleep( 8)
252
+
253
+ # Execution
254
+ # Reporting
255
+ while True:
256
+ # ppres = gpib.parallel_poll( 0) # board
257
+ ppres = gpib.parallel_poll( self.con_1F) # device
258
+ print( f"{ppres:08b} {ppres:02x} {self.ppmask:02x}")
259
+ if self.ppmask & ppres:
260
+ break
261
+ time.sleep( 1)
262
+
263
+
264
+
265
+
266
+ dsj = self.dsj( )
267
+
268
+ if b'\x00' != dsj:
269
+ print( f"{dsj} INITIATE SELFTEST")
270
+
271
+ return
@@ -0,0 +1,160 @@
1
+ #
2
+ # This file is part of the lif80utils distribution (https://codeberg.org/Boeingflieger/lif80utils).
3
+ # Copyright (c) 2026 Christian Grosz.
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, version 3.
8
+ #
9
+ # This program is distributed in the hope that it will be useful, but
10
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+
18
+ from .amigo import Amigo
19
+ import os
20
+
21
+ class AmigoFile:
22
+ """This class is an [informal] interface providing needed "file" functions."""
23
+
24
+ # Actually this information should come from the volume header.
25
+ # But all is set to 0 there.
26
+ # tracks_per_surface
27
+ # surfaces
28
+ # sectors_per_track
29
+ SECTOR_SIZE = 256
30
+
31
+ def __init__ ( self, board, pad, unit, verbose = False):
32
+ self.verbose = verbose
33
+
34
+ self.amigo = Amigo( board, pad)
35
+ self.unit = unit
36
+
37
+ self.protocol = self.amigo
38
+
39
+ dsj = self.amigo.dsj( )
40
+
41
+ # if b'\x00' != dsj:
42
+ if self.verbose:
43
+ print( f"{dsj} HOLDOFF STATE")
44
+
45
+ # Request status
46
+ res = self.amigo.request_status( unit)
47
+
48
+ if self.verbose:
49
+ print( f"{res[0]:08b} {res[1]:08b} {res[2]:08b} {res[3]:08b}")
50
+
51
+ tttt = (res[2] & 0x1E) >> 1
52
+ r = (res[2] & 0x01) >> 0
53
+ w = (res[3] & 0x40) >> 6
54
+ ss = (res[3] & 0x03) >> 0
55
+
56
+ if self.verbose:
57
+ print( f" TTTT {tttt:04b}")
58
+ print( f" R {r}")
59
+ print( f" W {w}")
60
+ print( f" SS {ss:02b}")
61
+
62
+ if 0 != ss:
63
+ print( "Error 130 : DISC")
64
+ exit( -1)
65
+
66
+ # TTTT 0110
67
+ self.cylinders = 33
68
+ self.heads = 2
69
+ self.sectors = 16
70
+
71
+ # self.offset = self.SECTOR_SIZE
72
+ # self.buf = None
73
+ self.seek( 0)
74
+
75
+ def seek ( self, offset, whence = os.SEEK_SET):
76
+ # We only support SEEK_SET for now.
77
+
78
+ # Only allow sector aligned seek.
79
+ if 0 != offset % self.SECTOR_SIZE:
80
+ exit( -1)
81
+
82
+ # Invalidate buffer.
83
+ self.offset = self.SECTOR_SIZE
84
+ self.buf = None
85
+
86
+ # capacity = cylinders * heads * sectors * SECTOR_SIZE
87
+ # 270336 = 33 * 2 * 16 * 256
88
+
89
+ sector = offset // self.SECTOR_SIZE
90
+
91
+ cylinder = sector // self.sectors
92
+ cylinder = cylinder // self.heads
93
+
94
+ head = (sector % (self.sectors * self.heads)) // self.sectors
95
+ sector = sector % self.sectors
96
+
97
+ self.amigo.seek( self.unit, cylinder, head, sector)
98
+
99
+ return offset
100
+
101
+
102
+ def tell ( self):
103
+ offset = 0
104
+
105
+ log = self.amigo.request_logical( )
106
+
107
+ cylinder = int.from_bytes( log[0:2], byteorder='big')
108
+ head = log[2]
109
+ sector = log[3]
110
+
111
+ offset += cylinder * self.sectors * self.heads
112
+ offset += self.sectors * head
113
+ offset += sector
114
+
115
+ offset *= self.SECTOR_SIZE
116
+
117
+ if self.verbose:
118
+ print( f"LOG C {cylinder:02} H {head} S {sector:02} {offset:06x}")
119
+
120
+ return offset
121
+
122
+
123
+ def read ( self, size = 256):
124
+ res = b''
125
+
126
+ while size:
127
+ # Remaining bytes in buffer.
128
+ avail = self.SECTOR_SIZE - self.offset
129
+
130
+ # If there are none, read the next sector.
131
+ if 0 == avail:
132
+ dsj = self.amigo.buffered_read( self.unit)
133
+
134
+ if b'\x00' == dsj:
135
+ self.buf = self.amigo.receive( self.SECTOR_SIZE)
136
+ self.offset = 0
137
+ avail = self.SECTOR_SIZE # - self.offset
138
+ else:
139
+ return res
140
+
141
+ n = size if avail > size else avail
142
+
143
+ res = res + self.buf[self.offset:self.offset+n]
144
+ size -= n
145
+ self.offset += n
146
+
147
+ return res
148
+
149
+ def write ( self, buf):
150
+ size = len( buf)
151
+
152
+ for n in range( 0, size, self.SECTOR_SIZE):
153
+ dsj = self.amigo.buffered_write( self.unit)
154
+
155
+ if b'\x00' == dsj:
156
+ dsj = self.amigo.send( buf[n:n + self.SECTOR_SIZE])
157
+ else:
158
+ return None
159
+
160
+ return size
@@ -0,0 +1,144 @@
1
+ #
2
+ # This file is part of the lif80utils distribution (https://codeberg.org/Boeingflieger/lif80utils).
3
+ # Copyright (c) 2026 Christian Grosz.
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, version 3.
8
+ #
9
+ # This program is distributed in the hope that it will be useful, but
10
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+
18
+ import sys
19
+
20
+ def decode_0001 ( infile, codepage = 'focal', linenumber = True):
21
+ # Always ascii. Overwrite for now.
22
+ # codepage = 'ascii'
23
+ # codepage = 'cp1252'
24
+ # codepage = 'utf-8'
25
+ # codepage = 'focal'
26
+
27
+ # MAIN.. .................
28
+ # 4D 41 49 4E 00 00 20 1B 06 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00
29
+
30
+ # HP 85 Assembler ROM source code internal format
31
+ # https://groups.io/g/hpseries80/wiki/20863
32
+ # MAIN NUL NUL Program name
33
+ program_name = infile.read( 6)
34
+ # 20 Program type (MAIN, BASIC, ALLOCATED)
35
+ program_type = int.from_bytes( infile.read( 1), byteorder='little')
36
+ # 1B 06 Program length (061B = 1563 bytes total)
37
+ program_length = int.from_bytes( infile.read( 2), byteorder='little')
38
+ # 00... 12 decimal bytes of NUL (unused)
39
+ infile.read( 12)
40
+ # 80 Security bits
41
+ security_bits = int.from_bytes( infile.read( 1), byteorder='little')
42
+ # 00 00 No security code
43
+ security_code = infile.read( 2)
44
+
45
+ # print( f"{program_name} {program_type:x} {program_length} {security_bits:x} {security_code}")
46
+
47
+ program_length = program_length - 24
48
+ length = 24
49
+
50
+ while True:
51
+ # 01 00 | 1E | FE | 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A | 0E
52
+ # 01 00 BCD line# = 0001 = 1
53
+ line_number = int( infile.read( 2)[::-1].hex( ))
54
+ # 1E len of this line of 'code'
55
+ line_length = int.from_bytes( infile.read( 1), byteorder='little')
56
+
57
+ line = infile.read( line_length)
58
+ # print( f"{line_number} {line_length:x} {line}")
59
+
60
+ # program_length = program_length - line_length - 3
61
+ program_length -= line_length + 3
62
+ length += line_length + 3
63
+
64
+ # FE Assembler ROM's COMMENT 'token'
65
+ # 2A 2A... ASCII comment (line of *'s) term'd by 0E
66
+ # 0E octal 16, END-OF-LINE token
67
+
68
+ # 20 00 | 0C | 47 4C 4F | FF | 47 4C 4F 42 41 4C 20 | 0E
69
+ # FF Assembler ROM's LABEL 'token'
70
+ # ..GLOÿGLOBAL.
71
+
72
+ label = ""
73
+ comment = ""
74
+ opcode = ""
75
+ arguments = ""
76
+
77
+ if 0xFF == line[0]:
78
+ label = line[1:7].decode( codepage)
79
+ line = line[7:]
80
+
81
+ comment_pos = line.find( b'\xFE')
82
+
83
+ if 0 <= comment_pos:
84
+ comment = f"!{line[comment_pos+1:-1].decode( codepage)}"
85
+ line = line[:comment_pos] + line[-1:]
86
+
87
+ if 0 == comment_pos:
88
+ if linenumber:
89
+ yield f"{line_number} {comment}\n"
90
+ else:
91
+ yield f"{comment}\n"
92
+
93
+ continue
94
+
95
+ # print( f" {label} {line} {comment}")
96
+
97
+ if 0x0E != line[0]:
98
+ opcode = line[0:3].decode( codepage)
99
+ line = line[3:]
100
+
101
+ if 0xFF == line[0]:
102
+ arguments = line[1:7].decode( codepage)
103
+ line = line[7:]
104
+ elif b'=\xFF' == line[0:2]:
105
+ arguments = (line[0:1] + line[2:-1]).decode( codepage)
106
+ line = line[-1:]
107
+ else:
108
+ arguments = line[:-1].decode( codepage)
109
+ line = line[-1:]
110
+
111
+ if linenumber:
112
+ yield f"{line_number} {label:6s} {opcode:3s} {arguments:15s} {comment}\n"
113
+ else:
114
+ yield f"{label:6s} {opcode:3s} {arguments:15s} {comment}\n"
115
+
116
+ # Another 5 bytes in the file.
117
+ # 99 A9 02 19 0E
118
+ # Constant, same also for all PGM files.
119
+ # Rest of block filled with garbage.
120
+
121
+ # https://groups.io/g/hpseries80/wiki/1893
122
+ # HP 85 System ROMs [Memory Layout (and Tokens and Allocation and...)]
123
+ # 231 251 002 031 016 (A999 len=2 STOP EOL)
124
+
125
+ # HP LIF/assets/attachments/parse_hp85.c.txt
126
+ # case 0031:
127
+ # printf("<\\031 END>\n");
128
+ # break;
129
+
130
+ # This is simple the tokenized END statement from Basic.
131
+ # Probably it is also used by the editor to detect the end of the program.
132
+
133
+ # https://archived.hpcalc.org/greendyk/hp85/83.html
134
+ # The END statement should be the highest numbered statement in a program. It
135
+ # not only tells the computer where a program ends, but also terminates program
136
+ # execution. (You may also use the STOP statement; on the HP-85 both END and
137
+ # STOP perform exactly the same function. END is provided on the HP-85 for
138
+ # compatibility with other BASIC systems.)
139
+
140
+ # print( f"{program_length} {length}")
141
+
142
+ if 5 == program_length:
143
+ break
144
+
@@ -0,0 +1,127 @@
1
+ #
2
+ # This file is part of the lif80utils distribution (https://codeberg.org/Boeingflieger/lif80utils).
3
+ # Copyright (c) 2026 Christian Grosz.
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, version 3.
8
+ #
9
+ # This program is distributed in the hope that it will be useful, but
10
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+
18
+ # My notes on decoding E010 format LIF images for HP-85
19
+ # https://github.com/magore/hp85disk/blob/master/lif/lif-notes.txt
20
+ #
21
+ # ef [ff]* = end of data in this sector (no size) , pad with ff's optionally if there is room
22
+ # df size = string
23
+ # cf size = split accross sector end "6f size" at start of next sector
24
+ # but the 6f size bytes are not included in cf size! (yuck!)
25
+ # 6f size = split continue (always starts at sector boundry)
26
+ # df 00 00 ef [ff]* = EOF (df size = 0) ef send of sector and optional padding
27
+ #
28
+ # cf 29 00 (19 is to sector end) (new sector start with 6F 10 00 (10 is remainder)
29
+ # So 29 = 19 and 10 (yuck!)
30
+
31
+ import sys
32
+ import re
33
+
34
+ #
35
+ #
36
+ #
37
+
38
+ SECTOR_SIZE = 256
39
+
40
+
41
+ def decode_E010 ( infile, codepage = 'focal', linenumber = True):
42
+
43
+ n = SECTOR_SIZE
44
+
45
+ while True:
46
+ b = infile.read( 1)
47
+ n -= 1
48
+
49
+ # if not b:
50
+ # print( "End of file")
51
+ # break
52
+
53
+ if b == b'\xdf': # Start of line.
54
+ l = infile.read( 2)
55
+ n -= 2
56
+
57
+ # Length of line including CR.
58
+ len = int.from_bytes( l, "little")
59
+ # print( f'DF {l} {len}')
60
+
61
+ if 0 == len:
62
+ # print( "End of program")
63
+ break
64
+
65
+ s = infile.read( len)
66
+ n -= len
67
+ # print( s.translate(trantab).decode( codepage), end = '')
68
+ if linenumber:
69
+ yield s.decode( codepage, 'combined')
70
+ else:
71
+ yield re.sub( r'^\d+ ', '', s.decode( codepage, 'combined'))
72
+
73
+ # yield s[:-1].decode( codepage, 'combined')
74
+
75
+ if 0 == n:
76
+ n = SECTOR_SIZE
77
+
78
+ elif b == b'\xcf': # Split accross sector end.
79
+ l = infile.read( 2)
80
+ n -= 2
81
+
82
+ # Length of line including CR.
83
+ len = int.from_bytes( l, "little")
84
+ # print( f'CF {l} {len}')
85
+
86
+ if len > n:
87
+ len = n
88
+
89
+ s = infile.read( len)
90
+ n -= len
91
+ # print( s.translate(trantab).decode( codepage), end = '')
92
+ # yield s.decode( codepage, 'combined')
93
+ n = SECTOR_SIZE
94
+
95
+ elif b == b'\x6f': # Split continue.
96
+ l = infile.read( 2)
97
+ n -= 2
98
+
99
+ # Length of line including CR.
100
+ len = int.from_bytes( l, "little")
101
+ # print( f'6F {l} {len}')
102
+
103
+ # s = infile.read( len)
104
+ s += infile.read( len)
105
+ n -= len
106
+
107
+ if linenumber:
108
+ yield s.decode( codepage, 'combined')
109
+ else:
110
+ yield re.sub( r'^\d+ ', '', s.decode( codepage, 'combined'))
111
+
112
+ elif b == b'\xef': # Pad sector.
113
+ while True:
114
+ b = infile.read( 1)
115
+
116
+ # TODO: Is that smart? Actually I know the size of the sector.
117
+ if b != b'\xff':
118
+ # print( f'EF {n}')
119
+ infile.seek(-1, 1) # goes back by one relative to current position
120
+ n = SECTOR_SIZE
121
+ break
122
+
123
+ n -= 1
124
+ else:
125
+ print( "Unexpected")
126
+ break
127
+