Guido van Rossum | 17448e2 | 1995-01-30 11:53:55 +0000 | [diff] [blame^] | 1 | # Look for scriptable applications -- that is, applications with an 'aete' resource |
| 2 | # Also contains (partially) reverse engineered 'aete' resource decoding |
| 3 | |
| 4 | import MacOS |
| 5 | import os |
| 6 | import string |
| 7 | import sys |
| 8 | import types |
| 9 | import StringIO |
| 10 | |
| 11 | from Res import * |
| 12 | |
| 13 | def main(): |
| 14 | filename = raw_input("Listing file? (default stdout): ") |
| 15 | redirect(filename, realmain) |
| 16 | |
| 17 | def redirect(filename, func, *args): |
| 18 | f = filename and open(filename, 'w') |
| 19 | save_stdout = sys.stdout |
| 20 | try: |
| 21 | if f: sys.stdout = f |
| 22 | return apply(func, args) |
| 23 | finally: |
| 24 | sys.stdout = save_stdout |
| 25 | if f: f.close() |
| 26 | |
| 27 | def realmain(): |
| 28 | #list('C:Tao AppleScript:Finder Liaison:Finder Liaison 1.0') |
| 29 | #list('C:Internet:Eudora 1.4.2:Eudora1.4.2') |
| 30 | list('E:Excel 4.0:Microsoft Excel') |
| 31 | #list('C:Internet:Netscape 1.0N:Netscape 1.0N') |
| 32 | #find('C:') |
| 33 | #find('D:') |
| 34 | #find('E:') |
| 35 | #find('F:') |
| 36 | |
| 37 | def find(dir, maxlevel = 5): |
| 38 | hits = [] |
| 39 | cur = CurResFile() |
| 40 | names = os.listdir(dir) |
| 41 | tuples = map(lambda x: (os.path.normcase(x), x), names) |
| 42 | tuples.sort() |
| 43 | names = map(lambda (x, y): y, tuples) |
| 44 | for name in names: |
| 45 | if name in (os.curdir, os.pardir): continue |
| 46 | fullname = os.path.join(dir, name) |
| 47 | if os.path.islink(fullname): |
| 48 | pass |
| 49 | if os.path.isdir(fullname): |
| 50 | if maxlevel > 0: |
| 51 | sys.stderr.write(" %s\n" % `fullname`) |
| 52 | hits = hits + find(fullname, maxlevel-1) |
| 53 | else: |
| 54 | ctor, type = MacOS.GetCreatorAndType(fullname) |
| 55 | if type == 'APPL': |
| 56 | sys.stderr.write(" %s\n" % `fullname`) |
| 57 | try: |
| 58 | rf = OpenRFPerm(fullname, 0, '\1') |
| 59 | except MacOS.Error, msg: |
| 60 | print "Error:", fullname, msg |
| 61 | continue |
| 62 | UseResFile(rf) |
| 63 | n = Count1Resources('aete') |
| 64 | if rf <> cur: |
| 65 | CloseResFile(rf) |
| 66 | UseResFile(cur) |
| 67 | if n > 1: |
| 68 | hits.append(fullname) |
| 69 | sys.stderr.write("YES! %d in %s\n" % (n, `fullname`)) |
| 70 | list(fullname) |
| 71 | return hits |
| 72 | |
| 73 | def list(fullname): |
| 74 | cur = CurResFile() |
| 75 | rf = OpenRFPerm(fullname, 0, '\1') |
| 76 | try: |
| 77 | UseResFile(rf) |
| 78 | resources = [] |
| 79 | for i in range(Count1Resources('aete')): |
| 80 | res = Get1IndResource('aete', 1+i) |
| 81 | resources.append(res) |
| 82 | for i in range(Count1Resources('aeut')): |
| 83 | res = Get1IndResource('aeut', 1+i) |
| 84 | resources.append(res) |
| 85 | print "\nLISTING aete+aeut RESOURCE IN", `fullname` |
| 86 | for res in resources: |
| 87 | print res.GetResInfo() |
| 88 | data = res.data |
| 89 | try: |
| 90 | aete = decode(data) |
| 91 | showaete(aete) |
| 92 | f = StringIO.StringIO() |
| 93 | putaete(f, aete) |
| 94 | newdata = f.getvalue() |
| 95 | if len(newdata) == len(data): |
| 96 | if newdata == data: |
| 97 | print "putaete created identical data" |
| 98 | else: |
| 99 | newaete = decode(newdata) |
| 100 | if newaete == aete: |
| 101 | print "putaete created equivalent data" |
| 102 | else: |
| 103 | print "putaete failed the test:" |
| 104 | showaete(newaete) |
| 105 | else: |
| 106 | print "putaete created different data:" |
| 107 | print `newdata` |
| 108 | except: |
| 109 | import traceback |
| 110 | traceback.print_exc() |
| 111 | sys.stdout.flush() |
| 112 | finally: |
| 113 | if rf <> cur: |
| 114 | CloseResFile(rf) |
| 115 | UseResFile(cur) |
| 116 | |
| 117 | def decode(data): |
| 118 | f = StringIO.StringIO(data) |
| 119 | aete = generic(getaete, f) |
| 120 | aete = simplify(aete) |
| 121 | processed = f.tell() |
| 122 | unprocessed = len(f.read()) |
| 123 | total = f.tell() |
| 124 | if unprocessed: |
| 125 | sys.stderr.write("%d processed + %d unprocessed = %d total\n" % |
| 126 | (processed, unprocessed, total)) |
| 127 | return aete |
| 128 | |
| 129 | def simplify(item): |
| 130 | if type(item) is types.ListType: |
| 131 | return map(simplify, item) |
| 132 | elif type(item) == types.TupleType and len(item) == 2: |
| 133 | return simplify(item[1]) |
| 134 | else: |
| 135 | return item |
| 136 | |
| 137 | |
| 138 | # Here follows the aete resource decoder. |
| 139 | # It is presented bottom-up instead of top-down because there are direct |
| 140 | # references to the lower-level part-decoders from the high-level part-decoders. |
| 141 | |
| 142 | def getword(f, *args): |
| 143 | getalign(f) |
| 144 | s = f.read(2) |
| 145 | if len(s) < 2: |
| 146 | raise EOFError, 'in getword' + str(args) |
| 147 | return (ord(s[0])<<8) | ord(s[1]) |
| 148 | |
| 149 | def getlong(f, *args): |
| 150 | getalign(f) |
| 151 | s = f.read(4) |
| 152 | if len(s) < 4: |
| 153 | raise EOFError, 'in getlong' + str(args) |
| 154 | return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3]) |
| 155 | |
| 156 | def getostype(f, *args): |
| 157 | getalign(f) |
| 158 | s = f.read(4) |
| 159 | if len(s) < 4: |
| 160 | raise EOFError, 'in getostype' + str(args) |
| 161 | return s |
| 162 | |
| 163 | def getpstr(f, *args): |
| 164 | c = f.read(1) |
| 165 | if len(c) < 1: |
| 166 | raise EOFError, 'in getpstr[1]' + str(args) |
| 167 | nbytes = ord(c) |
| 168 | if nbytes == 0: return '' |
| 169 | s = f.read(nbytes) |
| 170 | if len(s) < nbytes: |
| 171 | raise EOFError, 'in getpstr[2]' + str(args) |
| 172 | return s |
| 173 | |
| 174 | def getalign(f): |
| 175 | if f.tell() & 1: |
| 176 | c = f.read(1) |
| 177 | ##if c <> '\0': |
| 178 | ## print 'align:', `c` |
| 179 | |
| 180 | def getlist(f, description, getitem): |
| 181 | count = getword(f) |
| 182 | list = [] |
| 183 | for i in range(count): |
| 184 | getalign(f) |
| 185 | list.append(generic(getitem, f)) |
| 186 | return list |
| 187 | |
| 188 | def alt_generic(what, f, *args): |
| 189 | print "generic", `what`, args |
| 190 | res = vageneric(what, f, args) |
| 191 | print '->', `res` |
| 192 | return res |
| 193 | |
| 194 | def generic(what, f, *args): |
| 195 | if type(what) == types.FunctionType: |
| 196 | return apply(what, (f,) + args) |
| 197 | if type(what) == types.ListType: |
| 198 | record = [] |
| 199 | for thing in what: |
| 200 | item = apply(generic, thing[:1] + (f,) + thing[1:]) |
| 201 | record.append((thing[1], item)) |
| 202 | return record |
| 203 | return "BAD GENERIC ARGS: %s" % `what` |
| 204 | |
| 205 | getdata = [(getostype, "type"), (getpstr, "description"), (getword, "flags")] |
| 206 | getoptarg = [(getpstr, "name"), (getostype, "keyword"), (getdata, "what")] |
| 207 | getcommand = [(getpstr, "name"), (getpstr, "description"), |
| 208 | (getostype, "suite code"), (getostype, "command code"), |
| 209 | (getdata, "returns"), |
| 210 | (getdata, "accepts"), |
| 211 | (getlist, "optional arguments", getoptarg)] |
| 212 | getprop = [(getpstr, "name"), (getostype, "code"), (getdata, "what")] |
| 213 | getelem = [(getostype, "type"), (getlist, "accessibility", getostype)] |
| 214 | getclass = [(getpstr, "name"), (getostype, "class code"), (getpstr, "description"), |
| 215 | (getlist, "properties", getprop), (getlist, "elements", getelem)] |
| 216 | getenumitem = [(getpstr, "name"), (getostype, "value"), (getpstr, "description")] |
| 217 | getenum = [(getostype, "enumtype"), (getlist, "enumitem", getenumitem)] |
| 218 | getsuite = [(getpstr, "name"), (getpstr, "description"), (getostype, "code"), |
| 219 | (getword, "flags1"), (getword, "flags2"), |
| 220 | (getlist, "commands", getcommand), |
| 221 | (getlist, "classes", getclass), |
| 222 | (getword, "count???"), (getlist, "enums", getenum)] |
| 223 | getaete = [(getword, "skip1"), (getword, "skip2"), (getword, "skip3"), |
| 224 | (getlist, "suites", getsuite)] |
| 225 | |
| 226 | |
| 227 | # Display 'aete' resources in a friendly manner. |
| 228 | # This one's done top-down again... |
| 229 | |
| 230 | def showaete(aete): |
| 231 | [flags1, flags2, flags3, suites] = aete |
| 232 | print "\nGlobal flags: x%x, x%x, x%x\n" % (flags1, flags2, flags3) |
| 233 | for suite in suites: |
| 234 | showsuite(suite) |
| 235 | |
| 236 | def showsuite(suite): |
| 237 | [name, desc, code, flags1, flags2, commands, classes, skip1, enums] = suite |
| 238 | print "\nSuite %s -- %s (%s)" % (`name`, `desc`, `code`) |
| 239 | for command in commands: |
| 240 | showcommand(command) |
| 241 | for classe in classes: |
| 242 | showclass(classe) |
| 243 | for enum in enums: |
| 244 | showenum(enum) |
| 245 | |
| 246 | def showcommand(command): |
| 247 | [name, desc, code, subcode, returns, accepts, arguments] = command |
| 248 | print "\n Command %s -- %s (%s, %s)" % (`name`, `desc`, `code`, `subcode`) |
| 249 | print " returns", showdata(returns) |
| 250 | print " accepts", showdata(accepts) |
| 251 | for arg in arguments: |
| 252 | showargument(arg) |
| 253 | |
| 254 | def showargument(arg): |
| 255 | [name, keyword, what] = arg |
| 256 | print " %s (%s)" % (name, `keyword`), showdata(what) |
| 257 | |
| 258 | def showclass(classe): |
| 259 | [name, code, desc, properties, elements] = classe |
| 260 | print "\n Class %s (%s) -- %s" % (`name`, `code`, `desc`) |
| 261 | for prop in properties: |
| 262 | showproperty(prop) |
| 263 | for elem in elements: |
| 264 | showelement(elem) |
| 265 | |
| 266 | def showproperty(prop): |
| 267 | [name, code, what] = prop |
| 268 | print " property %s (%s)" % (name, code), showdata(what) |
| 269 | |
| 270 | def showelement(elem): |
| 271 | [code, accessibility] = elem |
| 272 | print " element %s" % `code`, "as", accessibility |
| 273 | |
| 274 | def showenum(enum): |
| 275 | [code, items] = enum |
| 276 | print "\n Enum %s" % `code` |
| 277 | for item in items: |
| 278 | showitem(item) |
| 279 | |
| 280 | def showitem(item): |
| 281 | [name, code, desc] = item |
| 282 | print " %s (%s) -- %s" % (`name`, `code`, `desc`) |
| 283 | |
| 284 | def showdata(data): |
| 285 | [type, description, flags] = data |
| 286 | return "%s -- %s %s" % (`type`, `description`, showdataflags(flags)) |
| 287 | |
| 288 | dataflagdict = {15: "optional", 14: "list", 13: "enum", 12: "writable"} |
| 289 | def showdataflags(flags): |
| 290 | bits = [] |
| 291 | for i in range(16): |
| 292 | if flags & (1<<i): |
| 293 | if i in dataflagdict.keys(): |
| 294 | bits.append(dataflagdict[i]) |
| 295 | else: |
| 296 | bits.append(`i`) |
| 297 | return '[%s]' % string.join(bits) |
| 298 | |
| 299 | |
| 300 | # Write an 'aete' resource. |
| 301 | # Closedly modelled after showaete()... |
| 302 | |
| 303 | def putaete(f, aete): |
| 304 | [flags1, flags2, flags3, suites] = aete |
| 305 | putword(f, flags1) |
| 306 | putword(f, flags2) |
| 307 | putword(f, flags3) |
| 308 | putlist(f, suites, putsuite) |
| 309 | |
| 310 | def putsuite(f, suite): |
| 311 | [name, desc, code, flags1, flags2, commands, classes, skip1, enums] = suite |
| 312 | putpstr(f, name) |
| 313 | putpstr(f, desc) |
| 314 | putostype(f, code) |
| 315 | putword(f, flags1) |
| 316 | putword(f, flags2) |
| 317 | putlist(f, commands, putcommand) |
| 318 | putlist(f, classes, putclass) |
| 319 | putword(f, skip1) |
| 320 | putlist(f, enums, putenum) |
| 321 | |
| 322 | def putcommand(f, command): |
| 323 | [name, desc, code, subcode, returns, accepts, arguments] = command |
| 324 | putpstr(f, name) |
| 325 | putpstr(f, desc) |
| 326 | putostype(f, code) |
| 327 | putostype(f, subcode) |
| 328 | putdata(f, returns) |
| 329 | putdata(f, accepts) |
| 330 | putlist(f, arguments, putargument) |
| 331 | |
| 332 | def putargument(f, arg): |
| 333 | [name, keyword, what] = arg |
| 334 | putpstr(f, name) |
| 335 | putostype(f, keyword) |
| 336 | putdata(f, what) |
| 337 | |
| 338 | def putclass(f, classe): |
| 339 | [name, code, desc, properties, elements] = classe |
| 340 | putpstr(f, name) |
| 341 | putostype(f, code) |
| 342 | putpstr(f, desc) |
| 343 | putlist(f, properties, putproperty) |
| 344 | putlist(f, elements, putelement) |
| 345 | |
| 346 | putproperty = putargument |
| 347 | |
| 348 | def putelement(f, elem): |
| 349 | [code, parts] = elem |
| 350 | putostype(f, code) |
| 351 | putlist(f, parts, putostype) |
| 352 | |
| 353 | def putenum(f, enum): |
| 354 | [code, items] = enum |
| 355 | putostype(f, code) |
| 356 | putlist(f, items, putitem) |
| 357 | |
| 358 | def putitem(f, item): |
| 359 | [name, code, desc] = item |
| 360 | putpstr(f, name) |
| 361 | putostype(f, code) |
| 362 | putpstr(f, desc) |
| 363 | |
| 364 | def putdata(f, data): |
| 365 | [type, description, flags] = data |
| 366 | putostype(f, type) |
| 367 | putpstr(f, description) |
| 368 | putword(f, flags) |
| 369 | |
| 370 | def putlist(f, list, putitem): |
| 371 | putword(f, len(list)) |
| 372 | for item in list: |
| 373 | putalign(f) |
| 374 | putitem(f, item) |
| 375 | |
| 376 | def putalign(f): |
| 377 | if f.tell() & 1: |
| 378 | f.write('\0') |
| 379 | |
| 380 | def putword(f, value): |
| 381 | putalign(f) |
| 382 | f.write(chr((value>>8)&0xff)) |
| 383 | f.write(chr(value&0xff)) |
| 384 | |
| 385 | def putostype(f, value): |
| 386 | putalign(f) |
| 387 | if type(value) != types.StringType or len(value) != 4: |
| 388 | raise TypeError, "ostype must be 4-char string" |
| 389 | f.write(value) |
| 390 | |
| 391 | def putpstr(f, value): |
| 392 | if type(value) != types.StringType or len(value) > 255: |
| 393 | raise TypeError, "pstr must be string <= 255 chars" |
| 394 | f.write(chr(len(value)) + value) |
| 395 | |
| 396 | |
| 397 | # Call the main program |
| 398 | |
| 399 | if __name__ == '__main__': |
| 400 | main() |
| 401 | else: |
| 402 | realmain() |