|
1 #!/usr/bin/env python |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 ############################################################################## |
|
5 # decodemif.py - Decodes a Symbian OS v9.x multi-image file (MIF) |
|
6 # Copyright 2006, 2007 Jussi Ylänen |
|
7 # |
|
8 # This program is part of Ensymble developer utilities for Symbian OS(TM). |
|
9 # |
|
10 # Ensymble is free software; you can redistribute it and/or modify |
|
11 # it under the terms of the GNU General Public License as published by |
|
12 # the Free Software Foundation; either version 2 of the License, or |
|
13 # (at your option) any later version. |
|
14 # |
|
15 # Ensymble is distributed in the hope that it will be useful, |
|
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
18 # GNU General Public License for more details. |
|
19 # |
|
20 # You should have received a copy of the GNU General Public License |
|
21 # along with Ensymble; if not, write to the Free Software |
|
22 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
23 # |
|
24 # |
|
25 # Version history |
|
26 # --------------- |
|
27 # |
|
28 # v0.03 2006-09-22 |
|
29 # Replaced every possible range(...) with xrange(...) for efficiency |
|
30 # |
|
31 # v0.02 2006-08-22 |
|
32 # Added file type recognition (SVG, binary SVG or other) |
|
33 # |
|
34 # v0.01 2006-08-14 |
|
35 # Added support for strange index entries (length 0) |
|
36 # |
|
37 # v0.00 2006-08-13 |
|
38 # Work started |
|
39 ############################################################################## |
|
40 |
|
41 VERSION = "v0.02 2006-09-22" |
|
42 |
|
43 import sys |
|
44 import os |
|
45 import struct |
|
46 import getopt |
|
47 import random |
|
48 import tempfile |
|
49 |
|
50 # Parameters |
|
51 MAXMIFFILESIZE = 1024 * 1024 # Arbitrary maximum size of MIF file |
|
52 |
|
53 tempdir = None |
|
54 dumpcounter = 0 |
|
55 |
|
56 def mkdtemp(template): |
|
57 ''' |
|
58 Create a unique temporary directory. |
|
59 |
|
60 tempfile.mkdtemp() was introduced in Python v2.3. This is for |
|
61 backward compatibility. |
|
62 ''' |
|
63 |
|
64 # Cross-platform way to determine a suitable location for temporary files. |
|
65 systemp = tempfile.gettempdir() |
|
66 |
|
67 if not template.endswith("XXXXXX"): |
|
68 raise ValueError("invalid template for mkdtemp(): %s" % template) |
|
69 |
|
70 for n in xrange(10000): |
|
71 randchars = [] |
|
72 for m in xrange(6): |
|
73 randchars.append(random.choice("abcdefghijklmnopqrstuvwxyz")) |
|
74 |
|
75 tempdir = os.path.join(systemp, template[: -6]) + "".join(randchars) |
|
76 |
|
77 try: |
|
78 os.mkdir(tempdir, 0700) |
|
79 return tempdir |
|
80 except OSError: |
|
81 pass |
|
82 |
|
83 def dumpdata(data): |
|
84 '''Dumps data to a file in a temporary directory.''' |
|
85 |
|
86 global tempdir, dumpcounter |
|
87 |
|
88 if tempdir == None: |
|
89 # Create temporary directory for dumped files. |
|
90 tempdir = mkdtemp("decodemif-XXXXXX") |
|
91 dumpcounter = 0 |
|
92 |
|
93 # Determine file type. |
|
94 if data[0:5] == "<?xml": |
|
95 ext = "svg" |
|
96 elif data[0:4] == '\xcc\x56\xfa\x03': |
|
97 ext = "svgb" |
|
98 else: |
|
99 ext = "dat" |
|
100 |
|
101 filename = os.path.join(tempdir, "dump%04d.%s" % (dumpcounter, ext)) |
|
102 dumpcounter += 1 |
|
103 f = file(filename, "wb") |
|
104 f.write(data) |
|
105 f.close() |
|
106 print "%s written" % filename |
|
107 |
|
108 def main(): |
|
109 global tempdir, dumpcounter |
|
110 |
|
111 pgmname = os.path.basename(sys.argv[0]) |
|
112 pgmversion = VERSION |
|
113 |
|
114 try: |
|
115 try: |
|
116 gopt = getopt.gnu_getopt |
|
117 except: |
|
118 # Python <v2.3, GNU-style parameter ordering not supported. |
|
119 gopt = getopt.getopt |
|
120 |
|
121 # Parse command line using getopt. |
|
122 short_opts = "t:h" |
|
123 long_opts = [ |
|
124 "dumpdir", "help" |
|
125 ] |
|
126 args = gopt(sys.argv[1:], short_opts, long_opts) |
|
127 |
|
128 opts = dict(args[0]) |
|
129 pargs = args[1] |
|
130 |
|
131 if "--help" in opts.keys() or "-h" in opts.keys(): |
|
132 # Help requested. |
|
133 print ( |
|
134 ''' |
|
135 DecodeMIF - Symbian OS v9.x MIF file decoder %(pgmversion)s |
|
136 |
|
137 usage: %(pgmname)s [--dumpdir=DIR] [miffiles...] |
|
138 |
|
139 -t, --dumpdir - Directory to use for dumped files (or automatic) |
|
140 miffiles - MIF files to decode (stdin if not given or -) |
|
141 |
|
142 ''' % locals()) |
|
143 return 0 |
|
144 |
|
145 # A temporary directory is generated by default. |
|
146 tempdir = opts.get("--dumpdir", opts.get("-t", None)) |
|
147 |
|
148 if len(pargs) == 0: |
|
149 miffilenames = ["-"] |
|
150 else: |
|
151 miffilenames = pargs |
|
152 |
|
153 for miffilename in miffilenames: |
|
154 if miffilename == '-': |
|
155 miffile = sys.stdin |
|
156 else: |
|
157 miffile = file(miffilename, "rb") |
|
158 |
|
159 try: |
|
160 # Load the whole MIF file as a string. |
|
161 mifdata = miffile.read(MAXMIFFILESIZE) |
|
162 if len(mifdata) == MAXMIFFILESIZE: |
|
163 raise IOError("%s: file too large" % miffilename) |
|
164 finally: |
|
165 if miffile != sys.stdin: |
|
166 miffile.close() |
|
167 |
|
168 # Verify MIF signature. |
|
169 if mifdata[:4] != "B##4": |
|
170 raise ValueError("%s: not a MIF file" % miffilename) |
|
171 |
|
172 if len(mifdata) < 16: |
|
173 raise ValueError("%s: file too short" % miffilename) |
|
174 |
|
175 entries = struct.unpack("<L", mifdata[12:16])[0] / 2 |
|
176 |
|
177 # Verify header length: |
|
178 # 16-byte header, 16 bytes per index entry |
|
179 if len(mifdata) < (16 + 16 * entries): |
|
180 raise ValueError("%s: file too short" % miffilename) |
|
181 |
|
182 # Read index. |
|
183 index = [] |
|
184 for n in xrange(entries): |
|
185 hdroff = 16 + n * 16 |
|
186 a = struct.unpack("<L", mifdata[hdroff + 0:hdroff + 4])[0] |
|
187 b = struct.unpack("<L", mifdata[hdroff + 4:hdroff + 8])[0] |
|
188 c = struct.unpack("<L", mifdata[hdroff + 8:hdroff + 12])[0] |
|
189 d = struct.unpack("<L", mifdata[hdroff + 12:hdroff + 16])[0] |
|
190 |
|
191 if b == 0 and d == 0: |
|
192 # Unknown index entry type, skip it. |
|
193 continue |
|
194 |
|
195 if a != c or b != d: |
|
196 raise ValueError("%s: invalid index entry %d" % |
|
197 (miffilename, n)) |
|
198 |
|
199 # Check total length of file. |
|
200 if a + b > len(mifdata): |
|
201 raise ValueError("%s: index %d out of range" % |
|
202 (miffilename, n)) |
|
203 |
|
204 index.append((a, b)) |
|
205 |
|
206 n = len(index) |
|
207 print "%s: %s %s inside" % (miffilename, n or "no", |
|
208 ((n == 1) and "file") or "files") |
|
209 |
|
210 # Extract contents. |
|
211 for i in index: |
|
212 offset = i[0] |
|
213 length = i[1] |
|
214 if mifdata[offset:offset + 4] != "C##4": |
|
215 raise ValueError("%s: invalid file header %d" % |
|
216 (miffilename, n)) |
|
217 |
|
218 print "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x" % ( |
|
219 tuple(struct.unpack("<LLLLLLL", mifdata[(offset + 4): |
|
220 (offset + 32)]))) |
|
221 dumpdata(mifdata[offset + 32:offset + length + 32]) |
|
222 except (TypeError, ValueError, IOError, OSError), e: |
|
223 return "%s: %s" % (pgmname, str(e)) |
|
224 except KeyboardInterrupt: |
|
225 return "" |
|
226 |
|
227 # Call main if run as stand-alone executable. |
|
228 if __name__ == '__main__': |
|
229 sys.exit(main()) |