Jack Jansen | a48d4ea | 2001-09-09 00:35:19 +0000 | [diff] [blame^] | 1 | # applesingle - a module to decode AppleSingle files |
| 2 | import struct |
| 3 | import MacOS |
| 4 | import sys |
| 5 | |
| 6 | Error="applesingle.Error" |
| 7 | |
| 8 | verbose=0 |
| 9 | |
| 10 | # File header format: magic, version, unused, number of entries |
| 11 | AS_HEADER_FORMAT="ll16sh" |
| 12 | AS_HEADER_LENGTH=26 |
| 13 | # The flag words for AppleSingle |
| 14 | AS_MAGIC=0x00051600 |
| 15 | AS_VERSION=0x00020000 |
| 16 | |
| 17 | # Entry header format: id, offset, length |
| 18 | AS_ENTRY_FORMAT="lll" |
| 19 | AS_ENTRY_LENGTH=12 |
| 20 | |
| 21 | # The id values |
| 22 | AS_DATAFORK=1 |
| 23 | AS_RESOURCEFORK=2 |
| 24 | AS_IGNORE=(3,4,5,6,8,9,10,11,12,13,14,15) |
| 25 | |
| 26 | def decode(input, output, resonly=0): |
| 27 | if type(input) == type(''): |
| 28 | input = open(input, 'rb') |
| 29 | # Should we also test for FSSpecs or FSRefs? |
| 30 | header = input.read(AS_HEADER_LENGTH) |
| 31 | print `header` |
| 32 | try: |
| 33 | magic, version, dummy, nentry = struct.unpack(AS_HEADER_FORMAT, header) |
| 34 | except ValueError, arg: |
| 35 | raise Error, "Unpack header error: %s"%arg |
| 36 | if verbose: |
| 37 | print 'Magic: 0x%8.8x'%magic |
| 38 | print 'Version: 0x%8.8x'%version |
| 39 | print 'Entries: %d'%nentry |
| 40 | if magic != AS_MAGIC: |
| 41 | raise Error, 'Unknown AppleSingle magic number 0x%8.8x'%magic |
| 42 | if version != AS_VERSION: |
| 43 | raise Error, 'Unknown AppleSingle version number 0x%8.8x'%version |
| 44 | if nentry <= 0: |
| 45 | raise Error, "AppleSingle file contains no forks" |
| 46 | headers = [input.read(AS_ENTRY_LENGTH) for i in range(nentry)] |
| 47 | didwork = 0 |
| 48 | for hdr in headers: |
| 49 | try: |
| 50 | id, offset, length = struct.unpack(AS_ENTRY_FORMAT, hdr) |
| 51 | except ValueError, arg: |
| 52 | raise Error, "Unpack entry error: %s"%arg |
| 53 | if verbose: |
| 54 | print 'Fork %d, offset %d, length %d'%(id, offset, length) |
| 55 | input.seek(offset) |
| 56 | if length == 0: |
| 57 | data = '' |
| 58 | else: |
| 59 | data = input.read(length) |
| 60 | if len(data) != length: |
| 61 | raise Error, 'Short read: expected %d bytes got %d'%(length, len(data)) |
| 62 | if id == AS_DATAFORK: |
| 63 | if verbose: |
| 64 | print ' (data fork)' |
| 65 | if not resonly: |
| 66 | didwork = 1 |
| 67 | fp = open(output, 'wb') |
| 68 | fp.write(data) |
| 69 | fp.close() |
| 70 | elif id == AS_RESOURCEFORK: |
| 71 | didwork = 1 |
| 72 | if verbose: |
| 73 | print ' (resource fork)' |
| 74 | if resonly: |
| 75 | fp = open(output, 'wb') |
| 76 | else: |
| 77 | fp = MacOS.openrf(output, 'wb') |
| 78 | fp.write(data) |
| 79 | fp.close() |
| 80 | elif id in AS_IGNORE: |
| 81 | if verbose: |
| 82 | print ' (ignored)' |
| 83 | else: |
| 84 | raise Error, 'Unknown fork type %d'%id |
| 85 | if not didwork: |
| 86 | raise Error, 'No useful forks found' |
| 87 | |
| 88 | def _test(): |
| 89 | if len(sys.argv) < 3 or sys.argv[1] == '-r' and len(sys.argv) != 4: |
| 90 | print 'Usage: applesingle.py [-r] applesinglefile decodedfile' |
| 91 | sys.exit(1) |
| 92 | if sys.argv[1] == '-r': |
| 93 | resonly = 1 |
| 94 | del sys.argv[1] |
| 95 | else: |
| 96 | resonly = 0 |
| 97 | decode(sys.argv[1], sys.argv[2], resonly=resonly) |
| 98 | |
| 99 | if __name__ == '__main__': |
| 100 | _test() |
| 101 | |