junglejourney

view UEFfile.py @ 225:685e7583ff59

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