blob: 2e0e96708a14fc194e1ceb0b9f5879c600fd847e [file] [log] [blame]
Jack Jansen3997f582003-11-18 23:09:19 +00001r"""Routines to decode AppleSingle files
2"""
Jack Jansena48d4ea2001-09-09 00:35:19 +00003import struct
Jack Jansena48d4ea2001-09-09 00:35:19 +00004import sys
Jack Jansen3997f582003-11-18 23:09:19 +00005try:
6 import MacOS
7 import Carbon.File
8except:
9 class MacOS:
10 def openrf(path, mode):
11 return open(path + '.rsrc', mode)
12 openrf = classmethod(openrf)
13 class Carbon:
14 class File:
15 class FSSpec:
16 pass
17 class FSRef:
18 pass
19 class Alias:
20 pass
Jack Jansena48d4ea2001-09-09 00:35:19 +000021
Jack Jansen3997f582003-11-18 23:09:19 +000022# all of the errors in this module are really errors in the input
23# so I think it should test positive against ValueError.
24class Error(ValueError):
25 pass
Jack Jansena48d4ea2001-09-09 00:35:19 +000026
27# File header format: magic, version, unused, number of entries
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000028AS_HEADER_FORMAT=">LL16sh"
Jack Jansena48d4ea2001-09-09 00:35:19 +000029AS_HEADER_LENGTH=26
30# The flag words for AppleSingle
31AS_MAGIC=0x00051600
32AS_VERSION=0x00020000
33
34# Entry header format: id, offset, length
Jack Jansende540e22006-02-23 14:54:30 +000035AS_ENTRY_FORMAT=">lll"
Jack Jansena48d4ea2001-09-09 00:35:19 +000036AS_ENTRY_LENGTH=12
37
38# The id values
39AS_DATAFORK=1
40AS_RESOURCEFORK=2
41AS_IGNORE=(3,4,5,6,8,9,10,11,12,13,14,15)
42
Jack Jansen3997f582003-11-18 23:09:19 +000043class AppleSingle(object):
44 datafork = None
45 resourcefork = None
Jack Jansena48d4ea2001-09-09 00:35:19 +000046
Jack Jansen3997f582003-11-18 23:09:19 +000047 def __init__(self, fileobj, verbose=False):
48 header = fileobj.read(AS_HEADER_LENGTH)
49 try:
50 magic, version, ig, nentry = struct.unpack(AS_HEADER_FORMAT, header)
Guido van Rossumb940e112007-01-10 16:19:56 +000051 except ValueError as arg:
Jack Jansen3997f582003-11-18 23:09:19 +000052 raise Error, "Unpack header error: %s" % (arg,)
53 if verbose:
Guido van Rossumbe19ed72007-02-09 05:37:30 +000054 print('Magic: 0x%8.8x' % (magic,))
55 print('Version: 0x%8.8x' % (version,))
56 print('Entries: %d' % (nentry,))
Jack Jansen3997f582003-11-18 23:09:19 +000057 if magic != AS_MAGIC:
58 raise Error, "Unknown AppleSingle magic number 0x%8.8x" % (magic,)
59 if version != AS_VERSION:
60 raise Error, "Unknown AppleSingle version number 0x%8.8x" % (version,)
61 if nentry <= 0:
62 raise Error, "AppleSingle file contains no forks"
63 headers = [fileobj.read(AS_ENTRY_LENGTH) for i in xrange(nentry)]
64 self.forks = []
65 for hdr in headers:
66 try:
67 restype, offset, length = struct.unpack(AS_ENTRY_FORMAT, hdr)
Guido van Rossumb940e112007-01-10 16:19:56 +000068 except ValueError as arg:
Jack Jansen3997f582003-11-18 23:09:19 +000069 raise Error, "Unpack entry error: %s" % (arg,)
70 if verbose:
Guido van Rossumbe19ed72007-02-09 05:37:30 +000071 print("Fork %d, offset %d, length %d" % (restype, offset, length))
Jack Jansen3997f582003-11-18 23:09:19 +000072 fileobj.seek(offset)
73 data = fileobj.read(length)
74 if len(data) != length:
75 raise Error, "Short read: expected %d bytes got %d" % (length, len(data))
76 self.forks.append((restype, data))
77 if restype == AS_DATAFORK:
78 self.datafork = data
79 elif restype == AS_RESOURCEFORK:
80 self.resourcefork = data
Tim Peters182b5ac2004-07-18 06:16:08 +000081
Jack Jansen3997f582003-11-18 23:09:19 +000082 def tofile(self, path, resonly=False):
83 outfile = open(path, 'wb')
84 data = False
85 if resonly:
86 if self.resourcefork is None:
87 raise Error, "No resource fork found"
88 fp = open(path, 'wb')
89 fp.write(self.resourcefork)
90 fp.close()
91 elif (self.resourcefork is None and self.datafork is None):
92 raise Error, "No useful forks found"
93 else:
94 if self.datafork is not None:
95 fp = open(path, 'wb')
96 fp.write(self.datafork)
97 fp.close()
98 if self.resourcefork is not None:
99 fp = MacOS.openrf(path, '*wb')
100 fp.write(self.resourcefork)
101 fp.close()
Tim Peters182b5ac2004-07-18 06:16:08 +0000102
Jack Jansen3997f582003-11-18 23:09:19 +0000103def decode(infile, outpath, resonly=False, verbose=False):
104 """decode(infile, outpath [, resonly=False, verbose=False])
105
106 Creates a decoded file from an AppleSingle encoded file.
Tim Peters182b5ac2004-07-18 06:16:08 +0000107 If resonly is True, then it will create a regular file at
Jack Jansen3997f582003-11-18 23:09:19 +0000108 outpath containing only the resource fork from infile.
109 Otherwise it will create an AppleDouble file at outpath
Tim Peters182b5ac2004-07-18 06:16:08 +0000110 with the data and resource forks from infile. On platforms
Jack Jansen3997f582003-11-18 23:09:19 +0000111 without the MacOS module, it will create inpath and inpath+'.rsrc'
112 with the data and resource forks respectively.
113
114 """
115 if not hasattr(infile, 'read'):
116 if isinstance(infile, Carbon.File.Alias):
117 infile = infile.ResolveAlias()[0]
118 if isinstance(infile, (Carbon.File.FSSpec, Carbon.File.FSRef)):
119 infile = infile.as_pathname()
120 infile = open(infile, 'rb')
121
Neal Norwitzf84c38a2006-03-22 07:12:41 +0000122 asfile = AppleSingle(infile, verbose=verbose)
123 asfile.tofile(outpath, resonly=resonly)
Tim Peters182b5ac2004-07-18 06:16:08 +0000124
Jack Jansena48d4ea2001-09-09 00:35:19 +0000125def _test():
Jack Jansen0ae32202003-04-09 13:25:43 +0000126 if len(sys.argv) < 3 or sys.argv[1] == '-r' and len(sys.argv) != 4:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000127 print('Usage: applesingle.py [-r] applesinglefile decodedfile')
Jack Jansen0ae32202003-04-09 13:25:43 +0000128 sys.exit(1)
129 if sys.argv[1] == '-r':
Jack Jansen3997f582003-11-18 23:09:19 +0000130 resonly = True
Jack Jansen0ae32202003-04-09 13:25:43 +0000131 del sys.argv[1]
132 else:
Jack Jansen3997f582003-11-18 23:09:19 +0000133 resonly = False
Jack Jansen0ae32202003-04-09 13:25:43 +0000134 decode(sys.argv[1], sys.argv[2], resonly=resonly)
Tim Peters182b5ac2004-07-18 06:16:08 +0000135
Jack Jansena48d4ea2001-09-09 00:35:19 +0000136if __name__ == '__main__':
Jack Jansen0ae32202003-04-09 13:25:43 +0000137 _test()