paul@0 | 1 | #!/usr/bin/env python |
paul@0 | 2 | |
paul@0 | 3 | """ |
paul@0 | 4 | UEFfile.py - Handle UEF archives. |
paul@0 | 5 | |
paul@0 | 6 | Copyright (c) 2001-2010, David Boddie <david@boddie.org.uk> |
paul@0 | 7 | |
paul@0 | 8 | This program is free software: you can redistribute it and/or modify |
paul@0 | 9 | it under the terms of the GNU General Public License as published by |
paul@0 | 10 | the Free Software Foundation, either version 3 of the License, or |
paul@0 | 11 | (at your option) any later version. |
paul@0 | 12 | |
paul@0 | 13 | This program is distributed in the hope that it will be useful, |
paul@0 | 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
paul@0 | 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
paul@0 | 16 | GNU General Public License for more details. |
paul@0 | 17 | |
paul@0 | 18 | You should have received a copy of the GNU General Public License |
paul@0 | 19 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
paul@0 | 20 | """ |
paul@0 | 21 | |
paul@0 | 22 | import exceptions, sys, string, os, gzip, types |
paul@0 | 23 | |
paul@0 | 24 | class UEFfile_error(exceptions.Exception): |
paul@0 | 25 | |
paul@0 | 26 | pass |
paul@0 | 27 | |
paul@0 | 28 | # Determine the platform on which the program is running |
paul@0 | 29 | |
paul@0 | 30 | sep = os.sep |
paul@0 | 31 | |
paul@0 | 32 | if sys.platform == 'RISCOS': |
paul@0 | 33 | suffix = '/' |
paul@0 | 34 | else: |
paul@0 | 35 | suffix = '.' |
paul@0 | 36 | |
paul@0 | 37 | version = '0.20' |
paul@0 | 38 | date = '2010-10-24' |
paul@0 | 39 | |
paul@0 | 40 | |
paul@0 | 41 | class UEFfile: |
paul@0 | 42 | """instance = UEFfile(filename, creator) |
paul@0 | 43 | |
paul@0 | 44 | Create an instance of a UEF container using an existing file. |
paul@0 | 45 | If filename is not defined then create a new UEF container. |
paul@0 | 46 | The creator parameter can be used to override the default |
paul@0 | 47 | creator string. |
paul@0 | 48 | |
paul@0 | 49 | """ |
paul@0 | 50 | |
paul@0 | 51 | def __init__(self, filename = None, creator = 'UEFfile '+version): |
paul@0 | 52 | """Create a new instance of the UEFfile class.""" |
paul@0 | 53 | |
paul@0 | 54 | if filename == None: |
paul@0 | 55 | |
paul@0 | 56 | # There are no chunks initially |
paul@0 | 57 | self.chunks = [] |
paul@0 | 58 | # There are no file positions defined |
paul@0 | 59 | self.files = [] |
paul@0 | 60 | |
paul@0 | 61 | # Emulator associated with this UEF file |
paul@0 | 62 | self.emulator = 'Unspecified' |
paul@0 | 63 | # Originator/creator of the UEF file |
paul@0 | 64 | self.creator = creator |
paul@0 | 65 | # Target machine |
paul@0 | 66 | self.target_machine = '' |
paul@0 | 67 | # Keyboard layout |
paul@0 | 68 | self.keyboard_layout = '' |
paul@0 | 69 | # Features |
paul@0 | 70 | self.features = '' |
paul@0 | 71 | |
paul@0 | 72 | # UEF file format version |
paul@0 | 73 | self.minor = 9 |
paul@0 | 74 | self.major = 0 |
paul@0 | 75 | |
paul@0 | 76 | # List of files |
paul@0 | 77 | self.contents = [] |
paul@0 | 78 | else: |
paul@0 | 79 | # Read in the chunks from the file |
paul@0 | 80 | |
paul@0 | 81 | # Open the input file |
paul@0 | 82 | try: |
paul@0 | 83 | in_f = open(filename, 'rb') |
paul@0 | 84 | except IOError: |
paul@0 | 85 | raise UEFfile_error, 'The input file, '+filename+' could not be found.' |
paul@0 | 86 | |
paul@0 | 87 | # Is it gzipped? |
paul@0 | 88 | if in_f.read(10) != 'UEF File!\000': |
paul@0 | 89 | |
paul@0 | 90 | in_f.close() |
paul@0 | 91 | in_f = gzip.open(filename, 'rb') |
paul@0 | 92 | |
paul@0 | 93 | try: |
paul@0 | 94 | if in_f.read(10) != 'UEF File!\000': |
paul@0 | 95 | in_f.close() |
paul@0 | 96 | raise UEFfile_error, 'The input file, '+filename+' is not a UEF file.' |
paul@0 | 97 | except: |
paul@0 | 98 | in_f.close() |
paul@0 | 99 | raise UEFfile_error, 'The input file, '+filename+' could not be read.' |
paul@0 | 100 | |
paul@0 | 101 | # Read version self.number of the file format |
paul@0 | 102 | self.minor = self.str2num(1, in_f.read(1)) |
paul@0 | 103 | self.major = self.str2num(1, in_f.read(1)) |
paul@0 | 104 | |
paul@0 | 105 | # Decode the UEF file |
paul@0 | 106 | |
paul@0 | 107 | # List of chunks |
paul@0 | 108 | self.chunks = [] |
paul@0 | 109 | |
paul@0 | 110 | # Read chunks |
paul@0 | 111 | |
paul@0 | 112 | while 1: |
paul@0 | 113 | |
paul@0 | 114 | # Read chunk ID |
paul@0 | 115 | chunk_id = in_f.read(2) |
paul@0 | 116 | if not chunk_id: |
paul@0 | 117 | break |
paul@0 | 118 | |
paul@0 | 119 | chunk_id = self.str2num(2, chunk_id) |
paul@0 | 120 | |
paul@0 | 121 | length = self.str2num(4, in_f.read(4)) |
paul@0 | 122 | |
paul@0 | 123 | if length != 0: |
paul@0 | 124 | self.chunks.append((chunk_id, in_f.read(length))) |
paul@0 | 125 | else: |
paul@0 | 126 | self.chunks.append((chunk_id, '')) |
paul@0 | 127 | |
paul@0 | 128 | # Close the input file |
paul@0 | 129 | in_f.close() |
paul@0 | 130 | |
paul@0 | 131 | # UEF file information (placed in "creator", "target_machine", |
paul@0 | 132 | # "keyboard_layout", "emulator" and "features" attributes). |
paul@0 | 133 | self.read_uef_details() |
paul@0 | 134 | |
paul@0 | 135 | # Read file contents (placed in the list attribute "contents"). |
paul@0 | 136 | self.read_contents() |
paul@0 | 137 | |
paul@0 | 138 | |
paul@0 | 139 | def write(self, filename, write_creator_info = True, |
paul@0 | 140 | write_machine_info = True, write_emulator_info = True): |
paul@0 | 141 | """ |
paul@0 | 142 | Write a UEF file containing all the information stored in an |
paul@0 | 143 | instance of UEFfile to the file with the specified filename. |
paul@0 | 144 | |
paul@0 | 145 | By default, information about the file's creator, target machine and |
paul@0 | 146 | emulator is written to the file. These can be omitted by calling this |
paul@0 | 147 | method with individual arguments set to False. |
paul@0 | 148 | """ |
paul@0 | 149 | |
paul@0 | 150 | # Open the UEF file for writing |
paul@0 | 151 | try: |
paul@0 | 152 | uef = gzip.open(filename, 'wb') |
paul@0 | 153 | except IOError: |
paul@0 | 154 | raise UEFfile_error, "Couldn't open %s for writing." % filename |
paul@0 | 155 | |
paul@0 | 156 | # Write the UEF file header |
paul@0 | 157 | self.write_uef_header(uef) |
paul@0 | 158 | |
paul@0 | 159 | if write_creator_info: |
paul@0 | 160 | # Write the UEF creator chunk to the file |
paul@0 | 161 | self.write_uef_creator(uef) |
paul@0 | 162 | |
paul@0 | 163 | if write_machine_info: |
paul@0 | 164 | # Write the machine information |
paul@0 | 165 | self.write_machine_info(uef) |
paul@0 | 166 | |
paul@0 | 167 | if write_emulator_info: |
paul@0 | 168 | # Write the emulator information |
paul@0 | 169 | self.write_emulator_info(uef) |
paul@0 | 170 | |
paul@0 | 171 | # Write the chunks to the file |
paul@0 | 172 | self.write_chunks(uef) |
paul@0 | 173 | |
paul@0 | 174 | # Close the file |
paul@0 | 175 | uef.close() |
paul@0 | 176 | |
paul@0 | 177 | |
paul@0 | 178 | def number(self, size, n): |
paul@0 | 179 | """Convert a number to a little endian string of bytes for writing to a binary file.""" |
paul@0 | 180 | |
paul@0 | 181 | # Little endian writing |
paul@0 | 182 | |
paul@0 | 183 | s = "" |
paul@0 | 184 | |
paul@0 | 185 | while size > 0: |
paul@0 | 186 | i = n % 256 |
paul@0 | 187 | s = s + chr(i) |
paul@0 | 188 | n = n >> 8 |
paul@0 | 189 | size = size - 1 |
paul@0 | 190 | |
paul@0 | 191 | return s |
paul@0 | 192 | |
paul@0 | 193 | |
paul@0 | 194 | def str2num(self, size, s): |
paul@0 | 195 | """Convert a string of ASCII characters to an integer.""" |
paul@0 | 196 | |
paul@0 | 197 | i = 0 |
paul@0 | 198 | n = 0 |
paul@0 | 199 | while i < size: |
paul@0 | 200 | |
paul@0 | 201 | n = n | (ord(s[i]) << (i*8)) |
paul@0 | 202 | i = i + 1 |
paul@0 | 203 | |
paul@0 | 204 | return n |
paul@0 | 205 | |
paul@0 | 206 | |
paul@0 | 207 | def hex2num(self, s): |
paul@0 | 208 | """Convert a string of hexadecimal digits to an integer.""" |
paul@0 | 209 | |
paul@0 | 210 | n = 0 |
paul@0 | 211 | |
paul@0 | 212 | for i in range(0,len(s)): |
paul@0 | 213 | |
paul@0 | 214 | a = ord(s[len(s)-i-1]) |
paul@0 | 215 | if (a >= 48) & (a <= 57): |
paul@0 | 216 | n = n | ((a-48) << (i*4)) |
paul@0 | 217 | elif (a >= 65) & (a <= 70): |
paul@0 | 218 | n = n | ((a-65+10) << (i*4)) |
paul@0 | 219 | elif (a >= 97) & (a <= 102): |
paul@0 | 220 | n = n | ((a-97+10) << (i*4)) |
paul@0 | 221 | else: |
paul@0 | 222 | return None |
paul@0 | 223 | |
paul@0 | 224 | return n |
paul@0 | 225 | |
paul@0 | 226 | |
paul@0 | 227 | # CRC calculation routines (begin) |
paul@0 | 228 | |
paul@0 | 229 | def rol(self, n, c): |
paul@0 | 230 | |
paul@0 | 231 | n = n << 1 |
paul@0 | 232 | |
paul@0 | 233 | if (n & 256) != 0: |
paul@0 | 234 | carry = 1 |
paul@0 | 235 | n = n & 255 |
paul@0 | 236 | else: |
paul@0 | 237 | carry = 0 |
paul@0 | 238 | |
paul@0 | 239 | n = n | c |
paul@0 | 240 | |
paul@0 | 241 | return n, carry |
paul@0 | 242 | |
paul@0 | 243 | |
paul@0 | 244 | def crc(self, s): |
paul@0 | 245 | |
paul@0 | 246 | high = 0 |
paul@0 | 247 | low = 0 |
paul@0 | 248 | |
paul@0 | 249 | for i in s: |
paul@0 | 250 | |
paul@0 | 251 | high = high ^ ord(i) |
paul@0 | 252 | |
paul@0 | 253 | for j in range(0,8): |
paul@0 | 254 | |
paul@0 | 255 | a, carry = self.rol(high, 0) |
paul@0 | 256 | |
paul@0 | 257 | if carry == 1: |
paul@0 | 258 | high = high ^ 8 |
paul@0 | 259 | low = low ^ 16 |
paul@0 | 260 | |
paul@0 | 261 | low, carry = self.rol(low, carry) |
paul@0 | 262 | high, carry = self.rol(high, carry) |
paul@0 | 263 | |
paul@0 | 264 | return high | (low << 8) |
paul@0 | 265 | |
paul@0 | 266 | # CRC calculation routines (end) |
paul@0 | 267 | |
paul@0 | 268 | def read_contents(self): |
paul@0 | 269 | """Find the positions of files in the list of chunks""" |
paul@0 | 270 | |
paul@0 | 271 | # List of files |
paul@0 | 272 | self.contents = [] |
paul@0 | 273 | |
paul@0 | 274 | current_file = {} |
paul@0 | 275 | |
paul@0 | 276 | position = 0 |
paul@0 | 277 | |
paul@0 | 278 | while 1: |
paul@0 | 279 | |
paul@0 | 280 | position = self.find_next_block(position) |
paul@0 | 281 | |
paul@0 | 282 | if position == None: |
paul@0 | 283 | |
paul@0 | 284 | # No more blocks, so store the details of the last file in |
paul@0 | 285 | # the contents list |
paul@0 | 286 | if current_file != {}: |
paul@0 | 287 | self.contents.append(current_file) |
paul@0 | 288 | break |
paul@0 | 289 | |
paul@0 | 290 | else: |
paul@0 | 291 | |
paul@0 | 292 | # Read the block information |
paul@0 | 293 | name, load, exec_addr, data, block_number, last = self.read_block(self.chunks[position]) |
paul@0 | 294 | |
paul@0 | 295 | if current_file == {}: |
paul@0 | 296 | |
paul@0 | 297 | # No current file, so store details |
paul@0 | 298 | current_file = {'name': name, 'load': load, 'exec': exec_addr, 'blocks': block_number, 'data': data} |
paul@0 | 299 | |
paul@0 | 300 | # Locate the first non-block chunk before the block |
paul@0 | 301 | # and store the position of the file |
paul@0 | 302 | current_file['position'] = self.find_file_start(position) |
paul@0 | 303 | # This may also be the position of the last chunk related to |
paul@0 | 304 | # this file in the archive |
paul@0 | 305 | current_file['last position'] = position |
paul@0 | 306 | else: |
paul@0 | 307 | |
paul@0 | 308 | # Current file exists |
paul@0 | 309 | if block_number == 0: |
paul@0 | 310 | |
paul@0 | 311 | # New file, so write the previous one to the |
paul@0 | 312 | # contents list, but before doing so, find the next |
paul@0 | 313 | # non-block chunk and mark that as the last chunk in |
paul@0 | 314 | # the file |
paul@0 | 315 | |
paul@0 | 316 | if current_file != {}: |
paul@0 | 317 | self.contents.append(current_file) |
paul@0 | 318 | |
paul@0 | 319 | # Store details of this new file |
paul@0 | 320 | current_file = {'name': name, 'load': load, 'exec': exec_addr, 'blocks': block_number, 'data': data} |
paul@0 | 321 | |
paul@0 | 322 | # Locate the first non-block chunk before the block |
paul@0 | 323 | # and store the position of the file |
paul@0 | 324 | current_file['position'] = self.find_file_start(position) |
paul@0 | 325 | # This may also be the position of the last chunk related to |
paul@0 | 326 | # this file in the archive |
paul@0 | 327 | current_file['last position'] = position |
paul@0 | 328 | else: |
paul@0 | 329 | # Not a new file, so update the number of |
paul@0 | 330 | # blocks and append the block data to the |
paul@0 | 331 | # data entry |
paul@0 | 332 | current_file['blocks'] = block_number |
paul@0 | 333 | current_file['data'] = current_file['data'] + data |
paul@0 | 334 | |
paul@0 | 335 | # Update the last position information to mark the end of the file |
paul@0 | 336 | current_file['last position'] = position |
paul@0 | 337 | |
paul@0 | 338 | # Increase the position |
paul@0 | 339 | position = position + 1 |
paul@0 | 340 | |
paul@0 | 341 | # We now have a contents list which tells us |
paul@0 | 342 | # 1) the names of files in the archive |
paul@0 | 343 | # 2) the load and execution addresses of them |
paul@0 | 344 | # 3) the number of blocks they contain |
paul@0 | 345 | # 4) their data, and from this their length |
paul@0 | 346 | # 5) their start position (chunk number) in the archive |
paul@0 | 347 | |
paul@0 | 348 | |
paul@0 | 349 | def chunk(self, f, n, data): |
paul@0 | 350 | """Write a chunk to the file specified by the open file object, chunk number and data supplied.""" |
paul@0 | 351 | |
paul@0 | 352 | # Chunk ID |
paul@0 | 353 | f.write(self.number(2, n)) |
paul@0 | 354 | # Chunk length |
paul@0 | 355 | f.write(self.number(4, len(data))) |
paul@0 | 356 | # Data |
paul@0 | 357 | f.write(data) |
paul@0 | 358 | |
paul@0 | 359 | |
paul@0 | 360 | def read_block(self, chunk): |
paul@0 | 361 | """Read a data block from a tape chunk and return the program name, load and execution addresses, |
paul@0 | 362 | block data, block number and whether the block is supposedly the last in the file.""" |
paul@0 | 363 | |
paul@0 | 364 | # Chunk number and data |
paul@0 | 365 | chunk_id = chunk[0] |
paul@0 | 366 | data = chunk[1] |
paul@0 | 367 | |
paul@0 | 368 | # For the implicit tape data chunk, just read the block as a series |
paul@0 | 369 | # of bytes, as before |
paul@0 | 370 | if chunk_id == 0x100: |
paul@0 | 371 | |
paul@0 | 372 | block = data |
paul@0 | 373 | |
paul@0 | 374 | else: # 0x102 |
paul@0 | 375 | |
paul@0 | 376 | if UEF_major == 0 and UEF_minor < 9: |
paul@0 | 377 | |
paul@0 | 378 | # For UEF file versions earlier than 0.9, the number of |
paul@0 | 379 | # excess bits to be ignored at the end of the stream is |
paul@0 | 380 | # set to zero implicitly |
paul@0 | 381 | ignore = 0 |
paul@0 | 382 | bit_ptr = 0 |
paul@0 | 383 | else: |
paul@0 | 384 | # For later versions, the number of excess bits is |
paul@0 | 385 | # specified in the first byte of the stream |
paul@0 | 386 | ignore = data[0] |
paul@0 | 387 | bit_ptr = 8 |
paul@0 | 388 | |
paul@0 | 389 | # Convert the data to the implicit format |
paul@0 | 390 | block = [] |
paul@0 | 391 | write_ptr = 0 |
paul@0 | 392 | |
paul@0 | 393 | after_end = (len(data)*8) - ignore |
paul@0 | 394 | if after_end % 10 != 0: |
paul@0 | 395 | |
paul@0 | 396 | # Ensure that the number of bits to be read is a |
paul@0 | 397 | # multiple of ten |
paul@0 | 398 | after_end = after_end - (after_end % 10) |
paul@0 | 399 | |
paul@0 | 400 | while bit_ptr < after_end: |
paul@0 | 401 | |
paul@0 | 402 | # Skip start bit |
paul@0 | 403 | bit_ptr = bit_ptr + 1 |
paul@0 | 404 | |
paul@0 | 405 | # Read eight bits of data |
paul@0 | 406 | bit_offset = bit_ptr % 8 |
paul@0 | 407 | if bit_offset == 0: |
paul@0 | 408 | # Write the byte to the block |
paul@0 | 409 | block[write_ptr] = data[bit_ptr >> 3] |
paul@0 | 410 | else: |
paul@0 | 411 | # Read the byte containing the first bits |
paul@0 | 412 | b1 = data[bit_ptr >> 3] |
paul@0 | 413 | # Read the byte containing the rest |
paul@0 | 414 | b2 = data[(bit_ptr >> 3) + 1] |
paul@0 | 415 | |
paul@0 | 416 | # Construct a byte of data |
paul@0 | 417 | # Shift the first byte right by the bit offset |
paul@0 | 418 | # in that byte |
paul@0 | 419 | b1 = b1 >> bit_offset |
paul@0 | 420 | |
paul@0 | 421 | # Shift the rest of the bits from the second |
paul@0 | 422 | # byte to the left and ensure that the result |
paul@0 | 423 | # fits in a byte |
paul@0 | 424 | b2 = (b2 << (8 - bit_offset)) & 0xff |
paul@0 | 425 | |
paul@0 | 426 | # OR the two bytes together and write it to |
paul@0 | 427 | # the block |
paul@0 | 428 | block[write_ptr] = b1 | b2 |
paul@0 | 429 | |
paul@0 | 430 | # Increment the block pointer |
paul@0 | 431 | write_ptr = write_ptr + 1 |
paul@0 | 432 | |
paul@0 | 433 | # Move the data pointer on eight bits and skip the |
paul@0 | 434 | # stop bit |
paul@0 | 435 | bit_ptr = bit_ptr + 9 |
paul@0 | 436 | |
paul@0 | 437 | # Read the block |
paul@0 | 438 | name = '' |
paul@0 | 439 | a = 1 |
paul@0 | 440 | while 1: |
paul@0 | 441 | c = block[a] |
paul@0 | 442 | if ord(c) != 0: # was > 32: |
paul@0 | 443 | name = name + c |
paul@0 | 444 | a = a + 1 |
paul@0 | 445 | if ord(c) == 0: |
paul@0 | 446 | break |
paul@0 | 447 | |
paul@0 | 448 | load = self.str2num(4, block[a:a+4]) |
paul@0 | 449 | exec_addr = self.str2num(4, block[a+4:a+8]) |
paul@0 | 450 | block_number = self.str2num(2, block[a+8:a+10]) |
paul@0 | 451 | last = self.str2num(1, block[a+12]) |
paul@0 | 452 | |
paul@0 | 453 | if last & 0x80 != 0: |
paul@0 | 454 | last = 1 |
paul@0 | 455 | else: |
paul@0 | 456 | last = 0 |
paul@0 | 457 | |
paul@0 | 458 | return (name, load, exec_addr, block[a+19:-2], block_number, last) |
paul@0 | 459 | |
paul@0 | 460 | |
paul@0 | 461 | def write_block(self, block, name, load, exe, n): |
paul@0 | 462 | """Write data to a string as a file data block in preparation to be written |
paul@0 | 463 | as chunk data to a UEF file.""" |
paul@0 | 464 | |
paul@0 | 465 | # Write the alignment character |
paul@0 | 466 | out = "*"+name[:10]+"\000" |
paul@0 | 467 | |
paul@0 | 468 | # Load address |
paul@0 | 469 | out = out + self.number(4, load) |
paul@0 | 470 | |
paul@0 | 471 | # Execution address |
paul@0 | 472 | out = out + self.number(4, exe) |
paul@0 | 473 | |
paul@0 | 474 | # Block number |
paul@0 | 475 | out = out + self.number(2, n) |
paul@0 | 476 | |
paul@0 | 477 | # Block length |
paul@0 | 478 | out = out + self.number(2, len(block)) |
paul@0 | 479 | |
paul@0 | 480 | # Block flag (last block) |
paul@0 | 481 | if len(block) == 256: |
paul@0 | 482 | out = out + self.number(1, 0) |
paul@0 | 483 | last = 0 |
paul@0 | 484 | else: |
paul@0 | 485 | out = out + self.number(1, 128) # shouldn't be needed |
paul@0 | 486 | last = 1 |
paul@0 | 487 | |
paul@0 | 488 | # Next address |
paul@0 | 489 | out = out + self.number(2, 0) |
paul@0 | 490 | |
paul@0 | 491 | # Unknown |
paul@0 | 492 | out = out + self.number(2, 0) |
paul@0 | 493 | |
paul@0 | 494 | # Header CRC |
paul@0 | 495 | out = out + self.number(2, self.crc(out[1:])) |
paul@0 | 496 | |
paul@0 | 497 | out = out + block |
paul@0 | 498 | |
paul@0 | 499 | # Block CRC |
paul@0 | 500 | out = out + self.number(2, self.crc(block)) |
paul@0 | 501 | |
paul@0 | 502 | return out, last |
paul@0 | 503 | |
paul@0 | 504 | |
paul@0 | 505 | def get_leafname(self, path): |
paul@0 | 506 | """Get the leafname of the specified file.""" |
paul@0 | 507 | |
paul@0 | 508 | pos = string.rfind(path, os.sep) |
paul@0 | 509 | if pos != -1: |
paul@0 | 510 | return path[pos+1:] |
paul@0 | 511 | else: |
paul@0 | 512 | return path |
paul@0 | 513 | |
paul@0 | 514 | |
paul@0 | 515 | def find_next_chunk(self, pos, IDs): |
paul@0 | 516 | """position, chunk = find_next_chunk(start, IDs) |
paul@0 | 517 | Search through the list of chunks from the start position given |
paul@0 | 518 | for the next chunk with an ID in the list of IDs supplied. |
paul@0 | 519 | Return its position in the list of chunks and its details.""" |
paul@0 | 520 | |
paul@0 | 521 | while pos < len(self.chunks): |
paul@0 | 522 | |
paul@0 | 523 | if self.chunks[pos][0] in IDs: |
paul@0 | 524 | |
paul@0 | 525 | # Found a chunk with ID in the list |
paul@0 | 526 | return pos, self.chunks[pos] |
paul@0 | 527 | |
paul@0 | 528 | # Otherwise continue looking |
paul@0 | 529 | pos = pos + 1 |
paul@0 | 530 | |
paul@0 | 531 | return None, None |
paul@0 | 532 | |
paul@0 | 533 | |
paul@0 | 534 | def find_next_block(self, pos): |
paul@0 | 535 | """Find the next file block in the list of chunks.""" |
paul@0 | 536 | |
paul@0 | 537 | while pos < len(self.chunks): |
paul@0 | 538 | |
paul@0 | 539 | pos, chunk = self.find_next_chunk(pos, [0x100, 0x102]) |
paul@0 | 540 | |
paul@0 | 541 | if pos == None: |
paul@0 | 542 | |
paul@0 | 543 | return None |
paul@0 | 544 | else: |
paul@0 | 545 | if len(chunk[1]) > 1: |
paul@0 | 546 | |
paul@0 | 547 | # Found a block, return this position |
paul@0 | 548 | return pos |
paul@0 | 549 | |
paul@0 | 550 | # Otherwise continue looking |
paul@0 | 551 | pos = pos + 1 |
paul@0 | 552 | |
paul@0 | 553 | return None |
paul@0 | 554 | |
paul@0 | 555 | |
paul@0 | 556 | def find_file_start(self, pos): |
paul@0 | 557 | """Find a chunk before the one specified which is not a file block.""" |
paul@0 | 558 | |
paul@0 | 559 | pos = pos - 1 |
paul@0 | 560 | while pos > 0: |
paul@0 | 561 | |
paul@0 | 562 | if self.chunks[pos][0] != 0x100 and self.chunks[pos][0] != 0x102: |
paul@0 | 563 | |
paul@0 | 564 | # This is not a block |
paul@0 | 565 | return pos |
paul@0 | 566 | |
paul@0 | 567 | else: |
paul@0 | 568 | pos = pos - 1 |
paul@0 | 569 | |
paul@0 | 570 | return pos |
paul@0 | 571 | |
paul@0 | 572 | |
paul@0 | 573 | def find_file_end(self, pos): |
paul@0 | 574 | """Find a chunk after the one specified which is not a file block.""" |
paul@0 | 575 | |
paul@0 | 576 | pos = pos + 1 |
paul@0 | 577 | while pos < len(self.chunks)-1: |
paul@0 | 578 | |
paul@0 | 579 | if self.chunks[pos][0] != 0x100 and self.chunks[pos][0] != 0x102: |
paul@0 | 580 | |
paul@0 | 581 | # This is not a block |
paul@0 | 582 | return pos |
paul@0 | 583 | |
paul@0 | 584 | else: |
paul@0 | 585 | pos = pos + 1 |
paul@0 | 586 | |
paul@0 | 587 | return pos |
paul@0 | 588 | |
paul@0 | 589 | |
paul@0 | 590 | def read_uef_details(self): |
paul@0 | 591 | """Return details about the UEF file and its contents.""" |
paul@0 | 592 | |
paul@0 | 593 | # Find the creator chunk |
paul@0 | 594 | pos, chunk = self.find_next_chunk(0, [0x0]) |
paul@0 | 595 | |
paul@0 | 596 | if pos == None: |
paul@0 | 597 | |
paul@0 | 598 | self.creator = 'Unknown' |
paul@0 | 599 | |
paul@0 | 600 | elif chunk[1] == '': |
paul@0 | 601 | |
paul@0 | 602 | self.creator = 'Unknown' |
paul@0 | 603 | else: |
paul@0 | 604 | self.creator = chunk[1] |
paul@0 | 605 | |
paul@0 | 606 | # Delete the creator chunk |
paul@0 | 607 | if pos != None: |
paul@0 | 608 | del self.chunks[pos] |
paul@0 | 609 | |
paul@0 | 610 | # Find the target machine chunk |
paul@0 | 611 | pos, chunk = self.find_next_chunk(0, [0x5]) |
paul@0 | 612 | |
paul@0 | 613 | if pos == None: |
paul@0 | 614 | |
paul@0 | 615 | self.target_machine = 'Unknown' |
paul@0 | 616 | self.keyboard_layout = 'Unknown' |
paul@0 | 617 | else: |
paul@0 | 618 | |
paul@0 | 619 | machines = ('BBC Model A', 'Electron', 'BBC Model B', 'BBC Master') |
paul@0 | 620 | keyboards = ('Any layout', 'Physical layout', 'Remapped') |
paul@0 | 621 | |
paul@0 | 622 | machine = ord(chunk[1][0]) & 0x0f |
paul@0 | 623 | keyboard = (ord(chunk[1][0]) & 0xf0) >> 4 |
paul@0 | 624 | |
paul@0 | 625 | if machine < len(machines): |
paul@0 | 626 | self.target_machine = machines[machine] |
paul@0 | 627 | else: |
paul@0 | 628 | self.target_machine = 'Unknown' |
paul@0 | 629 | |
paul@0 | 630 | if keyboard < len(keyboards): |
paul@0 | 631 | self.keyboard_layout = keyboards[keyboard] |
paul@0 | 632 | else: |
paul@0 | 633 | self.keyboard_layout = 'Unknown' |
paul@0 | 634 | |
paul@0 | 635 | # Delete the target machine chunk |
paul@0 | 636 | del self.chunks[pos] |
paul@0 | 637 | |
paul@0 | 638 | # Find the emulator chunk |
paul@0 | 639 | pos, chunk = self.find_next_chunk(0, [0xff00]) |
paul@0 | 640 | |
paul@0 | 641 | if pos == None: |
paul@0 | 642 | |
paul@0 | 643 | self.emulator = 'Unspecified' |
paul@0 | 644 | |
paul@0 | 645 | elif chunk[1] == '': |
paul@0 | 646 | |
paul@0 | 647 | self.emulator = 'Unknown' |
paul@0 | 648 | else: |
paul@0 | 649 | self.emulator = chunk[1] |
paul@0 | 650 | |
paul@0 | 651 | # Delete the emulator chunk |
paul@0 | 652 | if pos != None: |
paul@0 | 653 | del self.chunks[pos] |
paul@0 | 654 | |
paul@0 | 655 | # Remove trailing null bytes |
paul@0 | 656 | while len(self.creator) > 0 and self.creator[-1] == '\000': |
paul@0 | 657 | |
paul@0 | 658 | self.creator = self.creator[:-1] |
paul@0 | 659 | |
paul@0 | 660 | while len(self.emulator) > 0 and self.emulator[-1] == '\000': |
paul@0 | 661 | |
paul@0 | 662 | self.emulator = self.emulator[:-1] |
paul@0 | 663 | |
paul@0 | 664 | self.features = '' |
paul@0 | 665 | if self.find_next_chunk(0, [0x1])[0] != None: |
paul@0 | 666 | self.features = self.features + '\n' + 'Instructions' |
paul@0 | 667 | if self.find_next_chunk(0, [0x2])[0] != None: |
paul@0 | 668 | self.features = self.features + '\n' + 'Credits' |
paul@0 | 669 | if self.find_next_chunk(0, [0x3])[0] != None: |
paul@0 | 670 | self.features = self.features + '\n' + 'Inlay' |
paul@0 | 671 | |
paul@0 | 672 | |
paul@0 | 673 | def write_uef_header(self, file): |
paul@0 | 674 | """Write the UEF file header and version number to a file.""" |
paul@0 | 675 | |
paul@0 | 676 | # Write the UEF file header |
paul@0 | 677 | file.write('UEF File!\000') |
paul@0 | 678 | |
paul@0 | 679 | # Minor and major version numbers |
paul@0 | 680 | file.write(self.number(1, self.minor) + self.number(1, self.major)) |
paul@0 | 681 | |
paul@0 | 682 | |
paul@0 | 683 | def write_uef_creator(self, file): |
paul@0 | 684 | """Write a creator chunk to a file.""" |
paul@0 | 685 | |
paul@0 | 686 | origin = self.creator + '\000' |
paul@0 | 687 | |
paul@0 | 688 | if (len(origin) % 4) != 0: |
paul@0 | 689 | origin = origin + ('\000'*(4-(len(origin) % 4))) |
paul@0 | 690 | |
paul@0 | 691 | # Write the creator chunk |
paul@0 | 692 | self.chunk(file, 0, origin) |
paul@0 | 693 | |
paul@0 | 694 | |
paul@0 | 695 | def write_machine_info(self, file): |
paul@0 | 696 | """Write the target machine and keyboard layout information to a file.""" |
paul@0 | 697 | |
paul@0 | 698 | machines = {'BBC Model A': 0, 'Electron': 1, 'BBC Model B': 2, 'BBC Master':3} |
paul@0 | 699 | keyboards = {'any': 0, 'physical': 1, 'logical': 2} |
paul@0 | 700 | |
paul@0 | 701 | if machines.has_key(self.target_machine): |
paul@0 | 702 | |
paul@0 | 703 | machine = machines[self.target_machine] |
paul@0 | 704 | else: |
paul@0 | 705 | machine = 0 |
paul@0 | 706 | |
paul@0 | 707 | if keyboards.has_key(self.keyboard_layout): |
paul@0 | 708 | |
paul@0 | 709 | keyboard = keyboards[keyboard_layout] |
paul@0 | 710 | else: |
paul@0 | 711 | keyboard = 0 |
paul@0 | 712 | |
paul@0 | 713 | self.chunk(file, 5, self.number(1, machine | (keyboard << 4) )) |
paul@0 | 714 | |
paul@0 | 715 | |
paul@0 | 716 | def write_emulator_info(self, file): |
paul@0 | 717 | """Write an emulator chunk to a file.""" |
paul@0 | 718 | |
paul@0 | 719 | emulator = self.emulator + '\000' |
paul@0 | 720 | |
paul@0 | 721 | if (len(emulator) % 4) != 0: |
paul@0 | 722 | emulator = emulator + ('\000'*(4-(len(emulator) % 4))) |
paul@0 | 723 | |
paul@0 | 724 | # Write the creator chunk |
paul@0 | 725 | self.chunk(file, 0xff00, emulator) |
paul@0 | 726 | |
paul@0 | 727 | |
paul@0 | 728 | def write_chunks(self, file): |
paul@0 | 729 | """Write all the chunks in the list to a file. Saves having loops in other functions to do this.""" |
paul@0 | 730 | |
paul@0 | 731 | for c in self.chunks: |
paul@0 | 732 | |
paul@0 | 733 | self.chunk(file, c[0], c[1]) |
paul@0 | 734 | |
paul@0 | 735 | |
paul@0 | 736 | def create_chunks(self, name, load, exe, data): |
paul@0 | 737 | """Create suitable chunks, and insert them into |
paul@0 | 738 | the list of chunks.""" |
paul@0 | 739 | |
paul@0 | 740 | # Reset the block number to zero |
paul@0 | 741 | block_number = 0 |
paul@0 | 742 | |
paul@0 | 743 | # Long gap |
paul@0 | 744 | gap = 1 |
paul@0 | 745 | |
paul@0 | 746 | new_chunks = [] |
paul@0 | 747 | |
paul@0 | 748 | # Write block details |
paul@0 | 749 | while 1: |
paul@0 | 750 | block, last = self.write_block(data[:256], name, load, exe, block_number) |
paul@0 | 751 | |
paul@0 | 752 | # Remove the leading 256 bytes as they have been encoded |
paul@0 | 753 | data = data[256:] |
paul@0 | 754 | |
paul@0 | 755 | if gap == 1: |
paul@0 | 756 | new_chunks.append((0x110, self.number(2,0x05dc))) |
paul@0 | 757 | gap = 0 |
paul@0 | 758 | else: |
paul@0 | 759 | new_chunks.append((0x110, self.number(2,0x0258))) |
paul@0 | 760 | |
paul@0 | 761 | # Write the block to the list of new chunks |
paul@0 | 762 | new_chunks.append((0x100, block)) |
paul@0 | 763 | |
paul@0 | 764 | if last == 1: |
paul@0 | 765 | break |
paul@0 | 766 | |
paul@0 | 767 | # Increment the block number |
paul@0 | 768 | block_number = block_number + 1 |
paul@0 | 769 | |
paul@0 | 770 | # Return the list of new chunks |
paul@0 | 771 | return new_chunks |
paul@0 | 772 | |
paul@0 | 773 | |
paul@0 | 774 | def import_files(self, file_position, info): |
paul@0 | 775 | """ |
paul@0 | 776 | Import a file into the UEF file at the specified location in the |
paul@0 | 777 | list of contents. |
paul@0 | 778 | positions is a positive integer or zero |
paul@0 | 779 | |
paul@0 | 780 | To insert one file, info can be a sequence: |
paul@0 | 781 | |
paul@0 | 782 | info = (name, load, exe, data) where |
paul@0 | 783 | name is the file's name. |
paul@0 | 784 | load is the load address of the file. |
paul@0 | 785 | exe is the execution address. |
paul@0 | 786 | data is the contents of the file. |
paul@0 | 787 | |
paul@0 | 788 | For more than one file, info must be a sequence of info sequences. |
paul@0 | 789 | """ |
paul@0 | 790 | |
paul@0 | 791 | if file_position < 0: |
paul@0 | 792 | |
paul@0 | 793 | raise UEFfile_error, 'Position must be zero or greater.' |
paul@0 | 794 | |
paul@0 | 795 | # Find the chunk position which corresponds to the file_position |
paul@0 | 796 | if self.contents != []: |
paul@0 | 797 | |
paul@0 | 798 | # There are files already present |
paul@0 | 799 | if file_position >= len(self.contents): |
paul@0 | 800 | |
paul@0 | 801 | # Position the new files after the end of the last file |
paul@0 | 802 | position = self.contents[-1]['last position'] + 1 |
paul@0 | 803 | |
paul@0 | 804 | else: |
paul@0 | 805 | |
paul@0 | 806 | # Position the new files before the end of the file |
paul@0 | 807 | # specified |
paul@0 | 808 | position = self.contents[file_position]['position'] |
paul@0 | 809 | else: |
paul@0 | 810 | # There are no files present in the archive, so put them after |
paul@0 | 811 | # all the other chunks |
paul@0 | 812 | position = len(self.chunks) |
paul@0 | 813 | |
paul@0 | 814 | # Examine the info sequence passed |
paul@0 | 815 | if len(info) == 0: |
paul@0 | 816 | return |
paul@0 | 817 | |
paul@0 | 818 | if type(info[0]) == types.StringType: |
paul@0 | 819 | |
paul@0 | 820 | # Assume that the info sequence contains name, load, exe, data |
paul@0 | 821 | info = [info] |
paul@0 | 822 | |
paul@0 | 823 | # Read the file details for each file and create chunks to add |
paul@0 | 824 | # to the list of chunks |
paul@0 | 825 | inserted_chunks = [] |
paul@0 | 826 | |
paul@0 | 827 | for name, load, exe, data in info: |
paul@0 | 828 | |
paul@0 | 829 | inserted_chunks = inserted_chunks + self.create_chunks(name, load, exe, data) |
paul@0 | 830 | |
paul@0 | 831 | # Insert the chunks in the list at the specified position |
paul@0 | 832 | self.chunks = self.chunks[:position] + inserted_chunks + self.chunks[position:] |
paul@0 | 833 | |
paul@0 | 834 | # Update the contents list |
paul@0 | 835 | self.read_contents() |
paul@0 | 836 | |
paul@0 | 837 | |
paul@0 | 838 | def chunk_number(self, name): |
paul@0 | 839 | """ |
paul@0 | 840 | Returns the relevant chunk number for the name given. |
paul@0 | 841 | """ |
paul@0 | 842 | |
paul@0 | 843 | # Use a convention for determining the chunk number to be used: |
paul@0 | 844 | # Certain names are converted to chunk numbers. These are listed |
paul@0 | 845 | # in the encode_as dictionary. |
paul@0 | 846 | |
paul@0 | 847 | encode_as = {'creator': 0x0, 'originator': 0x0, 'instructions': 0x1, 'manual': 0x1, |
paul@0 | 848 | 'credits': 0x2, 'inlay': 0x3, 'target': 0x5, 'machine': 0x5, |
paul@0 | 849 | 'multi': 0x6, 'multiplexing': 0x6, 'palette': 0x7, |
paul@0 | 850 | 'tone': 0x110, 'dummy': 0x111, 'gap': 0x112, 'baud': 0x113, |
paul@0 | 851 | 'position': 0x120, |
paul@0 | 852 | 'discinfo': 0x200, 'discside': 0x201, 'rom': 0x300, |
paul@0 | 853 | '6502': 0x400, 'ula': 0x401, 'wd1770': 0x402, 'memory': 0x410, |
paul@0 | 854 | 'emulator': 0xff00} |
paul@0 | 855 | |
paul@0 | 856 | # Attempt to convert name into a chunk number |
paul@0 | 857 | try: |
paul@0 | 858 | return encode_as[string.lower(name)] |
paul@0 | 859 | |
paul@0 | 860 | except KeyError: |
paul@0 | 861 | raise UEFfile_error, "Couldn't find suitable chunk number for %s" % name |
paul@0 | 862 | |
paul@0 | 863 | |
paul@0 | 864 | def export_files(self, file_positions): |
paul@0 | 865 | """ |
paul@0 | 866 | Given a file's location of the list of contents, returns its name, |
paul@0 | 867 | load and execution addresses, and the data contained in the file. |
paul@0 | 868 | If positions is an integer then return a tuple |
paul@0 | 869 | |
paul@0 | 870 | info = (name, load, exe, data) |
paul@0 | 871 | |
paul@0 | 872 | If positions is a list then return a list of info tuples. |
paul@0 | 873 | """ |
paul@0 | 874 | |
paul@0 | 875 | if type(file_positions) == types.IntType: |
paul@0 | 876 | |
paul@0 | 877 | file_positions = [file_positions] |
paul@0 | 878 | |
paul@0 | 879 | info = [] |
paul@0 | 880 | |
paul@0 | 881 | for file_position in file_positions: |
paul@0 | 882 | |
paul@0 | 883 | # Find the chunk position which corresponds to the file position |
paul@0 | 884 | if file_position < 0 or file_position >= len(self.contents): |
paul@0 | 885 | |
paul@0 | 886 | raise UEFfile_error, 'File position %i does not correspond to an actual file.' % file_position |
paul@0 | 887 | else: |
paul@0 | 888 | # Find the start and end positions |
paul@0 | 889 | name = self.contents[file_position]['name'] |
paul@0 | 890 | load = self.contents[file_position]['load'] |
paul@0 | 891 | exe = self.contents[file_position]['exec'] |
paul@0 | 892 | |
paul@0 | 893 | info.append( (name, load, exe, self.contents[file_position]['data']) ) |
paul@0 | 894 | |
paul@0 | 895 | if len(info) == 1: |
paul@0 | 896 | info = info[0] |
paul@0 | 897 | |
paul@0 | 898 | return info |
paul@0 | 899 | |
paul@0 | 900 | |
paul@0 | 901 | def chunk_name(self, number): |
paul@0 | 902 | """ |
paul@0 | 903 | Returns the relevant chunk name for the number given. |
paul@0 | 904 | """ |
paul@0 | 905 | |
paul@0 | 906 | decode_as = {0x0: 'creator', 0x1: 'manual', 0x2: 'credits', 0x3: 'inlay', |
paul@0 | 907 | 0x5: 'machine', 0x6: 'multiplexing', 0x7: 'palette', |
paul@0 | 908 | 0x110: 'tone', 0x111: 'dummy', 0x112: 'gap', 0x113: 'baud', |
paul@0 | 909 | 0x120: 'position', |
paul@0 | 910 | 0x200: 'discinfo', 0x201: 'discside', 0x300: 'rom', |
paul@0 | 911 | 0x400: '6502', 0x401: 'ula', 0x402: 'wd1770', 0x410: 'memory', |
paul@0 | 912 | 0xff00: 'emulator'} |
paul@0 | 913 | |
paul@0 | 914 | try: |
paul@0 | 915 | return decode_as[number] |
paul@0 | 916 | except KeyError: |
paul@0 | 917 | raise UEFfile_error, "Couldn't find name for chunk number %i." % number |
paul@0 | 918 | |
paul@0 | 919 | |
paul@0 | 920 | def remove_files(self, file_positions): |
paul@0 | 921 | """ |
paul@0 | 922 | Removes files at the positions in the list of contents. |
paul@0 | 923 | positions is either an integer or a list of integers. |
paul@0 | 924 | """ |
paul@0 | 925 | |
paul@0 | 926 | if type(file_positions) == types.IntType: |
paul@0 | 927 | |
paul@0 | 928 | file_positions = [file_positions] |
paul@0 | 929 | |
paul@0 | 930 | positions = [] |
paul@0 | 931 | for file_position in file_positions: |
paul@0 | 932 | |
paul@0 | 933 | # Find the chunk position which corresponds to the file position |
paul@0 | 934 | if file_position < 0 or file_position >= len(self.contents): |
paul@0 | 935 | |
paul@0 | 936 | print 'File position %i does not correspond to an actual file.' % file_position |
paul@0 | 937 | |
paul@0 | 938 | else: |
paul@0 | 939 | # Add the chunk positions within each file to the list of positions |
paul@0 | 940 | positions = positions + range(self.contents[file_position]['position'], |
paul@0 | 941 | self.contents[file_position]['last position'] + 1) |
paul@0 | 942 | |
paul@0 | 943 | # Create a new list of chunks without those in the positions list |
paul@0 | 944 | new_chunks = [] |
paul@0 | 945 | for c in range(0, len(self.chunks)): |
paul@0 | 946 | |
paul@0 | 947 | if c not in positions: |
paul@0 | 948 | new_chunks.append(self.chunks[c]) |
paul@0 | 949 | |
paul@0 | 950 | # Overwrite the chunks list with this new list |
paul@0 | 951 | self.chunks = new_chunks |
paul@0 | 952 | |
paul@0 | 953 | # Create a new contents list |
paul@0 | 954 | self.read_contents() |
paul@0 | 955 | |
paul@0 | 956 | |
paul@0 | 957 | def printable(self, s): |
paul@0 | 958 | |
paul@0 | 959 | new = '' |
paul@0 | 960 | for i in s: |
paul@0 | 961 | |
paul@0 | 962 | if ord(i) < 32: |
paul@0 | 963 | new = new + '?' |
paul@0 | 964 | else: |
paul@0 | 965 | new = new + i |
paul@0 | 966 | |
paul@0 | 967 | return new |
paul@0 | 968 | |
paul@0 | 969 | |
paul@0 | 970 | # Higher level functions ------------------------------ |
paul@0 | 971 | |
paul@0 | 972 | def info(self): |
paul@0 | 973 | """ |
paul@0 | 974 | Provides general information on the target machine, |
paul@0 | 975 | keyboard layout, file creator and target emulator. |
paul@0 | 976 | """ |
paul@0 | 977 | |
paul@0 | 978 | # Info command |
paul@0 | 979 | |
paul@0 | 980 | # Split paragraphs |
paul@0 | 981 | creator = string.split(self.creator, '\012') |
paul@0 | 982 | |
paul@0 | 983 | print 'File creator:' |
paul@0 | 984 | for line in creator: |
paul@0 | 985 | print line |
paul@0 | 986 | print |
paul@0 | 987 | print 'File format version: %i.%i' % (self.major, self.minor) |
paul@0 | 988 | print |
paul@0 | 989 | print 'Target machine : '+self.target_machine |
paul@0 | 990 | print 'Keyboard layout: '+self.keyboard_layout |
paul@0 | 991 | print 'Emulator : '+self.emulator |
paul@0 | 992 | print |
paul@0 | 993 | if self.features != '': |
paul@0 | 994 | |
paul@0 | 995 | print 'Contains:' |
paul@0 | 996 | print self.features |
paul@0 | 997 | print |
paul@0 | 998 | print '(%i chunks)' % len(self.chunks) |
paul@0 | 999 | print |
paul@0 | 1000 | |
paul@0 | 1001 | def cat(self): |
paul@0 | 1002 | """ |
paul@0 | 1003 | Prints a catalogue of the files stored in the UEF file. |
paul@0 | 1004 | """ |
paul@0 | 1005 | |
paul@0 | 1006 | # Catalogue command |
paul@0 | 1007 | |
paul@0 | 1008 | if self.contents == []: |
paul@0 | 1009 | |
paul@0 | 1010 | print 'No files' |
paul@0 | 1011 | |
paul@0 | 1012 | else: |
paul@0 | 1013 | |
paul@0 | 1014 | print 'Contents:' |
paul@0 | 1015 | |
paul@0 | 1016 | file_number = 0 |
paul@0 | 1017 | |
paul@0 | 1018 | for file in self.contents: |
paul@0 | 1019 | |
paul@0 | 1020 | # Converts non printable characters in the filename |
paul@0 | 1021 | # to ? symbols |
paul@0 | 1022 | new_name = self.printable(file['name']) |
paul@0 | 1023 | |
paul@0 | 1024 | print string.expandtabs(string.ljust(str(file_number), 3)+': '+ |
paul@0 | 1025 | string.ljust(new_name, 16)+ |
paul@0 | 1026 | string.upper( |
paul@0 | 1027 | string.ljust(hex(file['load'])[2:], 10) +'\t'+ |
paul@0 | 1028 | string.ljust(hex(file['exec'])[2:], 10) +'\t'+ |
paul@0 | 1029 | string.ljust(hex(len(file['data']))[2:], 6) |
paul@0 | 1030 | ) +'\t'+ |
paul@0 | 1031 | 'chunks %i to %i' % (file['position'], file['last position']) ) |
paul@0 | 1032 | |
paul@0 | 1033 | file_number = file_number + 1 |
paul@0 | 1034 | |
paul@0 | 1035 | def show_chunks(self): |
paul@0 | 1036 | """ |
paul@0 | 1037 | Display the chunks in the UEF file in a table format |
paul@0 | 1038 | with the following symbols denoting each type of |
paul@0 | 1039 | chunk: |
paul@0 | 1040 | O Originator information (0x0) |
paul@0 | 1041 | I Instructions/manual (0x1) |
paul@0 | 1042 | C Author credits (0x2) |
paul@0 | 1043 | S Inlay scan (0x3) |
paul@0 | 1044 | M Target machine information (0x5) |
paul@0 | 1045 | X Multiplexing information (0x6) |
paul@0 | 1046 | P Extra palette (0x7) |
paul@0 | 1047 | |
paul@0 | 1048 | #, * File data block (0x100,0x102) |
paul@0 | 1049 | #x, *x Multiplexed block (0x101,0x103) |
paul@0 | 1050 | - High tone (inter-block gap) (0x110) |
paul@0 | 1051 | + High tone with dummy byte (0x111) |
paul@0 | 1052 | _ Gap (silence) (0x112) |
paul@0 | 1053 | B Change of baud rate (0x113) |
paul@0 | 1054 | ! Position marker (0x120) |
paul@0 | 1055 | D Disc information (0x200) |
paul@0 | 1056 | d Standard disc side (0x201) |
paul@0 | 1057 | dx Multiplexed disc side (0x202) |
paul@0 | 1058 | R Standard machine ROM (0x300) |
paul@0 | 1059 | Rx Multiplexed machine ROM (0x301) |
paul@0 | 1060 | 6 6502 standard state (0x400) |
paul@0 | 1061 | U Electron ULA state (0x401) |
paul@0 | 1062 | W WD1770 state (0x402) |
paul@0 | 1063 | m Standard memory data (0x410) |
paul@0 | 1064 | mx Multiplexed memory data (0x410) |
paul@0 | 1065 | |
paul@0 | 1066 | E Emulator identification string (0xff00) |
paul@0 | 1067 | ? Unknown (unsupported chunk) |
paul@0 | 1068 | """ |
paul@0 | 1069 | |
paul@0 | 1070 | chunks_symbols = { |
paul@0 | 1071 | 0x0: 'O ', # Originator |
paul@0 | 1072 | 0x1: 'I ', # Instructions/manual |
paul@0 | 1073 | 0x2: 'C ', # Author credits |
paul@0 | 1074 | 0x3: 'S ', # Inlay scan |
paul@0 | 1075 | 0x5: 'M ', # Target machine info |
paul@0 | 1076 | 0x6: 'X ', # Multiplexing information |
paul@0 | 1077 | 0x7: 'P ', # Extra palette |
paul@0 | 1078 | 0x100: '# ', # Block information (implicit start/stop bit) |
paul@0 | 1079 | 0x101: '#x', # Multiplexed (as 0x100) |
paul@0 | 1080 | 0x102: '* ', # Generic block information |
paul@0 | 1081 | 0x103: '*x', # Multiplexed generic block (as 0x102) |
paul@0 | 1082 | 0x110: '- ', # High pitched tone |
paul@0 | 1083 | 0x111: '+ ', # High pitched tone with dummy byte |
paul@0 | 1084 | 0x112: '_ ', # Gap (silence) |
paul@0 | 1085 | 0x113: 'B ', # Change of baud rate |
paul@0 | 1086 | 0x120: '! ', # Position marker |
paul@0 | 1087 | 0x200: 'D ', # Disc information |
paul@0 | 1088 | 0x201: 'd ', # Standard disc side |
paul@0 | 1089 | 0x202: 'dx', # Multiplexed disc side |
paul@0 | 1090 | 0x300: 'R ', # Standard machine ROM |
paul@0 | 1091 | 0x301: 'Rx', # Multiplexed machine ROM |
paul@0 | 1092 | 0x400: '6 ', # 6502 standard state |
paul@0 | 1093 | 0x401: 'U ', # Electron ULA state |
paul@0 | 1094 | 0x402: 'W ', # WD1770 state |
paul@0 | 1095 | 0x410: 'm ', # Standard memory data |
paul@0 | 1096 | 0x411: 'mx', # Multiplexed memory data |
paul@0 | 1097 | 0xff00: 'E ' # Emulator identification string |
paul@0 | 1098 | } |
paul@0 | 1099 | |
paul@0 | 1100 | if len(self.chunks) == 0: |
paul@0 | 1101 | print 'No chunks' |
paul@0 | 1102 | return |
paul@0 | 1103 | |
paul@0 | 1104 | # Display chunks |
paul@0 | 1105 | print 'Chunks:' |
paul@0 | 1106 | |
paul@0 | 1107 | n = 0 |
paul@0 | 1108 | |
paul@0 | 1109 | for c in self.chunks: |
paul@0 | 1110 | |
paul@0 | 1111 | if n % 16 == 0: |
paul@0 | 1112 | sys.stdout.write(string.rjust('%i: '% n, 8)) |
paul@0 | 1113 | |
paul@0 | 1114 | if chunks_symbols.has_key(c[0]): |
paul@0 | 1115 | sys.stdout.write(chunks_symbols[c[0]]) |
paul@0 | 1116 | else: |
paul@0 | 1117 | # Unknown |
paul@0 | 1118 | sys.stdout.write('? ') |
paul@0 | 1119 | |
paul@0 | 1120 | if n % 16 == 15: |
paul@0 | 1121 | sys.stdout.write('\n') |
paul@0 | 1122 | |
paul@0 | 1123 | n = n + 1 |
paul@0 | 1124 | |
paul@0 | 1125 | print |