# Look for scriptable applications -- that is, applications with an 'aete' resource # Also contains (partially) reverse engineered 'aete' resource decoding import MacOS import os import string import sys import types import StringIO from Res import * def main(): filename = "" redirect(filename, realmain) def redirect(filename, func, *args): f = filename and open(filename, 'w') save_stdout = sys.stdout try: if f: sys.stdout = f return apply(func, args) finally: sys.stdout = save_stdout if f: f.close() def realmain(): #list('C:System Folder:Extensions:AppleScript\252') #list('C:Tao AppleScript:Finder Liaison:Finder Liaison 1.0') list('C:Tao AppleScript:Scriptable Text Editor') #list('C:Internet:Eudora 1.4.2:Eudora1.4.2') #list('E:Excel 4.0:Microsoft Excel') #list('C:Internet:Netscape 1.0N:Netscape 1.0N') #find('C:') #find('D:') #find('E:') #find('F:') def find(dir, maxlevel = 5): hits = [] cur = CurResFile() names = os.listdir(dir) tuples = map(lambda x: (os.path.normcase(x), x), names) tuples.sort() names = map(lambda (x, y): y, tuples) for name in names: if name in (os.curdir, os.pardir): continue fullname = os.path.join(dir, name) if os.path.islink(fullname): pass if os.path.isdir(fullname): if maxlevel > 0: sys.stderr.write(" %s\n" % `fullname`) hits = hits + find(fullname, maxlevel-1) else: ctor, type = MacOS.GetCreatorAndType(fullname) if type in ('APPL', 'FNDR', 'zsys', 'INIT', 'scri', 'cdev'): sys.stderr.write(" %s\n" % `fullname`) try: rf = OpenRFPerm(fullname, 0, '\1') except MacOS.Error, msg: print "Error:", fullname, msg continue UseResFile(rf) n = Count1Resources('aete') if rf <> cur: CloseResFile(rf) UseResFile(cur) if n > 1: hits.append(fullname) sys.stderr.write("YES! %d in %s\n" % (n, `fullname`)) list(fullname) return hits def list(fullname): cur = CurResFile() rf = OpenRFPerm(fullname, 0, '\1') try: UseResFile(rf) resources = [] for i in range(Count1Resources('aete')): res = Get1IndResource('aete', 1+i) resources.append(res) for i in range(Count1Resources('aeut')): res = Get1IndResource('aeut', 1+i) resources.append(res) print "\nLISTING aete+aeut RESOURCES IN", `fullname` for res in resources: print "decoding", res.GetResInfo(), "..." data = res.data try: aete = decode(data) showaete(aete) print "Checking putaete..." f = StringIO.StringIO() putaete(f, aete) newdata = f.getvalue() if len(newdata) == len(data): if newdata == data: print "putaete created identical data" else: newaete = decode(newdata) if newaete == aete: print "putaete created equivalent data" else: print "putaete failed the test:" showaete(newaete) else: print "putaete created different data:" print `newdata` except: import traceback traceback.print_exc() sys.stdout.flush() finally: if rf <> cur: CloseResFile(rf) UseResFile(cur) def decode(data): f = StringIO.StringIO(data) aete = generic(getaete, f) aete = simplify(aete) processed = f.tell() unprocessed = len(f.read()) total = f.tell() if unprocessed: sys.stderr.write("%d processed + %d unprocessed = %d total\n" % (processed, unprocessed, total)) return aete def simplify(item): if type(item) is types.ListType: return map(simplify, item) elif type(item) == types.TupleType and len(item) == 2: return simplify(item[1]) else: return item # Here follows the aete resource decoder. # It is presented bottom-up instead of top-down because there are direct # references to the lower-level part-decoders from the high-level part-decoders. def getbyte(f, *args): c = f.read(1) if not c: raise EOFError, 'in getbyte' + str(args) return ord(c) def getword(f, *args): getalign(f) s = f.read(2) if len(s) < 2: raise EOFError, 'in getword' + str(args) return (ord(s[0])<<8) | ord(s[1]) def getlong(f, *args): getalign(f) s = f.read(4) if len(s) < 4: raise EOFError, 'in getlong' + str(args) return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3]) def getostype(f, *args): getalign(f) s = f.read(4) if len(s) < 4: raise EOFError, 'in getostype' + str(args) return s def getpstr(f, *args): c = f.read(1) if len(c) < 1: raise EOFError, 'in getpstr[1]' + str(args) nbytes = ord(c) if nbytes == 0: return '' s = f.read(nbytes) if len(s) < nbytes: raise EOFError, 'in getpstr[2]' + str(args) return s def getalign(f): if f.tell() & 1: c = f.read(1) ##if c <> '\0': ## print 'align:', `c` def getlist(f, description, getitem): count = getword(f) list = [] for i in range(count): list.append(generic(getitem, f)) getalign(f) return list def alt_generic(what, f, *args): print "generic", `what`, args res = vageneric(what, f, args) print '->', `res` return res def generic(what, f, *args): if type(what) == types.FunctionType: return apply(what, (f,) + args) if type(what) == types.ListType: record = [] for thing in what: item = apply(generic, thing[:1] + (f,) + thing[1:]) record.append((thing[1], item)) return record return "BAD GENERIC ARGS: %s" % `what` getdata = [ (getostype, "type"), (getpstr, "description"), (getword, "flags") ] getargument = [ (getpstr, "name"), (getostype, "keyword"), (getdata, "what") ] getevent = [ (getpstr, "name"), (getpstr, "description"), (getostype, "suite code"), (getostype, "event code"), (getdata, "returns"), (getdata, "accepts"), (getlist, "optional arguments", getargument) ] getproperty = [ (getpstr, "name"), (getostype, "code"), (getdata, "what") ] getelement = [ (getostype, "type"), (getlist, "keyform", getostype) ] getclass = [ (getpstr, "name"), (getostype, "class code"), (getpstr, "description"), (getlist, "properties", getproperty), (getlist, "elements", getelement) ] getcomparison = [ (getpstr, "operator name"), (getostype, "operator ID"), (getpstr, "operator comment"), ] getenumerator = [ (getpstr, "enumerator name"), (getostype, "enumerator ID"), (getpstr, "enumerator comment") ] getenumeration = [ (getostype, "enumeration ID"), (getlist, "enumerator", getenumerator) ] getsuite = [ (getpstr, "suite name"), (getpstr, "suite description"), (getostype, "suite ID"), (getword, "suite level"), (getword, "suite version"), (getlist, "events", getevent), (getlist, "classes", getclass), (getlist, "comparisons", getcomparison), (getlist, "enumerations", getenumeration) ] getaete = [ (getword, "major/minor version in BCD"), (getword, "language code"), (getword, "script code"), (getlist, "suites", getsuite) ] # Display 'aete' resources in a friendly manner. # This one's done top-down again... def showaete(aete): [version, language, script, suites] = aete major, minor = divmod(version, 256) print "\nVersion %d/%d, language %d, script %d" % \ (major, minor, language, script) for suite in suites: showsuite(suite) def showsuite(suite): [name, desc, code, level, version, events, classes, comps, enums] = suite print "\nSuite %s -- %s (%s)" % (`name`, `desc`, `code`) print "Level %d, version %d" % (level, version) for event in events: showevent(event) for cls in classes: showclass(cls) for comp in comps: showcomparison(comp) for enum in enums: showenumeration(enum) def showevent(event): [name, desc, code, subcode, returns, accepts, arguments] = event print "\n Command %s -- %s (%s, %s)" % (`name`, `desc`, `code`, `subcode`) print " returns", showdata(returns) print " accepts", showdata(accepts) for arg in arguments: showargument(arg) def showargument(arg): [name, keyword, what] = arg print " %s (%s)" % (name, `keyword`), showdata(what) def showclass(cls): [name, code, desc, properties, elements] = cls print "\n Class %s (%s) -- %s" % (`name`, `code`, `desc`) for prop in properties: showproperty(prop) for elem in elements: showelement(elem) def showproperty(prop): [name, code, what] = prop print " property %s (%s)" % (`name`, `code`), showdata(what) def showelement(elem): [code, keyform] = elem print " element %s" % `code`, "as", keyform def showcomparison(comp): [name, code, comment] = comp print " comparison %s (%s) -- %s" % (`name`, `code`, comment) def showenumeration(enum): [code, items] = enum print "\n Enum %s" % `code` for item in items: showenumerator(item) def showenumerator(item): [name, code, desc] = item print " %s (%s) -- %s" % (`name`, `code`, `desc`) def showdata(data): [type, description, flags] = data return "%s -- %s %s" % (`type`, `description`, showdataflags(flags)) dataflagdict = {15: "optional", 14: "list", 13: "enum", 12: "mutable"} def showdataflags(flags): bits = [] for i in range(16): if flags & (1<>8)&0xff)) f.write(chr(value&0xff)) def putostype(f, value): putalign(f) if type(value) != types.StringType or len(value) != 4: raise TypeError, "ostype must be 4-char string" f.write(value) def putpstr(f, value): if type(value) != types.StringType or len(value) > 255: raise TypeError, "pstr must be string <= 255 chars" f.write(chr(len(value)) + value) # Call the main program if __name__ == '__main__': main() else: realmain()